Google App Engineで画像処理するためにpypngを使ってみた
Google App Engineで画像処理をしようと思ったのですが、公式で提供されているAPIは機能が少なすぎます。
http://code.google.com/intl/ja/appengine/docs/python/images/
Pythonの画像処理で有名なライブラリにPIL(Python Imaging Library)というのがあるらしいのですがGoogle App Engineでは使えないそうです。
http://www.pythonware.com/products/pil/
そこでPure Pythonのライブラリであるpypngを使うことにしました。
GIFが対象の場合はpygifで頑張ってる方がいらっしゃったのでリンクを張っておきます。
- http://d.hatena.ne.jp/miz999/20110218/1298056217
- http://d.hatena.ne.jp/miz999/20110226/1298720099
- http://d.hatena.ne.jp/miz999/20110304/1299254436
やりたいこと
PNGの左上(x,y)=(0,0)のドットと同じ色を透過色にする
PHPでGDを使えばこれだけで済むんだけど…
https://gist.github.com/824781
今までは外部サーバにこれを設置して処理を丸投げしていたのですが、諸事情で使えなくなりそうなので、頑張ってpypngでやります。
pypng
Google Code
http://code.google.com/p/pypng/
Document
http://packages.python.org/pypng/
説明がシンプルすぎます。
詳細な使い方を知るにはソース読むしかないです…。
とりあえず読み込む
read()でもいいのですがasRGB8()っていうメソッドを見つけたのでこれを使います。
8bitPNGとして読み込んで統一しておいたほうが書き込む時にRGBAの範囲を0-255に決め打ちできて後々楽だと考えました。
GAEのことは忘れてファイルの読み書きをしますよ。
import png pr = png.Reader(file=open('before.png', 'rb')) x,y,pixels,meta = pr.asRGB8() print x,y,pixels,meta
16 16{'bitdepth': 8, 'colormap': False, 'interlace': 0, 'planes': 3, 'greyscale': False, 'alpha': False, 'size': (16, 16)}
x,y,metaの型はわかった。generator objectってなんぞ?(・ω・`)
http://www.panopticon.jp/blog/2007/02/152332.html
なるほど。for文で回せるオブジェクトを関数みたいに定義して作成したもの、という理解であってるかな?
ピクセル全部を配列に入れて保持してるとメモリが大変でしょ、っていう配慮だろうか?
"イテレータ"っていうのを知るともっと賢くなれそうなので後で調べてみる。
http://jutememo.blogspot.com/2008/06/python.html
とりあえずforで回してみた
for p in pixels: print p
[125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55] [125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55] [125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 56, 125, 82, 58, 125, 82, 58, 125, 82, 57, 125, 81, 55, 125, 81, 55] [125, 81, 55, 125, 81, 55, 125, 81, 56, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 82, 56, 125, 80, 52, 125, 73, 41, 125, 74, 42, 125, 76, 45, 125, 82, 57, 125, 81, 55] [125, 81, 55, 125, 82, 57, 125, 79, 51, 125, 80, 53, 125, 82, 57, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 82, 56, 125, 79, 50, 125, 86, 65, 127, 126, 142, 128, 134, 153, 126, 107, 105, 125, 77, 46, 125, 82, 56] [125, 81, 56, 125, 76, 46, 125, 88, 69, 125, 85, 63, 125, 77, 47, 125, 82, 56, 125, 81, 55, 125, 81, 55, 125, 81, 54, 125, 82, 59, 129, 154, 195, 127, 124, 138, 126, 101, 90, 129, 155, 197, 127, 117, 124, 125, 74, 43] [125, 76, 46, 127, 119, 128, 128, 142, 173, 129, 144, 177, 126, 106, 104, 125, 77, 46, 125, 82, 57, 125, 82, 58, 125, 74, 41, 127, 118, 125, 129, 149, 186, 124, 68, 31, 125, 71, 36, 126, 99, 90, 129, 160, 206, 125, 78, 49] [126, 90, 73, 129, 158, 202, 125, 80, 53, 126, 98, 87, 129, 155, 197, 125, 79, 51, 125, 81, 55, 125, 83, 58, 125, 72, 38, 128, 130, 150, 128, 136, 160, 125, 74, 42, 125, 83, 59, 125, 90, 72, 129, 161, 207, 125, 84, 62] [126, 98, 87, 129, 153, 194, 125, 69, 32, 125, 84, 62, 129, 157, 199, 125, 83, 59, 125, 80, 54, 125, 83, 58, 125, 74, 42, 127, 115, 119, 129, 152, 191, 125, 69, 32, 124, 69, 32, 126, 103, 98, 129, 158, 204, 125, 77, 47] [125, 80, 52, 128, 146, 180, 127, 120, 132, 128, 134, 156, 128, 133, 155, 125, 75, 43, 125, 81, 55, 125, 80, 53, 125, 80, 53, 125, 80, 54, 129, 150, 187, 128, 131, 152, 127, 108, 106, 129, 158, 202, 127, 111, 113, 125, 75, 44] [125, 79, 51, 125, 84, 62, 127, 119, 129, 127, 114, 118, 125, 76, 46, 126, 102, 97, 128, 139, 162, 128, 137, 160, 127, 127, 142, 125, 80, 53, 125, 82, 58, 127, 120, 131, 128, 128, 145, 126, 99, 91, 125, 76, 46, 125, 82, 56] [125, 81, 56, 125, 80, 53, 125, 74, 41, 125, 75, 43, 125, 81, 54, 125, 84, 60, 125, 87, 67, 125, 87, 67, 125, 86, 64, 125, 81, 56, 125, 80, 54, 125, 74, 41, 125, 73, 40, 125, 77, 47, 125, 82, 57, 125, 81, 55] [125, 81, 55, 125, 81, 56, 125, 82, 58, 125, 82, 57, 125, 81, 55, 125, 80, 54, 125, 79, 52, 125, 79, 52, 125, 80, 53, 125, 81, 55, 125, 81, 56, 125, 82, 58, 125, 83, 58, 125, 82, 57, 125, 81, 55, 125, 81, 55] [125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55] [125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55] [125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55, 125, 81, 55]
3つずつのペアになっていて、左から順にRGB値になってるっぽいですね。
用意した画像が16x16なので16x16個、ちゃんとあります。
とりあえず書き込む
何も加工せずそのまま書き込めば画像のコピーができるはず。
pw = png.Writer( x, y, interlace=False, bitdepth=meta['bitdepth'], planes=meta['planes'], alpha=False ) pw.write(open('after.png', 'wb'), pixels)
本題
透過するには書き込み時にalpha=Trueとすれば良いわけですが、3つずつのペアになっていたピクセルのデータを4つのペアにする必要があります。
4つ目がアルファ値ですね。0が透過、255が透過無し、のようです。
左上のドットのRGB[r,g,b]を覚えておいてそれに一致するドットは[0,0,0,0]とかにしたいわけです。
それ以外は[r,g,b,255]にすれば元のままです。
import png pr = png.Reader(file=open('before.png', 'rb')) x,y,pixels,meta = pr.asRGB8() new_pixels = [] i = -1 for ps in pixels: if i == -1: r = ps[0] g = ps[1] b = ps[2] i = 0 new_p = [] new_p_set = [] for p in ps: new_p_set.append(p) i += 1 if i >= 3: new_p_set.append(255) if new_p_set == [r, g, b, 255]: new_p_set = [0, 0, 0, 0] new_p.extend(new_p_set) i = 0 new_p_set = [] new_pixels.append(new_p) pw = png.Writer( x, y, interlace=False, bitdepth=meta['bitdepth'], planes=meta['planes'], alpha=True ) pw.write(open('after.png', 'wb'), new_pixels)
な、なんかPythonなのに汚いな!でも動いたからよし!
ところでwriteするときにリスト渡しちゃってるけどせっかくreadでgenerator返ってきてるのに意味なくね?
generatorにして引数に渡しましょう。勉強にもなるし。
import png pr = png.Reader(file=open('before.png', 'rb')) x,y,pixels,meta = pr.asRGB8() def new_pixels(pixels): i = -1 for ps in pixels: if i == -1: r = ps[0] g = ps[1] b = ps[2] i = 0 new_p = [] new_p_set = [] for p in ps: new_p_set.append(p) i += 1 if i >= 3: new_p_set.append(255) if new_p_set == [r, g, b, 255]: new_p_set = [0, 0, 0, 0] new_p.extend(new_p_set) i = 0 new_p_set = [] yield new_p pw = png.Writer( x, y, interlace=False, bitdepth=meta['bitdepth'], planes=meta['planes'], alpha=True ) pw.write(open('after.png', 'wb'), new_pixels(pixels))
できました。
ここでGAEのことを思い出します。
GAEはファイルの読み書きができないのでStringIOを使うのが定石らしいです。
from StringIO import StringIO import png #前略 pr = png.Reader(file=StringIO(image_binary_data)) #中略 o = StringIO() pw.write(o, new_pixels(pixels)) new_image_binary_data = o.getvalue() #後略
こんな感じで変換後のバイナリデータを取り出せます。
画像処理って難しいですね!