2022/05/19(木) [n年前の日記]
#1 [python][gimp] Python-FuでARGB(BRGA)をRGBAにしたい
GIMP の Python-Fu + pycairo を使って、何かしらを描画して、GIMPのレイヤーに転送したい。
pycairo の surface は、1ドットにつきARGB(リトルエンディアンならBRGA)の並びで格納されている。対して、GIMPのレイヤーは1ドットにつきRGBAの並びになっているので、ARGB(BRGA)からRGBAに並びを変更する処理を書かないといけない。
以前、そういった処理に関してベンチマークを取りながら、16秒かかってた処理を1.7秒まで短くできたけれど。
_mieki256's diary - Pythonのリスト内包表記を試してみた
_mieki256's diary - Pythonのstructを使った時の処理時間を調べてた
リトルエンディアン、ビッグエンディアンを事前に判定してその後の処理を変えれば、もっとシンプルに書けるのではないか、処理時間も短くならないかと思えてきたので、そういう処理を試してみた。
環境は、Windows10 x64 21H2 + Python 2.7.18 32bit。ちなみに、前回実験した時は CPU が Ryzen 7 1700 だったけど、Ryzen 5 3600 に変わっているので、処理時間が少し短くなってる。
以下が、実験用スクリプト。
_04_argb_rgba_conv_2.py
関係ありそうなところだけを引用。get_rgba_str_g() が、今のところ最短時間で処理できる書き方。get_rgba_str_(h|i|j)() が、今回試した書き方。
ベンチマークを取ってみた。
今回の書き方で、元々は10秒ほどかかってた処理を1.45秒にすることはできたけど、それでも、前回辿り着いた(?)書き方なら1.29秒で終わるので、struct.pack()、struct.unpack()、リスト内包表記を使った書き方が今のところベストっぽい。
もっとも、また違う書き方をすれば、更に高速化できるかもしれない。自分は Python について詳しくないのでアレだけど。
ちなみに、以下の Python-Fuスクリプトにも、このあたりの書き方を反映させておいた。
_mieki256/sci-fi-texture-generator: Sci-Fi bump mapping texture generator with GIMP Script-fu.
pycairo の surface は、1ドットにつきARGB(リトルエンディアンならBRGA)の並びで格納されている。対して、GIMPのレイヤーは1ドットにつきRGBAの並びになっているので、ARGB(BRGA)からRGBAに並びを変更する処理を書かないといけない。
以前、そういった処理に関してベンチマークを取りながら、16秒かかってた処理を1.7秒まで短くできたけれど。
_mieki256's diary - Pythonのリスト内包表記を試してみた
_mieki256's diary - Pythonのstructを使った時の処理時間を調べてた
リトルエンディアン、ビッグエンディアンを事前に判定してその後の処理を変えれば、もっとシンプルに書けるのではないか、処理時間も短くならないかと思えてきたので、そういう処理を試してみた。
環境は、Windows10 x64 21H2 + Python 2.7.18 32bit。ちなみに、前回実験した時は CPU が Ryzen 7 1700 だったけど、Ryzen 5 3600 に変わっているので、処理時間が少し短くなってる。
以下が、実験用スクリプト。
_04_argb_rgba_conv_2.py
関係ありそうなところだけを引用。get_rgba_str_g() が、今のところ最短時間で処理できる書き方。get_rgba_str_(h|i|j)() が、今回試した書き方。
def get_rgba_str_g(src): lmax = len(src) / 4 argb = list(struct.unpack("=%dL" % lmax, src)) rgba = [(((d & 0x0ffffff) << 8) + ((d >> 24) & 0x0ff)) for d in argb] return struct.pack(">%dL" % lmax, *rgba) def get_rgba_str_h(src): dst = list(src) if sys.byteorder == "little": for i in range(0, len(src), 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 2], src[i + 1], src[i], src[i + 3] else: for i in range(0, len(src), 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 1], src[i + 2], src[i + 3], src[i] return ''.join(dst) def get_rgba_str_i(src): dst = list(src) if sys.byteorder == "little": for i in range(0, len(src), 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 2], src[i + 1], src[i], src[i + 3] else: for i in range(0, len(src), 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 1], src[i + 2], src[i + 3], src[i] return struct.pack("%dc" % len(src), *dst) def get_rgba_str_j(src): cnt = len(src) dst = bytearray(cnt) if sys.byteorder == "little": for i in range(0, cnt, 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 2], src[i + 1], src[i], src[i + 3] else: for i in range(0, cnt, 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 1], src[i + 2], src[i + 3], src[i] return dst
ベンチマークを取ってみた。
> py -2 04_argb_rgba_conv_2.py start. create source data. ## benchmarker: release 4.0.1 (for python) ## python version: 2.7.18 ## python compiler: MSC v.1500 32 bit (Intel) ## python platform: Windows-10-10.0.19041 ## python executable: C:\Python\Python27\python.exe ## cpu model: AMD64 Family 23 Model 113 Stepping 0, AuthenticAMD ## parameters: loop=5, cycle=1, extra=0 ## real (total = user + sys) argb split 9.7370 9.7344 7.6094 2.1250 rgb shift A 3.0880 3.0781 3.0625 0.0156 rgb shift G 1.2940 1.2969 1.2188 0.0781 rgb shift H 1.6870 1.6875 1.6875 0.0000 rgb shift I 1.8540 1.8594 1.7812 0.0781 rgb shift J 1.4470 1.4531 1.4531 0.0000 ## Ranking real rgb shift G 1.2940 (100.0) ******************** rgb shift J 1.4470 ( 89.4) ****************** rgb shift H 1.6870 ( 76.7) *************** rgb shift I 1.8540 ( 69.8) ************** rgb shift A 3.0880 ( 41.9) ******** argb split 9.7370 ( 13.3) *** ## Matrix real [01] [02] [03] [04] [05] [06] [01] rgb shift G 1.2940 100.0 111.8 130.4 143.3 238.6 752.5 [02] rgb shift J 1.4470 89.4 100.0 116.6 128.1 213.4 672.9 [03] rgb shift H 1.6870 76.7 85.8 100.0 109.9 183.0 577.2 [04] rgb shift I 1.8540 69.8 78.0 91.0 100.0 166.6 525.2 [05] rgb shift A 3.0880 41.9 46.9 54.6 60.0 100.0 315.3 [06] argb split 9.7370 13.3 14.9 17.3 19.0 31.7 100.0
今回の書き方で、元々は10秒ほどかかってた処理を1.45秒にすることはできたけど、それでも、前回辿り着いた(?)書き方なら1.29秒で終わるので、struct.pack()、struct.unpack()、リスト内包表記を使った書き方が今のところベストっぽい。
もっとも、また違う書き方をすれば、更に高速化できるかもしれない。自分は Python について詳しくないのでアレだけど。
ちなみに、以下の Python-Fuスクリプトにも、このあたりの書き方を反映させておいた。
_mieki256/sci-fi-texture-generator: Sci-Fi bump mapping texture generator with GIMP Script-fu.
◎ bytearrayを知った。 :
当初、バイト文字列をインデックス値を使って変更しようとしたらエラーになって、一旦リスト化しないと変更できないのかなと思ったのだけど。Python2.7 にも bytearray というものがあって、それを使えばバイト文字列相当をインデックス値で指定して変更できると知った。
Python では、通常のバイト文字列は変更不可だけど、bytearray なら変更可能、ということらしい。
前述の関数で言えば、get_rgba_str_h() が一旦リスト化している例で、get_rgba_str_j() がbytearray を使った例。
Python では、通常のバイト文字列は変更不可だけど、bytearray なら変更可能、ということらしい。
前述の関数で言えば、get_rgba_str_h() が一旦リスト化している例で、get_rgba_str_j() がbytearray を使った例。
def get_rgba_str_h(src): dst = list(src) if sys.byteorder == "little": for i in range(0, len(src), 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 2], src[i + 1], src[i], src[i + 3] else: for i in range(0, len(src), 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 1], src[i + 2], src[i + 3], src[i] return ''.join(dst)
def get_rgba_str_j(src): cnt = len(src) dst = bytearray(cnt) if sys.byteorder == "little": for i in range(0, cnt, 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 2], src[i + 1], src[i], src[i + 3] else: for i in range(0, cnt, 4): dst[i], dst[i + 1], dst[i + 2], dst[i + 3] = src[i + 1], src[i + 2], src[i + 3], src[i] return dst
◎ Python 3.x では動かない。 :
上記のスクリプトは、Python 2.7 では動いたけれど、Python 3.x ではエラーが出て動かなかった。
どうも join() の処理が違っているようだなと…。おそらく以下で紹介されている事例に引っ掛かっている予感。str は結合できるけど int は結合できないのだとか。
_TypeError: sequence item 0: expected string, int found - せつないぶろぐ
また、Python 3.x は「/」を使って除算をした時に float になってしまう点もよろしくなかった。「//」を使って結果を整数に限定するべきだった。
どうも join() の処理が違っているようだなと…。おそらく以下で紹介されている事例に引っ掛かっている予感。str は結合できるけど int は結合できないのだとか。
_TypeError: sequence item 0: expected string, int found - せつないぶろぐ
また、Python 3.x は「/」を使って除算をした時に float になってしまう点もよろしくなかった。「//」を使って結果を整数に限定するべきだった。
[ ツッコむ ]
以上、1 日分です。