Blog

PNGとGIFとJPEGにコメントを埋め込む

By Hiroyuki OYAMA Thu Apr 8 18:56:17 2004

mod_ezweb_downloadモジュールに絡んで、携帯端末むけの著作権保護機能(?)について遊んでました。というわけでPNG,GIF,JPEGにコメントを埋め込む場合についての簡単なメモとテスト実装。

これらの情報は遊びで調べて実装した物なので、著しく正確さを書いている可能性があります。また画像のpixelサイズの参照や操作、といった話題は含まれませんので注意。
それにしてもKDDIの"kddi_copyright=on"で転載(?)を抑制する、というまるで
著作権のあるコンテンツは一切転載不許可
by KDDI
と語っているようなネーミングセンスがスゴいな。あ、いやべつに私は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ファイルには少なくとも
  1. SIGNATURE
  2. IHDRチャンク
  3. IDATチャンク
  4. 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のヘッダは可変長なので、
  1. ヘッダを13byte読み込む
  2. Global Color Tableのサイズを計算する
  3. 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
など、ファイル形式に応じたマーカが用意されています。

このため
  1. SOIセグメントと続くAPPnセグメントのmarkerとlength、計6バイト読み込む
  2. APPnセグメントのサイズだけオフセットした位置にCOMセグメントを埋め込む
といった手続きが必要です。

実装

というわけでこれらのネタをもとにPNG, GIF, JPEGの各ファイルにコメントを埋め込むツールを書いてみました。
imgcmt version 1.0.0をダウンロード
各ファイル形式用に
  • imgcmtpng - PNG用
  • imgcmtgif - GIF用
  • imgcmtjpg - JPEG用
の3つのプログラムを含みます。それぞれ標準入力でうけとった画像のストリームにコメントを埋め込み、標準出力に吐き出します。
$ 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 total
mgcmtpng.cだけCRCのテーブルを定数としてもたせたのでちょっとだけ大きいですね。

そして私が持っているTU-KAのEZweb端末TK22で、PNGに関して期待どおりの動作を確認できましたが、JPEGに関してはどうも私の理解が間違っているのかよく判りませんが期待通りの動作を行いません。またTK22は壁紙でのGIFをサポートしていないらしいので完全に未検証です。
詳しい方のコメント希望っす。あとKDDIのWebサイトに上がっている以上の情報のポインタがあれば是非。是非とか言っても単に個人的趣味で調べてただけなので、本職の方には申し訳ないことこの上ありませぬが(汗)

Comments

Post a comment

Name:


URL:


Comments:


WebエンジニアのためのApacheモジュールプログラミングガイド

ApacheをHackする!
モジュールプログラミング強烈初体験!!
定価: 2,919円(税込)
ISBN: 4-7741-1799-4

hiroyuki_oyama IM status

Apache Users

Apache Modules

CPAN


Home > Blog > PNGとGIFとJPEGにコメントを埋め込む