PIL(pillow)のデータ構造ってどうなっている?

読み込んだデータは、どうなっている?

画像読み込み

from PIL import Image
im = Image.open('sample20x15.bmp')
im

f:id:chiyoh:20190503233653p:plain

print("画像モード = ",im.mode)
print("画像サイズ(x,y) = ",im.size)
print("pixel格納形式 = ",im.getbands())
print("pixel(0,0) = ",im.getpixel((0,0)))
print("pixel(0,0)の形式 = ",type(im.getpixel((0,0))))
print("各チャンネルの形式 = ",type(im.getpixel((0,0))[0]),type(im.getpixel((0,0))[1]),type(im.getpixel((0,0))[2]))
画像モード =  RGB
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('R', 'G', 'B')
pixel(0,0) =  (200, 36, 179)
pixel(0,0)の形式 =  <class 'tuple'>
各チャンネルの形式 =  <class 'int'> <class 'int'> <class 'int'>

なるほど、なるほど。なら書き込みは

im.putpixel((0,0),(255,255,255))
print("[1] pixel(0,0) = ",im.getpixel((0,0)))
im.putpixel((0,0),(256,256,256))
print("[2] pixel(0,0) = ",im.getpixel((0,0)))
im.putpixel((0,0),(1000,-1000,0))
print("[3] pixel(0,0) = ",im.getpixel((0,0)))
[1] pixel(0,0) =  (255, 255, 255)
[2] pixel(0,0) =  (255, 255, 255)
[3] pixel(0,0) =  (255, 0, 0)

データ形式がintとなっていたので意地悪して入力 各チャンネルR,G,Bは0~255までのuint8の範囲でそれ以外はmin,maxにまとまるという感じか

im.putpixel((20,15),(215,215,215))
print("[4] pixel(-1,0) = ",im.getpixel((20,15)))
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-4-b38936316c41> in <module>
----> 1 im.putpixel((20,15),(215,215,215))
      2 print("[4] pixel(-1,0) = ",im.getpixel((20,15)))


h:\Anaconda3\lib\site-packages\PIL\Image.py in putpixel(self, xy, value)
   1681             # RGB or RGBA value for a P image
   1682             value = self.palette.getcolor(value)
-> 1683         return self.im.putpixel(xy, value)
   1684 
   1685     def remap_palette(self, dest_map, source_palette=None):


IndexError: image index out of range

画像サイズが20x15なので(0~19,0~14)が範囲なのは分かる。よって(20,15)はErrorか。rangeエラーなのね

im.putpixel((-1,0),(111,111,111))
print("[5] pixel(-1,0) = ",im.getpixel((-1,0)))
[5] pixel(-1,0) =  (111, 111, 111)

(-1,0)ってどこ?

print("[1] pixel(0,0) = ",im.getpixel((0,0)))
print("[2] pixel(19,0) = ",im.getpixel((19,0)))
print("[3] pixel(0,14) = ",im.getpixel((0,14)))
print("[4] pixel(19,14) = ",im.getpixel((19,14)))
[1] pixel(0,0) =  (255, 0, 0)
[2] pixel(19,0) =  (111, 111, 111)
[3] pixel(0,14) =  (147, 42, 197)
[4] pixel(19,14) =  (49, 53, 230)

なるほど、Pythonの配列みたいにxy[-1][0]がxy[19][0]という感じか

格納形式は?

print(type(im.tobytes()))
print(im.tobytes())
<class 'bytes'>
b"\xff\x00\x00\xc5%\xb5\xc0%\xb6\xbc%\xb7\xb8&\xb9\xb3&\xbb\xad'\xbc\xa9(\xbe\xa4(\xbf\x9e)\xc2\x99)\xc4\x94*\xc5\x8e+\xc7\x89+\xc9\x84,\xcb~,\xccx-\xcfs.\xd0n.\xd2ooo\xc5$\xb4\xc1$\xb6\xbd%\xb7\xb8&\xb9\xb3'\xba\xae'\xbb\xa9(\xbe\xa4(\xbf\x9f(\xc1\x9a)\xc3\x94*\xc5\x90*\xc7\x8a+\xc8\x85+\xca\x7f,\xcdz-\xcet-\xd0n.\xd2i/\xd4c/\xd6\xc2$\xb5\xbe%\xb6\xb9&\xb8\xb5&\xba\xaf'\xbc\xaa(\xbd\xa6(\xbf\xa1(\xc1\x9c)\xc2\x96*\xc4\x90+\xc6\x8b+\xc8\x86+\xca\x80,\xcc{-\xceu-\xd0p.\xd1j.\xd4e/\xd5_0\xd7\xbe%\xb6\xba%\xb8\xb5&\xba\xb1'\xbb\xac'\xbc\xa7'\xbf\xa2)\xc0\x9c)\xc2\x97*\xc4\x91*\xc6\x8c*\xc8\x86+\xca\x81,\xcc|,\xcdv-\xcfq.\xd2k.\xd3f/\xd5a0\xd7\\0\xd8\xbb%\xb8\xb6&\xba\xb2'\xbb\xac'\xbd\xa8'\xbe\xa3(\xc0\x9d(\xc2\x98)\xc3\x92*\xc5\x8e+\xc7\x87+\xc9\x83,\xcb}-\xcdw.\xcfr-\xd0l.\xd3h/\xd5a0\xd6]1\xd8W1\xda\xb7&\xb9\xb3'\xbb\xad&\xbc\xa9'\xbe\xa4(\xc0\x9e(\xc1\x99*\xc3\x93*\xc5\x8f*\xc7\x89+\xc9\x83,\xcb~,\xccy-\xcfs.\xd0n/\xd2h.\xd4b/\xd6^0\xd7X0\xd9S1\xdb\xb4&\xba\xaf'\xbc\xaa'\xbe\xa4(\xbf\xa0)\xc2\x9a)\xc3\x95*\xc5\x8f+\xc6\x8a+\xc9\x85,\xca\x7f,\xccz-\xcet-\xd0o.\xd2j/\xd3d/\xd5_0\xd7Y0\xdaT1\xdbO2\xdd\xb0'\xbb\xab'\xbd\xa5'\xbf\xa0)\xc0\x9c)\xc2\x96)\xc4\x90*\xc6\x8c*\xc8\x85+\xca\x80,\xcc{-\xceu-\xcfp.\xd1k/\xd3e0\xd5_0\xd7[0\xd9U1\xdaP1\xddK2\xde\xab'\xbd\xa6(\xbf\xa2(\xc0\x9d)\xc2\x97)\xc4\x92*\xc6\x8c+\xc8\x87+\xc9\x82,\xcc|,\xcdw-\xd0q.\xd1l/\xd3f/\xd5a0\xd7\\1\xd9W1\xdaQ2\xdcL2\xdeG3\xdf\xa8(\xbe\xa3)\xc0\x9e)\xc2\x99)\xc4\x92*\xc6\x8d+\xc8\x88+\xc9\x82,\xcb},\xcdx-\xcfr-\xd0m.\xd3g/\xd4b/\xd6]0\xd8X1\xd9R1\xdcM2\xddH2\xdfC3\xe1\xa4(\xbf\x9e(\xc1\x99*\xc3\x94*\xc6\x8f+\xc6\x89+\xc9\x84,\xca\x7f,\xcdy-\xces.\xd0n.\xd2h/\xd4c/\xd6^0\xd8X0\xd9S2\xdbN2\xddI2\xdfD3\xe0?3\xe3\x9f(\xc1\x9a)\xc3\x95*\xc5\x90+\xc7\x8b*\xc9\x85+\xca\x7f,\xccz-\xceu.\xd0o.\xd2j.\xd4d/\xd6_0\xd7Z0\xd9T1\xdbO2\xdcJ2\xdfE2\xe0A3\xe2<4\xe3\x9c)\xc2\x96*\xc4\x91*\xc6\x8c+\xc8\x86+\xca\x81,\xcc{-\xceu-\xcfq.\xd2j/\xd3f/\xd5`0\xd7Z1\xd8U1\xdaP2\xdcK2\xdeF3\xe0B3\xe1=4\xe384\xe5\x97)\xc4\x92*\xc6\x8d+\xc7\x87,\xca\x81,\xcb|-\xcdw-\xcfq.\xd1k/\xd3f/\xd5a/\xd7\\0\xd8V1\xdbR1\xdcL2\xdeG2\xdfB3\xe1>4\xe394\xe455\xe5\x93*\xc5\x8e+\xc7\x89,\xc9\x82,\xca},\xcdx-\xcer.\xd0m.\xd2h/\xd4b/\xd6]0\xd8X0\xdaR1\xdcN1\xddI2\xdfC3\xe1?3\xe3;4\xe465\xe615\xe6"

分からんので、cls

im = Image.new('RGB',(20,15),color=(0,0,0))
print(im.tobytes().hex())
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

ピクセルにでーたを書き込む

im.putpixel((0,0),(1,2,3)) # 左上(0,0)
im.putpixel((19,14),(253,254,255)) # 右下(19,14)
im.putpixel((1,0),(4,5,6)) # (0,1) x=1
im.putpixel((0,1),(7,8,9)) # (1,0) y=1
print(im.tobytes().hex())
010203040506000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070809000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fdfeff

なるほどR(0,0),G(0,0),B(0,0),R(1,0),G(1,0),B(1,0),.......R(0,1),G(0,1),B(0,1), ............. R(19,14),G(19,14),B(19,14) の順なのね

print('データのサイズ = ',len(im.tobytes()))
データのサイズ =  900

R,G,B=3 20x15 で3x20x15=900か
ならば

def getBytes2pixel(im,xy):
    x,y = xy
    w,h = im.size
    dat = im.tobytes()
    print(xy," = ",dat[3*(w*y+x)],dat[3*(w*y+x)+1],dat[3*(w*y+x)+2])

getBytes2pixel(im,(0,0))
getBytes2pixel(im,(19,14))
getBytes2pixel(im,(1,0))
getBytes2pixel(im,(0,1))
(0, 0)  =  1 2 3
(19, 14)  =  253 254 255
(1, 0)  =  4 5 6
(0, 1)  =  7 8 9

まとめると

スクリーンXY座標(左上原点)で(0,0)(1,0)(2,0)(3,0)...(19,0)(0,1)(1,1)(2,1).....(19,13)(19,14) の順に並んでいて、アクセスはaddress = 3*(w*y+1)R=address[0], G=address[1], B=address[2] な感じなのね

パレットカラーだと

p_im = im.quantize() # 256色インデックスカラー化
print("画像モード = ",p_im.mode)
print("画像サイズ(x,y) = ",p_im.size)
print("pixel格納形式 = ",p_im.getbands())
print("データサイス = ",len(p_im.tobytes()))
print(p_im.tobytes().hex())
画像モード =  P
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('P',)
データサイス =  300
030204040404040404040404040404040404040401040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040400

P*h*wなので1*20*15=300ピクセルの並びは同じ