2016/11/27(日) [n年前の日記]
#1 [python] PySide の QImage を使って PIL Image と同様のモード結果を得たい
PIL Image は、Image.mode で画像モードを調べることができる。
例えば…。
コレと似たようなことを、PySide の QImage を使ってやりたいなと。
_image_format_out.py
テスト画像は以下。
_tmp_1bit.png
_tmp_grayscale.png
_tmp_indexed.png
_tmp_rgb.png
_tmp_rgba.png
こんな結果に。
QImage.format() で、 _QImage.Format の値が得られるので、それを使って画像モードを判別。
QImage の場合、グレースケール画像も、インデックスカラー画像も、QImage.Format_Indexed8 が返ってきてしまって悩んだけど。 _QImage.allGray() を使えば、パレット内容がグレースケールか、そうでないかが分かるので、そこからグレースケール or インデックスカラーを判別できた。
例えば…。
>>> from PIL import Image >>> im = Image.open("hoge.png") >>> im.modeとでもすれば、文字列で画像モード? 画像フォーマット? が分かる。
- "1" : 1bit画像。白黒のみの画像。
- "L" : グレースケール画像。256色のパレットを持っていて、パレットは0(黒)〜255(白)が割り当てられてる。
- "P" : インデックスカラー画像。256色以下のパレットを持っている。
- "RGB" : RGB画像。アルファチャンネルは持ってない。
- "RGBA" : RGBA画像。アルファチャンネルを持っている。
コレと似たようなことを、PySide の QImage を使ってやりたいなと。
_image_format_out.py
u""" QImageを使って画像フォーマットを確認する. L(グレースケール)、P(インデックスカラー)、RGB、RGBAを判断する。 動作確認 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4 + Pillow 3.4.2 """ import sys from PySide.QtCore import * # NOQA from PySide.QtGui import * # NOQA class MyWidget(QWidget): u"""メインウインドウ相当.""" def __init__(self, *argv, **keywords): """init.""" super(MyWidget, self).__init__(*argv, **keywords) l = QVBoxLayout() tb = QTextEdit(self) l.addWidget(tb) self.setLayout(l) lst = [ "./tmp_1bit.png", "./tmp_grayscale.png", "./tmp_indexed.png", "./tmp_rgb.png", "./tmp_rgba.png" ] for i, fpath in enumerate(lst): qim = QImage(fpath) ret = self.get_image_mode(qim) tb.append("%s is %s" % (fpath, ret)) def get_image_mode(self, qim): u"""画像モードを調べて返す. PIL Image.mode と同様、1,L,P,RGB,RGBAで返す。 """ ret = "" fmt = qim.format() if fmt == QImage.Format_Mono: ret = "1" elif fmt == QImage.Format_MonoLSB: ret = "1" elif fmt == QImage.Format_Indexed8: ret = "L" if qim.allGray() else "P" elif fmt == QImage.Format_RGB32: ret = "RGB" elif fmt == QImage.Format_ARGB32: ret = "RGBA" else: ret = "UNKNOWN" print(f) del qim return ret if __name__ == '__main__': app = QApplication(sys.argv) w = MyWidget() w.show() sys.exit(app.exec_())
テスト画像は以下。
_tmp_1bit.png
_tmp_grayscale.png
_tmp_indexed.png
_tmp_rgb.png
_tmp_rgba.png
こんな結果に。
QImage.format() で、 _QImage.Format の値が得られるので、それを使って画像モードを判別。
QImage の場合、グレースケール画像も、インデックスカラー画像も、QImage.Format_Indexed8 が返ってきてしまって悩んだけど。 _QImage.allGray() を使えば、パレット内容がグレースケールか、そうでないかが分かるので、そこからグレースケール or インデックスカラーを判別できた。
[ ツッコむ ]
#2 [python] PySide + Pillow(PIL) で塗り潰し
Pillow(PIL)の、ImageDraw.floodfill(PILImage, (x, y), (r, g, b, a)) を利用して、PySideのウインドウ上で塗り潰し処理を試したり。
_qimage2pil_test2.py
クリックすると、クリックした位置から塗り潰しができる。今回は只の動作確認なので、塗り潰し色は固定。
現状、PySide 1.2.4 と Pillow(PIL) 3.4.2 の組み合わせでは、ImageQt を使って PIL Image から QImage へ変換すると Python が死んでしまう不具合が発生する時があるけど。RGBA しか使わないという前提にすれば、処理内容が数行で済むので、各スクリプト内に処理を書けそうだな、と思えてきたので上記のスクリプトではそのように書いてみたり。
_qimage2pil_test2.py
u""" QPixmap → QImage → PIL Image に変換して塗り潰しの処理をしてみる. ImageQtに相当するクラスをスクリプト内に置いてみた版。 Pillow のバージョンが上がれば、このクラスは不要になるかもしれない。 動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4 + Pillow 3.4.2 """ import cStringIO import sys # ここでPyQt4を使わないようにしているはず…なのだが… from PySide import QtGui from PySide import QtCore sys.modules['PyQt4.QtGui'] = QtGui sys.modules['PyQt4.QtCore'] = QtCore from PySide.QtCore import * # NOQA from PySide.QtGui import * # NOQA from PIL import Image from PIL import ImageDraw # from PIL import ImageQt class ImageQtPoor(QImage): u"""ImageQt substitute. Supports only RGBA image.""" def __init__(self, im): """convert PIL Image to PySide QImage.""" self.org_mode = im.mode if im.mode != "RGBA": im = im.convert("RGBA") w, h = im.size fmt = QImage.Format_ARGB32 self.__data = im.tobytes("raw", "BGRA") super(ImageQtPoor, self).__init__(self.__data, w, h, fmt) @staticmethod def fromqimage(qim): """convert PySide QImage ro PIL Image.""" buf = QBuffer() buf.open(QIODevice.ReadWrite) qim.save(buf, "PNG") fp = cStringIO.StringIO() fp.write(buf.data()) buf.close() fp.seek(0) return Image.open(fp) @staticmethod def toqimage(pim): """convert PIL Image to PySide QImage.""" fp = cStringIO.StringIO() pim.save(fp, "PNG") qim = QImage() qim.loadFromData(fp.getvalue(), "PNG") return qim class GView(QGraphicsView): u"""画像アイテム表示用.""" def __init__(self, *argv, **keywords): """init.""" super(GView, self).__init__(*argv, **keywords) self.setBackgroundBrush(Qt.gray) scene = QGraphicsScene(self) self.setScene(scene) pm = QPixmap("./tmp_rgba.png") self.pm_item = QGraphicsPixmapItem(pm) scene.addItem(self.pm_item) def mousePressEvent(self, event): u"""マウスクリックした際に呼ばれる処理.""" p = self.mapToScene(event.pos()) x, y = int(p.x()), int(p.y()) pm = self.pm_item.pixmap() # QPixmapを取得 qim = pm.toImage() # QImageに変換 pim = ImageQtPoor.fromqimage(qim) # PIL Imageに変換 if pim.mode != "RGBA": pim = pim.convert("RGBA") # 塗り潰し(Flood Fill)をする ImageDraw.floodfill(pim, (x, y), (128, 0, 64, 255)) qim2 = ImageQtPoor(pim) # PIL Image → QImageに変換 # qim2 = ImageQtPoor.toqimage(pim) pm2 = QPixmap.fromImage(qim2) # QPixmapに変換 self.pm_item.setPixmap(pm2) class MyWidget(QWidget): u"""メインウインドウ相当.""" def __init__(self, *argv, **keywords): """init.""" super(MyWidget, self).__init__(*argv, **keywords) gv = GView(self) l = QVBoxLayout() l.addWidget(gv) self.setLayout(l) if __name__ == '__main__': app = QApplication(sys.argv) w = MyWidget() w.show() sys.exit(app.exec_())
クリックすると、クリックした位置から塗り潰しができる。今回は只の動作確認なので、塗り潰し色は固定。
現状、PySide 1.2.4 と Pillow(PIL) 3.4.2 の組み合わせでは、ImageQt を使って PIL Image から QImage へ変換すると Python が死んでしまう不具合が発生する時があるけど。RGBA しか使わないという前提にすれば、処理内容が数行で済むので、各スクリプト内に処理を書けそうだな、と思えてきたので上記のスクリプトではそのように書いてみたり。
[ ツッコむ ]
#3 [python] PySide の QImage を QPixmap に変換するとアルファチャンネルが消える問題
QImageの全ドットが不透明な状態で、QPixmap.fromImage(QImage) を使って QPixmap に変換すると、QPixmap のアルファチャンネルが消滅する、という問題に遭遇。
PySide.QtCore.Qt.ImageConversionFlag の、Qt.NoOpaqueDetection を指定してやれば問題を回避できる。QPixmap.fromImage(QImage, Qt.NoOpaqueDetection) といった感じで指定する。以下のドキュメント内で、ImageConversionFlag でページ検索すれば説明が読める。
_Qt - PySide v1.0.7 documentation
参考までに、Google翻訳の結果をそのまま引用。
PySide.QtCore.Qt.ImageConversionFlag の、Qt.NoOpaqueDetection を指定してやれば問題を回避できる。QPixmap.fromImage(QImage, Qt.NoOpaqueDetection) といった感じで指定する。以下のドキュメント内で、ImageConversionFlag でページ検索すれば説明が読める。
_Qt - PySide v1.0.7 documentation
参考までに、Google翻訳の結果をそのまま引用。
Qt.NoOpaqueDetection全ドットに対して、透明・半透明部分の有無をチェックしながら変換してたのか…。
イメージに不透明でないピクセルが含まれているかどうかを確認しないでください。 画像が半透明で、不透明でないピクセルが見つかるまで画像のピクセルをチェックするオーバーヘッドを避けたい場合や、その他の理由でピックスマップにアルファチャンネルを保持させたい場合は、これを使用します。 画像にアルファチャンネルがない場合、このフラグは効果がありません。
[ ツッコむ ]
以上、1 日分です。