mod_ezweb_downloadモジュールに絡んで、携帯端末むけの著作権保護機能(?)について遊んでました。というわけでPNG,GIF,JPEGにコメントを埋め込む場合についての簡単なメモとテスト実装。
これらの情報は遊びで調べて実装した物なので、著しく正確さを書いている可能性があります。また画像のpixelサイズの参照や操作、といった話題は含まれませんので注意。それにしてもKDDIの"kddi_copyright=on"で転載(?)を抑制する、というまるで
著作権のあるコンテンツは一切転載不許可と語っているようなネーミングセンスがスゴいな。あ、いやべつに私はKDDIが嫌いとかそーいうわけじゃないですよ。はい。
by KDDI
PNG
PNGのファイルは、1つのシグネチャと、複数のチャンクが並ぶ構造です。SIGNATURE(8 byte); CHUNK(12 + data byte) x n;
PNGのシグネチャ
シグネチャは「このファイルはPNGである」を表すための値で、ファイルの先頭に次のような値で埋め込まれます。0x89 0x50 0 0x4e 0x47 0x0d 0xa 0x1a 0x0a
PNGのチャンク
チャンクはPNGファイルで保持する情報の種類に応じてIHDRチャンク・IDATチャンクといった複数の種類が存在します。チャンクの構造はデータの種類に関係なく次のとおりです。length(4 byte); type(4 byte); data(0〜n byte); crc(4 byte);lengthは、チャンクのdata部の長さを示す4バイトの値で、ビッグエンディアンで格納します。
typeはチャンクの種類を示す4バイトの値です。typeの値は文字列で表現できます。チャンクの種類についてはPNGのドキュメント - Chunk typesを参照します。
dataはチャンクに含むデータそのものです。長さが0の場合もあります。
crcはtypeとdata部の値を検査するための値です。CRCアルゴリズムについての解説とサンプルコードはPNGのドキュメント - Cyclic Redundancy Code algorithmを参照します。
正しいPNGファイルには少なくとも
- SIGNATURE
- IHDRチャンク
- IDATチャンク
- IENDチャンク
IHDRチャンクは画像のサイズや圧縮方法などといった画像の表現形式に関する情報を保持し、すべてのチャンクの先頭に位置する必要があります。IHDRチャンクのtypeは
0x73(I) 0x72(H) 0x68(D) 0x82(R)で、data部は次のようなデータ構造です。
width(4 byte); height(4 byte); bit depth(1 byte); color type(1 byte); compression method(1 byte); filter method(1 byte); interface method(1 byte);
IDATチャンクは画像のデータを保持します。IDATチャンクのtypeは
0x73(I) 0x68(D) 0x65(A) 0x84(T)です。
IENDチャンクはPNGのデータストリームの終端を示します。IENDチャンクのtypeは
0x73(I) 0x69(E) 0x78(N) 0x68(D)です。
IHDRチャンクは先頭、IENDチャンクは末尾に位置する必要がありますが、これ以外のチャンクの並び順に制約はありません。
PNGのコメント
PNGにテキスト情報を埋め込む場合はtEXtチャンクなどを利用します。tEXtチャンクのtypeは0x74(t) 0x45(E) 0x58(X) 0x74(t)です。data部は
keyword(1〜79 byte); separator(1 byte); text(n byte);の形式で格納されます。keywordはテキスト情報の種別をしめす文字列です。separatorはkeywordと続くtextを分割する文字で'\0'を使用します。textはテキスト情報の本体です。
PNGに著作権保護情報を埋め込む
携帯電話などにダウンロードした画像の転送などを制限する場合は、以下のようなデータのtEXtチャンクを追加します。keyword = "Copyright"; text = "kddi_copyright=on"; // EZwebの場合 // text = "copy=\"NO\""; // iMODEの場合tEXtチャンクはIHDRチャンクの直後など任意の場所に埋め込みます。IHDRチャンクの直後とはファイル先頭から33バイト目(SIGNATURE(8) + IHDR(25))です。
GIF
GIFファイルはシグネチャを含む1つのヘッダと、複数のブロックが並び、最後にTrailer(0x3b)を含む構造です。HEADER(13〜781 byte); BLOCK x n; Trailer(1 byte);
GIFのヘッダ
ヘッダには「このファイルはGIFである」を表すシグネチャと、バージョンと、画像のサイズやカラーテーブルなどの表現形式を含んでいます。signature(3 byte); version(3 byte); width(2 byte); height(2 byte); color table(1 byte); background color index(1 byte); pixel aspect ratio(1 byte); global color table(0〜768 byte);このうちcolor tableの各ビットは
global color table flag(0x80); color resolution(0x70); sort flag(0x08); size of global color table(0x07);を示しています。もしglobal color table flagが立っている場合は、ヘッダの末尾にglobal color tableが付与されます。そのサイズはsize of global color tableの値をもとに次のように計算します。
3 * (2 ^ (size_of_global_color_table + 1)); // C の場合は 3 * (1 << (size_of_global_color_table + 1)) とか? // Perlは 3 * (2**(size_of_global_color_table + 1)) とか?
GIFのブロック
ブロックの先頭1でブロックの種類を判定します。画像データを保持するImageブロックの場合は0x2cその他の拡張ブロックの場合は
0x21です。拡張ブロックの場合は次の1バイトの値によって種類を判定します。透過処理やアニメーション時のdelayなどを定義するGraphic Control Extensionブロックの場合は
0x21 0xf9コメントを保持するComment Extensionブロックの場合は
0x21 0xfeです。
ブロックの種類によってデータ構造が異なります。
GIFのコメント
コメントを保持するComment Extensionブロックはblock size(1 byte) text(n byte) x n block terminator(1 byte)という構造です。
block sizeはtext部のサイズを1バイトで示します。
textはコメントとして埋め込む文字列を保持します。
block terminatorはブロックの終端を示す1バイトの値(0x00)です。
GIFに著作権保護情報を埋め込む
(注:未検証)携帯電話などにダウンロードした画像の転送などを制限する場合は、以下のようなデータのComment Extensionブロックを追加します。
text = "kddi_copyright=on"; // EZwebの場合 // text = "copy=\"NO\""; // iMODEの場合Comment Extensionブロックはヘッダに続く任意の場所に埋め込みます。GIFのヘッダは可変長なので、
- ヘッダを13byte読み込む
- Global Color Tableのサイズを計算する
- Global Color Tableのサイズだけオフセットした位置にComment Extensionブロックを埋め込む
JPEG
JPEGのファイルは複数のセグメントから構成されます。SOI SEGMENT(2 byte) SEGMENT x n EOI SEGMENT(2 byte)先頭に現れるセグメントがSOIセグメントで、次の2バイトの値で構成されます。
0xff 0xd8末尾に現れるセグメントがEOIセグメントで、次の2バイトの値で構成されます。
0xff 0xd9また、SOIセグメントの直後には画像の情報を保持するAPP0やAPP1などのAPPnセグメントが配置されます。
JPEGのセグメント
SOIやEOIを除くセグメントは役割によって構造が異なりますが、ほとんどはmarker(2 byte) length(2 byte) data(n byte)といった構造です。
markerはセグメントの種類を示す2バイトの値です。SOI(0xff 0xd8), EOI(0xff 0xd9)の他にもAPP0(0xff 0xe0), SOF0(0xff 0xc0)などセグメントの種類に応じた値が定義されています。
lengthはセグメントが保持するdataのサイズを示す2バイトの値で、ビッグエンディアンで格納します。lengthで示される長さはdataのサイズ + 2である点に注意が必要です。
JPEGのコメント
JPEGにコメントを埋め込むにはCOMセグメント(0xff 0xfe)を使用します。COMセグメントはmarker(2 byte) length(2 byte) text(n byte)という構造です。
JPEGに著作権保護情報を埋め込む
(注:未検証。COMセグメントではなくAPP1(Exif)のCopyright(0x82 0x98)タグに埋め込むべき?)携帯電話などにダウンロードした画像の転送などを制限する場合は、以下のようなデータのCOMセグメントを追加します。
text = "kddi_copyright=on"; // EZwebの場合 // text = "copy=\"NO\""; // iMODEの場合COMセグメントはSOIセグメントに続く、APP0やAPP1といった"APPn"セグメントの後ろの任意の場所に埋め込みます。APPnセグメントのmarkerは
0xff 0xe0; // APP0 JFIF application segment 0xff 0xe1; // APP1 Exif ... 0xff 0xef; // APP15など、ファイル形式に応じたマーカが用意されています。
このため
- SOIセグメントと続くAPPnセグメントのmarkerとlength、計6バイト読み込む
- APPnセグメントのサイズだけオフセットした位置にCOMセグメントを埋め込む
実装
というわけでこれらのネタをもとにPNG, GIF, JPEGの各ファイルにコメントを埋め込むツールを書いてみました。imgcmt version 1.0.0をダウンロード各ファイル形式用に
- imgcmtpng - PNG用
- imgcmtgif - GIF用
- imgcmtjpg - JPEG用
$ imgcmtpng Copyright kddi_copyright=on < src.png > dist.png $ imgcmtgif kddi_copyright=on < src.gif > dist.gif $ imgcmtjpg kddi_copyright=on < src.jpg > dist.jpgといった使い方。エラー時にはストリームを切る実装ですが、この場合は極力スルーするつくりの方がよいかもしれませんね。
imgcmtはlibpngやlibjpegなどのフル装備の実装ではなく、コメント追加のみのシンプルで小さな実装です。
$ wc -l *.c 114 imgcmtgif.c 117 imgcmtjpg.c 166 imgcmtpng.c 397 totalmgcmtpng.cだけCRCのテーブルを定数としてもたせたのでちょっとだけ大きいですね。
そして私が持っているTU-KAのEZweb端末TK22で、PNGに関して期待どおりの動作を確認できましたが、JPEGに関してはどうも私の理解が間違っているのかよく判りませんが期待通りの動作を行いません。またTK22は壁紙でのGIFをサポートしていないらしいので完全に未検証です。
詳しい方のコメント希望っす。あとKDDIのWebサイトに上がっている以上の情報のポインタがあれば是非。是非とか言っても単に個人的趣味で調べてただけなので、本職の方には申し訳ないことこの上ありませぬが(汗)