milk_spoonのブログ

さじの情報科学的活動、考えたこと、その他を雑記するためのブログです。

PySide2+Pillowでコード-1073740771 (0xC000041D)エラー

前の記事ではwxPythonが~PyQtが~って言ってたんですが結局PySide2やってます。
ほとんどPyQt5と違いは無いらしいけど、ライセンスがLGPLなのでちょっと安心かなと
(あまり皆気にしてないような気もする)

2020/12/20追記

PySide2で開発してて、こんな感じでエラーコードしか出なくて詳しいエラーがわからなくなったとき
もしPyCharm等のIDEから実行してる場合は一度素のpythonインタプリタから実行してみると文字列でエラーが出る時がある。

あと大抵はオブジェクトが勝手に破棄されるエラーのような気も。
PySide2関連のオブジェクトを作ったらとりあえずコンストラクタのparent(QObjectを継承してるなら必ずある)を設定して親子関係にしてあげるか、クラスのメンバとして保持してあげる。
QThreadを作ってjoinしないで実行しっぱなしだからってparentにも設定しないメンバにも持たないさようなら~だと死。

本題

で、タイトルの通り…
Pillowを使って画像をいじり、QPixmapでPySide2のGUI画面上に表示しようとしたら
コード-1073740771 (0xC000041D)でエラーになりました。
f:id:milk_spoon:20191123143653p:plain

結論から言うと、QPixmapを作るときにcopyするとOKでした。

こうしていたのを

#image: PIL.Image
qtimg = ImageQt(image)
pixmap = QPixmap.fromImage(qtimg)

こう。

#image: PIL.Image
qtimg = ImageQt(image)
pixmap = QPixmap.fromImage(qtimg).copy()  #★copy

ここの回答ほぼまんまで解決しましたが、エラー内容は違うかな…?
stackoverflow.com
下で書きますが、QGraphicsViewあたりもいじってたので原因特定がちょっと大変でした…

具体例(半分雑記)

PillowのImageGrabでスクリーンショットを撮って
PySide2のQGraphicsViewに表示させよう~としたらハマりました。
再現できるコードはコチラ。

#main_form_ui.py
#UI部分のみ
from PySide2 import QtCore, QtGui, QtWidgets

class Ui_MainForm(object):
    def setupUi(self, MainForm):
        MainForm.setObjectName("MainForm")
        MainForm.resize(400, 300)
        self.horizontalLayout = QtWidgets.QHBoxLayout(MainForm)
        self.horizontalLayout.setObjectName("horizontalLayout")
        #QGraphicsViewを配置
        self.viewMain = QtWidgets.QGraphicsView(MainForm)
        self.viewMain.setObjectName("viewMain")
        self.horizontalLayout.addWidget(self.viewMain)

        self.retranslateUi(MainForm)
        QtCore.QMetaObject.connectSlotsByName(MainForm)

    def retranslateUi(self, MainForm):
        MainForm.setWindowTitle(QtWidgets.QApplication.translate("MainForm", "Dialog", None, -1))
#main_form.py
#フォームの実装とmain
from PySide2.QtWidgets import QApplication, QMainWindow, QDialog, QGraphicsScene,QGraphicsPixmapItem
from PySide2.QtGui import QPixmap
from PIL import Image, ImageGrab
from PIL.ImageQt import ImageQt
from main_form_ui import Ui_MainForm
import sys

class MainForm(QDialog):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.ui = Ui_MainForm()
        self.ui.setupUi(self)

        scene = QGraphicsScene(None)
        #スクリーンショットを取得、QGraphicsPixmapItemになるまで変換してsceneに追加
        image = ImageGrab.grab()
        imgqt = ImageQt(image)
        #★ココ!
        pixmap = QPixmap.fromImage(imgqt)#.copy()
        pixmap_item = QGraphicsPixmapItem(pixmap)
        scene.addItem(pixmap_item)

        self.ui.viewMain.setScene(scene)

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = MainForm()
    window.show()

    sys.exit(app.exec_())

ウィンドウにQGraphicsViewが貼ってあって、そこに起動時にImageGrabで撮ったスクショを貼るだけのコードです。

問題の箇所は★ココ!の部分で、ImageGrabで取得したスクショ画像をQPixmapにするところですね。
ちなみにImageGrabではなく、Image.openで画像ファイルを開いたものを渡したときはQPixmapをcopyしなくても動作していました。
StackOverflowの回答にあるように、特定状況でリソースが解放されてしまう感じなんでしょうか。

こんな感じで。
PySide2楽しいです。皆どんどんGUIpythonで書こう~