環境
- Windows10 Pro
- Python 3.6.4 :: Anaconda
- zlib 1.2.11
概要
- pythonでバイナリの読み書きをするには、structモジュールが便利
- https://docs.python.org/ja/3/library/struct.html#struct.unpack_from
- pngの中身を読んでみる
- http://web.archive.org/web/20050407143942/http://tech.millto.net/~pngnews/kndh/PngSpec1.2/PNGcontents.html
前準備:読み込むpngを作る
1ピクセルの黒いpngをさくっと作ります
makePng.py
from PIL import Image, ImageDraw
screen = (1,1)
bgcolor=(0,0,0)
filename = "black.png"
img = Image.new('RGB', screen, bgcolor)
img.save(filename)
pngをバイナリデータとして読み込む
エンディアン
pngはネットワークバイトオーダ(=ビッグエンディアン)でデータが格納されます。
以下に記載されているように、フォーマット文字列の最初に">"を入れることによってビッグエンディアンを指定します。
ちなみに、自分のマシンのエンディアン(=native)を知りたければ以下のようにしましょう。
import sys
print(sys.byteorder)
#little or big
zlibによるデータ解凍
さて、念願のデータを手に入れましたが、deflate圧縮がかかっています。
idata = list(struct.unpack_from(">" + str(length[0]) + "B", data, offset + 8))
print(idata)
#[120, 156, 99, 96, 96, 96, 0, 0, 0, 4, 0, 1]
みんな大好きzlibで解凍しましょう。
バイト列に変換してからdecompressすればOKです。
import zlib
idata = zlib.decompress(bytearray(idata))
print(idata)
#b'\x00\x00\x00\x00'
コード全文
offsetをずらしながら、必要なデータをどんどん読み込んでいきます。
binary.py
import struct
import os
import zlib
f = open("black.png", "rb")
data = f.read()
#unpack_fromはtupleを返す,たとえ要素が1つでも
#pngシグネチャ 8byteで(0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A)
signature = struct.unpack_from(">8B", data, 0)
offset = 8
#image header(25 bytes)
length = struct.unpack_from(">I", data, offset)
ctype = struct.unpack_from(">4s", data, offset + 4)
width = struct.unpack_from(">I", data, offset + 8)
height = struct.unpack_from(">I", data, offset + 12)
bitDepth = struct.unpack_from(">B", data, offset +16)
colorType = struct.unpack_from(">B", data, offset + 17)
compless = struct.unpack_from(">B", data, offset + 18)
filter = struct.unpack_from(">B", data, offset + 19)
interlace = struct.unpack_from(">B", data, offset + 20)
crc = struct.unpack_from(">I", data, offset + 21)
print(length, ctype, width, height, bitDepth, colorType, compless, filter, interlace)
offset += 25
#image data
length = struct.unpack_from(">I", data, offset)
ctype = struct.unpack_from(">4s", data, offset + 4)
print(length, ctype)
idata = list(struct.unpack_from(">" + str(length[0]) + "B", data, offset + 8))
offset += length[0] + 12
#Image trailer(12 bytes)
length = struct.unpack_from(">I", data, offset)
ctype = struct.unpack_from(">4s", data, offset + 4)
print(length, ctype)
f.close()
#zlibによる解凍
idata = zlib.decompress(bytearray(idata))
print(idata)
出力
(13,) (b'IHDR',) (1,) (1,) (8,) (2,) (0,) (0,) (0,)
(12,) (b'IDAT',)
(0,) (b'IEND',)
b'\x00\x00\x00\x00'
最後の行がpngのデータ列です。
(フィルター,R,G,B)の順にデータが入っています。
フィルター:0 → フィルタなし
R,G,B:(0,0,0) → 黒
データの中身を取り出すことができました。素晴らしい。
必要なのは正確なフォーマット情報と、根気ですね。