Python
png
zlib
Endian
Binary

pythonでバイナリデータを読む(PNGを例として)


環境


  • Windows10 Pro

  • Python 3.6.4 :: Anaconda

  • zlib 1.2.11


概要


前準備:読み込む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)


black.png


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) → 黒

データの中身を取り出すことができました。素晴らしい。

必要なのは正確なフォーマット情報と、根気ですね。