UWSCでPNGファイルを解析する

Filrという、Flickrを任意のファイルストレージにしてしまうソフトがあるようです。
PNGに任意のファイルをつっこんで、そのPNGファイルを入れてしまうという仕掛け。


興味が湧いたので、PNGファイルを覗いてみた。



PNGフォーマット

8byteのシグネチャの後、「サイズ(4byte)、タイプ(4byte)、データ(サイズbyte)、CRC(4byte)」のチャンクが続く。


チャンクは、IHDRが最初で、IENDが最後。
あとは自由。
IDATが必須チャンクで、zlibにより圧縮されたイメージデータ。
カラーパレットを使う場合は、PLTEも必須。


基本的にビッグエンディアンの模様。(サイズ/CRCはそう。他チャンクのデータは知らん)
タイプの4文字は、大文字・小文字に意味がある。
CRCは、タイプとデータのもの。

CRC算出スクリプト

CRCの算出式がc言語で書いてあったので、UWSCスクリプトにしてみた。
crc.uws

OPTION EXPLICIT

IFB GET_UWSC_NAME = "crc.uws" THEN
	DEF_DLL RtlMoveMemory(var BYTE[], string, DWORD): kernel32
	DIM str = "tEXtAuthor" + CHR(0) + "junjune", len = LENGTH(str), buf[len-1]
	RtlMoveMemory(buf, str, len)
	PRINT FORMAT(len-4, 8, -1) + " " + FORMAT(CRC.calc(buf), 8, -1)
ENDIF



MODULE CRC
	// http://www.w3.org/TR/PNG/#D-CRCAppendix

	DIM crc_table[255]

	PROCEDURE CRC
		DIM c, n, k
		FOR n = 0 TO LENGTH(crc_table) - 1
			c = n
			FOR k = 0 TO 7
				IFB c AND 1 THEN
					c = $edb88320 xor INT(c / 2)
				ELSE
					c = c / 2
				ENDIF
			NEXT
			crc_table[n] = c
		NEXT
	FEND

	FUNCTION update(crc, buf[], len)
		DIM c = crc, n
		FOR n = 0 TO len - 1
			c = crc_table[(c xor buf[n]) and $ff] xor INT(c / 256)
		NEXT
		RESULT = c
	FEND

	FUNCTION calc(buf[], len=-1)
		IF len = -1 THEN len = LENGTH(buf)
		RESULT = update($ffffffff, buf, len) xor $ffffffff
	FEND

ENDMODULE

CRC.calcにバイト配列を渡すだけ。

PNGファイルチェックスクリプト

シグネチャが正しいことを検証して、どんなチャンクがあるか列挙。
ついでにCRCが正しいか確認します。

OPTION EXPLICIT

CALL Binary
CALL crc

DIM h = Binary.Open(INPUT("target PNG file?"))
IF h = Binary.INVALID_HANDLE OR !Binary.Exist(h) THEN EXITEXIT

// PNGシグネチャ確認
DIM buf = Binary.ReadArray(h, 8)
IFB buf[0]<>$89 OR buf[1]<>$50 OR buf[2]<>$4E OR buf[3]<>$47 OR buf[4]<>$0D OR buf[5]<>$0A OR buf[6]<>$1A OR buf[7]<>$0A THEN
	PRINT "Not PNG Format"
	EXITEXIT
ENDIF

DIM t, size, crc
WHILE !Binary.EOS(h)
	buf = Binary.ReadArray(h, 4)
	size = ((buf[0] * 256 + buf[1]) * 256 + buf[2]) * 256 + buf[3]
	buf = Binary.ReadArray(h, 4 + size)
	t = CHR(buf[0]) + CHR(buf[1]) + CHR(buf[2]) + CHR(buf[3])
	crc = CRC.calc(buf, 4 + size)
	buf = Binary.ReadArray(h, 4)
	IFB ((buf[0] * 256 + buf[1]) * 256 + buf[2]) * 256 + buf[3] = crc THEN
		crc = ""
	ELSE
		crc = " CRC Error!"
	ENDIF
	PRINT t + "(" + size + ")" + crc
WEND

Binary.Close(h)

Binaryモジュールは、以下から。
UWSCでバイナリーファイルの読み書き - じゅんじゅんのきまぐれ

Filrの仕組み

tEXtチャンクを使う、とか、大きくなる、とか、圧縮している、とか、GIGAZINEに書いてあった。
それが正しいなら、ファイルを圧縮して、Base64エンコードして、tEXtチャンクを適当なところに、適当なキーワードで追加しているのでしょう。
でも、圧縮するなら、zTXtチャンクを使っても良い気がする、、、。
この場合、ファイルをBase64エンコードして、inflateして、zTXtチャンクを適当なところに、適当なキーワードで追加となる。
圧縮方式がdeflateより良いのを使っているとか?


Flickr、無料で1TB使えますからねぇ。
魅力的なアイデアではありますね。

PNGのtEXtチャンクについて

日本語が使えない、という怨嗟の声があるようです。
RFC2047(いわゆるMIMEエンコード)ということにしてしまえば良い気がするけど、、、対応するビューワがないか。


ISO 8859-1が使える文字だから、、、お、Base64じゃなくて、Base128ができますね。(そんなのないけど)
独自実装になりますが、増加量を少し抑えられます。
Base64の4/3倍(133.33%)から、8/7倍(114.29%)に。


0x0?,0x1?,0x8?,0x9?,0x7F以外を使えば良いのだから、例えば
0x3?〜0x6?,0xC?〜0xF?を0x00〜0x7fにマッピングして、8/7倍すればOKということかと。
埋める記号は「~」あたりが良いかな?
もしくは、CP932使用者に配慮して、0x2?〜0x6?,0xB?〜0xD?あたりにするか。(後半が半角カナ領域)
これなら、日本人と欧米人にやさしい感じです。(他は知らん)
スペースや記号がふんだんに入ってしまうことを気にするなら、少しずらすのもありですが、、、ま、いいでしょ。
どうせ読めないのだから。