【画像処理】ヘッダ情報の確認とバイナリエディタからの画像編集(PNG編)


ヘッダ情報の確認とバイナリエディタからの画像編集(PNG編)
前回の記事→「[ヘッダ情報の確認とバイナリエディタからの画像編集(JPEG編)](https://qiita.com/spc_ehara/items/87d383a59a37a2c82531)」

今回はpng画像の中身にはどんな情報が含まれているのかを理解するためにバイナリエディタを使用してpngファイル形式の画像内部を見ていきます。

## ファイル形式ごとのフォーマット

そもそも画像ファイルには、画像ビューアで画面に表示されているデータ以外にも、画像の幅・高さ、画像そのもの以外のデータサイズ、圧縮方式など様々な情報が含まれています。それらのフォーマットはファイル形式によって異なり、今回は、pngフォーマットの内容を解説した後にバイナリエディタを使って具体的に中身を見ていきます。

## pngのファイル構造

pngの全体的なファイル構造は次のようになっています。

|pngの構造|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/120473696-44d88d80-c3e2-11eb-960a-f26decc5de1f.PNG)|

先頭に、PNGファイルシグネチャというpngファイルであることを表す8バイト(89 50 4E 47 0D 0A 1A 0A)が必ずあります。
これによって、このファイルがpngファイルかどうかの判断をすることができます。

|シグネチャ(バイナリ)|意味|
|:-:|:-:|
|0x89|0x89は2進数では10001001で、8ビットデータをサポートしない転送システムを検知するのに有効(0x09と認識される)。また、ASCIIコードは0x00から0x7Fの範囲を使用するので、ソフトウェアによってはテキストデータとして誤認されることを防ぐことができる。|
|0x50|ASCIIコードで Pを表す。誤ってテキストエディタで開いてしまった際、`PNG`の文字列を表示することにより、PNGデータだと分かりやすくするため。ただし、使用している文字コードによっては正しく表示されない場合もある。|
|0x4E|ASCIIコードで Nを表す。以下、上に同じ。|
|0x47|ASCIIコードで Gを表す。以下、上に同じ。|
|0x0D|ファイルシステムによって、デコード・エンコード時に改行コードとして、\r\n(0x0D,0x0A)を\n(0x0A)へ変換が行われることがあり、それを検出するため。|
|0x0A|上に同じ。|
|0x1A|テキストファイルとして表示させた場合、ファイルの終端(EOF)として認識され、以降の読み込みを停止させるため。|
|0x0A|ファイルシステムによって、デコード・エンコード時に改行コードとして、\n(0x0A)を\r\n(0x0D,0x0A)へ変換が行われることがあり、それを検出するため。|


また、シグネチャの後ろにはチャンクと呼ばれるデータブロックがいくつも並んでいます。チャンクには様々な種類があり、画像の大きさや圧縮形式、実際の画像データなどがそれぞれ別々のチャンクに分かれて保存されています。また、ファイルの最後にはIENDチャンクが必ずあり、ファイルの終端を示しています。

チャンクには様々な種類があると言いましたが、その構造は常に同じで、必ず下記のような構造になっています。

|チャンクの構造|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/120473692-43a76080-c3e2-11eb-80b1-4cb938fe8025.PNG)|


|名称|内容|
|:-:|:-:|
|データ長(Length)|イメージデータのバイト数|
|チャンク名(Chunk Type)|機能に応じた4文字のASCIIコード(大文字と小文字が区別される)|
|本体データ(Chunk Data)|本体のデータ|
|チェックディジット(CRC)|チャンク名と画像本体のデータが正常なことを確認するためのデータ(巡回冗長検査とも呼ばれる)|

###### チャンク名(Chunk Type)の命名規則
チャンク名(Chunk Type)には、名前の付け方に一定の決まり(命名規則)があり、そのルールに従って命名する必要があります。実際には、各バイト文字の5ビット目のON/OFF(uppercase=0,lowercase=1)を基準に判断していますが、ここでは分かりやすくするために大文字・小文字という表記にしています。

|場所|命名規則|
|:-:|:-:|
|1バイト目|大文字：ファイルの内容を正常に表示するために必要なチャンク(必須チャンク)に分類される。<br>小文字：必須ではない補助チャンクでに分類され、デコーダはこのチャンクを無視することができる。|
|2バイト目|大文字：仕様が公開、定義されたもので、パブリックチャンク(公開チャンク)に分類される。<br>小文字：非公式または私的なチャンクで、プライベートチャンクに分類される。アプリケーション独自の仕様が多く、署名や電子透かしなどの情報が入っていることもある。|
|3バイト目|将来的な拡張のためにリザーブされているビットで、現在はすべて大文字で統一する必要がある。|
|4バイト目|大文字：他の必須チャンクへの変更の影響を受けるチャンクのため、そのままコピーすると問題が起きる。<br>小文字：ファイルへの変更内容に関わらず、チャンクをコピーして継続的に使用できる。|


### 必須チャンク

pngには様々なチャンクが格納されていますが、その中でも画像の表示に必ず必要となるチャンクが4つあります。
IHDRチャンク、PLTEチャンク、IDATチャンク、IENDチャンクです。
これらはどのpngファイルにも必ずあるチャンクで、それぞれ重要な役割を持っているので、1つずつ見ていきます。

#### IHDRチャンク
IHDRチャンクはPNGファイルシグネチャの直後に来るチャンクで、画像の大きさ・色深度・カラータイプ・圧縮方式などの画像における基本的な情報が記録されています。
実際には、ファイルヘッダの後には必ずIHDRチャンクが来て、そのサイズは13バイトで固定のため、シグネチャ8バイトの次の8バイトも00 00 00 0D 49 48 44 52(データ長＋チャンク名)で固定されています。


##### IHDRの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ(ここでは常に13)|
|チャンク名|4バイト|16進数で49 48 44 52(IHDR)|
|幅|4バイト|画像の横ピクセル数|
|高さ|4バイト|画像の縦ピクセル数|
|色深度|1バイト|ピクセル当たりの表現できる色数(8の倍数または約数)|
|カラータイプ|1バイト|カラー/グレースケール、アルファチャネルの有無|
|圧縮方式|1バイト|画像の圧縮方式で現在は0のみ(deflate圧縮)|
|フィルタ方式|1バイト|圧縮前の事前処理方式(0~4まで)|
|インタレース|1バイト|画像表示時の走査方式(0または1)|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算|

###### カラータイプ

カラータイプは、以下の条件を考慮し、各値を加算した結果が最終的な値として記録されます。
1. パレットを使用：+1
2. カラーを使用：+2
3. アルファチャネルを使用：+4

許可されているカラータイプと色深度の組み合わせは以下の通りです。また、下記以外の組み合わせはエラーとなります。

|カラータイプ|ビット深度|画像の種類|
|:-:|:-:|:-:|
|0|1,2,4,8,16|グレースケール画像|
|2|8,16|RGBカラー画像|
|3|1,2,4,8|インデックスカラー画像(PLTEチャンクが必須)|
|4|8,16|グレースケール画像＋アルファチャネル|
|6|8,16|RGBカラー画像＋アルファチャネル|

ちなみに、これらの制限は実装の簡素化とうまく圧縮されない組み合わせを除外するために定められています。
また、よく耳にするPNGの形式として`PNG-8`、`PNG-24`、`PNG-32`がありますが、それぞれ以下の意味で使われています。

|形式|意味|
|:-:|:-:|
|PNG-8|8ビット深度の画像(パレットなしならグレースケール、パレット使えばカラーにもできる)|
|PNG-24|24ビット深度の画像(RGBカラーにできる)|
|PNG-32|24ビット深度＋アルファチャネルの画像|

###### 圧縮方式

pngにおける圧縮方式は、現時点ではdeflate圧縮と呼ばれるzipファイル形式の圧縮にも使われる、特許のない技術が使用されています。これは、同じ並びの文字列が複数回出現した場合、2回目以降の文字列を短い符号に置き換えるというアルゴリズムを応用したもので、実際にはこれに加えてハフマン符号化を組み合わせて使用しています。

###### フィルタ方式

圧縮効率を高めるための事前処理で、エンコーダはdeflate圧縮の前段階として、デコーダは展開後データの復元時にフィルタ方式に従って処理を行います。フィルタはピクセル単位ではなく、バイト単位で適用され、以下の5種類のアルゴリズムから選ぶことができます。

|フィルタ番号|アルゴリズム|
|:-:|:-:|
|0|Noneフィルタ：前処理は行われない|
|1|Subフィルタ：左隣との差分を取る|
|2|Upフィルタ：真上との差分を取る|
|3|Averageフィルタ：左隣と真上の成分を足して2で割った値との差分を取る|
|4|Paethフィルタ：左隣、真上、左上の中で最も値が近いものとの差分を取る|


###### インターレース

画像伝送方法には大きく分けて2種類あり、画像を特定の方向から順番に処理する方式と画像全体を飛び飛びに処理する方法があります。前者はプログレッシブ方式、後者はインターレース方式といいます。
プログレッシブ方式では、通常画像の左上から右方向に走査・処理していきます。つまり、表示は左上から右下にかけて行われます。
一方で、インターレース方式では表示の早い段階から画像の大雑把な全体像の把握を可能にするためにAdam7というアルゴリズムを使って、画像全体をまんべんなく走査・処理していきます。

###### Adam7アルゴリズム
Adam7は画像を8ピクセル四方のブロックに分割し、各ブロックから7段階に指定されたピクセルを抽出していくアルゴリズムです。基本的には画像全体に対して、この処理を繰り返していくことでインターレース方式を実現しています。
以下の表はブロック内(8x8)の7段階に分けられたピクセルを表しています。振られた番号が小さいピクセルほど先に処理・表示されます。

|行/列|1|2|3|4|5|6|7|8|
|:-|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|1|1|6|4|6|2|6|4|6|
|2|7|7|7|7|7|7|7|7|
|3|5|6|5|6|5|6|5|6|
|4|7|7|7|7|7|7|7|7|
|5|3|6|4|6|3|6|4|6|
|6|7|7|7|7|7|7|7|7|
|7|5|6|5|6|5|6|5|6|
|8|7|7|7|7|7|7|7|7|


#### PLTEチャンク
PLTEチャンクはIDATチャンクより前に来るチャンクで、画像で使用されるカラーパレットが定義されています。カラーパレットは色情報ごとに通し番号が設定されていて、画像本体のデータから通し番号を参照することでピクセルごとの色を決めることができます。
ただし、フルカラーではインデックスカラーへの変更(減色)時に使用すべきバレットを指定するため、減色しない場合にはデコード時に無視されます。また、グレースケールではこのチャンクは存在できません。

##### PLTEの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ(ここでは常に3で割り切れる数)|
|チャンク名|4バイト|16進数で50 4C 54 45(PLTE)|
|パレット1(赤)|1バイト|赤(0~255)|
|パレット1(緑)|1バイト|緑(0~255)|
|パレット1(青)|1バイト|青(0~255)|
|パレット2(赤)|1バイト|赤(0~255)|
|パレット2(緑)|1バイト|緑(0~255)|
|パレット2(青)|1バイト|青(0~255)|
|・|・|・|
|・|・|・|
|・|・|・|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算|


#### IDATチャンク
IDATチャンクはIHDRとIENDの間にあり、画像そのもののデータが入っているチャンクです。画像ソフトや圧縮の都合で、複数のチャンクに分割されていることもありますが、それぞれのIDATチャンク自体は連続している必要があります。

##### IDATの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ(画像の大きさに比例)|
|チャンク名|4バイト|16進数で49 44 41 54(IDAT)|
|画像データ|nバイト|実際のイメージデータ|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算|


#### IENDチャンク
IENDチャンクはファイルの終端にある、データの終わりを示すチャンクです。Chunk Dataは常に0となります。

##### IENDの構造

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ(常に0)|
|チャンク名|4バイト|16進数で49 45 4E 44(IEND)|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算(ここでは常にAE 42 60 82)|


### その他補助チャンク
pngは上記で説明した4つのチャンク以外にも、必須ではありませんが、透明度の情報やテキスト情報などを記録できる補助チャンクが多くあります。今回はその中でもよく使われる透明度を指定できるチャンクを紹介します。

#### tRNSチャンク(透明度)
tRNSチャンクはPLTEとIDATの間にあり、透明度に関する情報を格納しています。
このチャンクでは、画像内で使用している色に透明度を指定することができます。
インデックスカラーを使用している場合は、各パレットに対して透明度を指定することができるため、非常に効率的に透明度を付加することができます。また、透明度が指定されていない色に関しては不透明な色として扱われる決まりになっています。

##### tRNSの構造

・インデックスカラーの場合

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ|
|チャンク名|4バイト|16進数で74 52 4e 53(tRNS)|
|パレット1|1バイト|アルファ値(0~255)|
|パレット2|1バイト|アルファ値(0~255)|
|パレット3|1バイト|アルファ値(0~255)|
|・|・|・|
|・|・|・|
|・|・|・|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算|

・グレースケールの場合

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ|
|チャンク名|4バイト|16進数で74 52 4e 53(tRNS)|
|グレーレベル|2バイト|グレー値(0~255)|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算|
ここでは指定されたグレー値1つのみを透明(0)として、それ以外を不透明(255)とします。

・RGBカラーの場合

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ|
|チャンク名|4バイト|16進数で74 52 4e 53(tRNS)|
|RGBカラー|2バイト|赤(R)(0~255)|
|RGBカラー|2バイト|緑(G)(0~255)|
|RGBカラー|2バイト|青(B)(0~255)|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算|
ここでは指定されたRGB値1つのみを透明(0)として、それ以外を不透明(255)とします。


ここまででひと通りpngの構造を説明しました。次は実際にpng画像の中を確認し、上記説明通りの構造になっているかを確認します。


# バイナリエディタからの画像編集
ここではバイナリエディタを使って、実際にpng画像の中身を確認していきます。
今回は、GIMPを使用して16×16の黒で塗りつぶした画像を使用します。

|1.1 サンプル画像|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113553784-3ca6e080-9633-11eb-9ffb-78ec235a86a5.png)|

上記の画像をバイナリエディタを使って見てみると、以下のようになります。



```makefile:1.1サンプル画像
  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52    .PNG........IHDR
00000010: 00 00 00 10 00 00 00 10 08 00 00 00 00 3A 98 A0    .............:..
00000020: BD 00 00 00 0D 49 44 41 54 18 D3 63 60 18 05 C8    =....IDAT.Sc`..H
00000030: 00 00 01 10 00 01 EC 97 E3 7A 00 00 00 00 49 45    ......l.cz....IE
00000040: 4E 44 AE 42 60 82                                  ND.B`.

```

## png構造の確認

### ファイルシグネチャ

|行数|データ|内容|
|:-:|:-:|:-:|
|00000000|89 50 4E 47 0D 0A 1A 0A|シグネチャ|

先頭行には「`89 50 4E 47 0D 0A 1A 0A`」が最初に来ています。このシグネチャによってこのファイルがpngであることが識別できます。
また、以降のデータはチャンクの集まりを見ていけばよいことになります。

### IHDRチャンク
#### データ長＋チャンク名

|行数|データ|内容|
|:-:|:-:|:-:|
|00000000|00 00 00 0D 49 48 44 52|データ長＋チャンク名|

IHDRチャンクはPNGファイルシグネチャの直後に来るチャンクで、画像における基本的な情報が記録されています。以降のデータを見ていくことでこの画像について詳しく知ることができます。

#### 画像の幅・高さ

|行数|データ|内容|
|:-:|:-:|:-:|
|00000010|00 00 00 10|画像の幅|
|00000010|00 00 00 10|画像の高さ|

続いて、画像の幅・高さの情報が4バイトごとにあり、ここでは16進数で10つまり、10進数で16ということになります。

#### 色深度

|行数|データ|内容|
|:-:|:-:|:-:|
|00000010|08|色深度|

色深度はピクセル当たりの表現できる色数で、必ず8の倍数または約数になります。
ここでは、08で8bit=256色を表せることがわかります。

#### カラータイプ

|行数|データ|内容|
|:-:|:-:|:-:|
|00000010|00|カラータイプ|

ここでは、カラータイプは0なのでこの画像が8bitのグレースケール画像であることがわかりました。

#### 圧縮方式・フィルタ方式・インタレース・CRC

|行数|データ|内容|
|:-:|:-:|:-:|
|00000010|00|圧縮方式|
|00000010|00|フィルタ方式|
|00000010|00|インタレース|
|00000010~00000020|3A 98 A0 BD|CRC|

最後に圧縮方式・フィルタ方式・インタレース・CRCを見ていきます。
圧縮方式は0でdeflate圧縮、フィルタ方式は0で前処理は行われません。
インタレースは0なのでなし、CRCはChunk TypeとChunk Dataをもとに計算されていますが、ここでは検算はしません。

### IDATチャンク

|行数|データ|内容|
|:-:|:-:|:-:|
|00000020|00 00 00 0D|データ長|
|00000020|49 44 41 54|チャンク名|
|00000020~00000030|18 D3 63 60 18 05 C8 00 00 01 10 00 01|画像データ|
|00000030|EC 97 E3 7A|CRC|

IDATチャンクはIHDRとIENDの間にあり、圧縮されているためそのままの状態ではデータの意味は理解できませんが、画像そのもののデータが入っているチャンクです。
データ長が「`0D`」で13byteです。実際にも画像データが13byteあることがわかります。

### IENDチャンク

|行数|データ|内容|
|:-:|:-:|:-:|
|00000030|00 00 00 0D|データ長|
|00000030~00000040|49 45 4E 44|チャンク名(IEND)|
|00000040|AE 42 60 82|CRC|

IENDチャンクはファイルの終端にある、データの終わりを示すチャンクです。Chunk Dataは常に0となるため、CRCも常に決まった値になり、結果としてIENDチャンクは必ず「`00 00 00 00 49 45 4E 44 AE 42 60 82`」というデータの並びとなります。


## png構造の編集
pngではイメージデータが圧縮されているため、今回は画像に関係ないチャンクを追加作成して、画像ビューアからその情報を読み取れるようにします。
具体的にはtEXtチャンクというコメントを記録できるチャンクを追加してみたいと思います。

```makefile:1.2サンプル画像
  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52    .PNG........IHDR
00000010: 00 00 00 10 00 00 00 10 08 00 00 00 00 3A 98 A0    .............:..
00000020: BD 00 00 00 27 74 45 58 74 43 6F 6D 6D 65 6E 74    =...'tEXtComment
00000030: 00 A9 20 32 30 32 31 20 53 43 49 45 4E 43 45 50    .).2021.SCIENCEP
00000040: 41 52 4B 20 43 4F 52 50 4F 52 41 54 49 4F 4E 2E    ARK.CORPORATION.
00000050: 8E 59 B2 07 00 00 00 0D 49 44 41 54 18 D3 63 60    .Y2.....IDAT.Sc`
00000060: 18 05 C8 00 00 01 10 00 01 EC 97 E3 7A 00 00 00    ..H......l.cz...
00000070: 00 49 45 4E 44 AE 42 60 82                         .IEND.B`.

```

|1.2 サンプル画像|
|:-:|
|![](https://user-images.githubusercontent.com/74283288/113553784-3ca6e080-9633-11eb-9ffb-78ec235a86a5.png)|

tEXtチャンクを追加した結果が上の状態です。ASCII文字でも「...'tEXtComment.).2021.SCIENCEPARK.CORPORATION..Y2.」という文字列で大まかにわかると思いますが、「© 2021 SCIENCEPARK CORPORATION.」というコメントを追加しました。
tEXtチャンクの構造は以下の通りです。

|名称|サイズ|内容|
|:-:|:-:|:-:|
|データ長|4バイト|Chunk Dataのデータサイズ(画像の大きさに比例)|
|チャンク名|4バイト|16進数で49 44 41 54(IDAT)|
|キーワード|1~79バイト|タイトル(Title)や作者の名前(Author)等|
|0データ|1バイト|常に0で、キーワードとテキスト文字列の間を示す|
|テキスト文字列|nバイト|テキストデータ|
|CRC|4バイト|Chunk TypeとChunk Dataをもとに計算|

ちなみに、CRCの計算については、[このサイト](https://crccalc.com/)を使わせていただきました。

結果として、「1.2 サンプル画像」をGIMPや他の画像ビューアで正常に表示でき、コメントも確認することができました。
ちなみに、自分で定義したチャンクもいくつか試しましたが、GIMPでは定義されていない私的なチャンクは出力時に削除されることがわかりました。


# さいごに
今回は、「ヘッダ情報の確認とバイナリエディタからの画像編集(PNG編)」について解説しました。png画像の構造を知ることができたと思います。

それでは引き続きよろしくお願いいたします。


目次は以下の記事からご覧になれます。

https://qiita.com/spc_ehara/items/e425b6dcc0398299c40d#%E7%9B%AE%E6%AC%A1