Python3 + Pillow + QT5:调整包含图像的标签大小时崩溃
Python3 + Pillow + QT5: Crash when I resize a label containing an image
我收到一个消息框:"Python has stopped working" 当我将图像加载到已经可见的 window 中的 QLabel 中时。选择调试显示:Python.exe.
中发生未处理的 Win32 异常
如果我在显示 window 之前将图像加载到标签中,它会正确显示。
这是精简代码:
#!/usr/bin/etc python
import sys
import os
import stat
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import *
from PIL.ImageQt import *
def update(label):
filename = r"C:\Users\me\Pictures\images[=11=]000229.jpg"
im1 = Image.open(filename)
print ("Read ({},{})".format(im1.width, im1.height))
im2 = im1.rotate(90, expand=True)
print("Rotate ({},{})".format(im2.width, im2.height))
im2.thumbnail((1200,1200))
print("Thumbnail({},{})".format(im2.width, im2.height))
qimage = ImageQt(im2)
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
app = QApplication(sys.argv)
desktop = QDesktopWidget()
deskGeometry = desktop.availableGeometry()
print("desktop ({},{})".format(deskGeometry.width(), deskGeometry.height()))
window = QFrame()
# If you let QT pick the sizes itself, it picks dumb ones, then complains
# 'cause they are dumb
window.setMinimumSize(300, 200)
window.setMaximumSize(deskGeometry.width(), deskGeometry.height())
label = QLabel()
#call update here: no crash
caption = QLabel()
caption.setText("Hello world")
box = QVBoxLayout()
box.addWidget(label)
box.addWidget(caption)
#call update here, no crash
window.setLayout(box)
#call update here, no crash
window.show()
#this call to update results in a crash
update(label)
#window.updateGeometry()
print("App: exec")
app.exec_()
输出:
desktop (3623,2160)
Read (1515,1051)
Rotate (1051,1515)
Thumbnail(832,1200)
App: exec
我是否需要做任何特别的事情来告诉 QT window 大小将会改变?从这里诊断问题的任何建议...
更新:
如果我复制更新函数的主体并将其粘贴到更新调用的位置,它不会再崩溃 -- 它会按预期工作。
由此我得出结论,存在对象生命周期问题。在幕后的某个地方,QT and/or Pillow 保留了一个指向内部缓冲区的指针,而不是制作副本或 "stealing" 缓冲区。当包含缓冲区的对象被删除时,指针变为无效并且 "Bad Things Happen[TM]"
现在确定谁在偷懒...
我根据更新中提到的观察发现了一个解决方案,这似乎是一个对象生命周期问题。
将 update
函数中的行更改为
pixmap = QPixmap.fromImage(qimage)
到
pixmap = QPixmap.fromImage(qimage).copy()
强制复制像素图。这个副本显然有自己的数据缓冲区,而不是从图像中借用缓冲区。
标签然后保留对像素图的引用——确保缓冲区的生命周期。 'bug' 似乎是 QPixmap.fromImage 捕获指向图像中数据的指针,但不保留对图像的引用,因此如果图像被垃圾收集(这可能是因为它很大对象,标签(和像素图)有一个指向未分配内存的指针。
[这 'pointer to the buffer' 纯粹是我的猜测,但最重要的是程序不再崩溃。]
以防其他人偶然发现:
我遇到了一个非常相似的问题,并且能够使用插槽和信号(基于 this great article)解决它,因为 .copy() 解决方案对我不起作用。我的解决方案是将 QPixmap 和 QLabel.setPixmap 的创建分成不同的函数。当创建 QPixmap 的函数完成时,它会发出一个信号,触发将像素图设置为标签的函数。
例如,如果您想使用线程:
class WorkerSignals(QObject):
ready = pyqtSignal()
class Worker(QRunnable):
def __init__(self, some_function, *args, **kwargs):
super(Worker, self).__init__()
self.some_function = some_function
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['ready'] = self.signals.ready
@pyqtSlot()
def run(self):
self.some_function(*self.args, **self.kwargs)
def make_pixmap(qimage, ready, **kwargs):
pixmap = QPixmap.fromImage(qimage) # qimage is some QImage
ready.emit()
def set_pixmap(pixmap):
label.setPixmap(pixmap) # 'label' is some QLabel
threadpool = QThreadPool
worker = Worker(make_pixmap, image)
worker.signals.ready.connect(set_pixmap)
threadpool.start(worker)
显然在这种情况下不需要使用线程,但这只是为了表明如果您想要处理和显示图像而不挂起 GUI 的其余部分是可能的。
编辑:崩溃又回来了,忽略上面的内容。正在处理另一个修复程序。
我收到一个消息框:"Python has stopped working" 当我将图像加载到已经可见的 window 中的 QLabel 中时。选择调试显示:Python.exe.
中发生未处理的 Win32 异常如果我在显示 window 之前将图像加载到标签中,它会正确显示。
这是精简代码:
#!/usr/bin/etc python
import sys
import os
import stat
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import *
from PIL.ImageQt import *
def update(label):
filename = r"C:\Users\me\Pictures\images[=11=]000229.jpg"
im1 = Image.open(filename)
print ("Read ({},{})".format(im1.width, im1.height))
im2 = im1.rotate(90, expand=True)
print("Rotate ({},{})".format(im2.width, im2.height))
im2.thumbnail((1200,1200))
print("Thumbnail({},{})".format(im2.width, im2.height))
qimage = ImageQt(im2)
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
app = QApplication(sys.argv)
desktop = QDesktopWidget()
deskGeometry = desktop.availableGeometry()
print("desktop ({},{})".format(deskGeometry.width(), deskGeometry.height()))
window = QFrame()
# If you let QT pick the sizes itself, it picks dumb ones, then complains
# 'cause they are dumb
window.setMinimumSize(300, 200)
window.setMaximumSize(deskGeometry.width(), deskGeometry.height())
label = QLabel()
#call update here: no crash
caption = QLabel()
caption.setText("Hello world")
box = QVBoxLayout()
box.addWidget(label)
box.addWidget(caption)
#call update here, no crash
window.setLayout(box)
#call update here, no crash
window.show()
#this call to update results in a crash
update(label)
#window.updateGeometry()
print("App: exec")
app.exec_()
输出:
desktop (3623,2160)
Read (1515,1051)
Rotate (1051,1515)
Thumbnail(832,1200)
App: exec
我是否需要做任何特别的事情来告诉 QT window 大小将会改变?从这里诊断问题的任何建议...
更新:
如果我复制更新函数的主体并将其粘贴到更新调用的位置,它不会再崩溃 -- 它会按预期工作。
由此我得出结论,存在对象生命周期问题。在幕后的某个地方,QT and/or Pillow 保留了一个指向内部缓冲区的指针,而不是制作副本或 "stealing" 缓冲区。当包含缓冲区的对象被删除时,指针变为无效并且 "Bad Things Happen[TM]"
现在确定谁在偷懒...
我根据更新中提到的观察发现了一个解决方案,这似乎是一个对象生命周期问题。
将 update
函数中的行更改为
pixmap = QPixmap.fromImage(qimage)
到
pixmap = QPixmap.fromImage(qimage).copy()
强制复制像素图。这个副本显然有自己的数据缓冲区,而不是从图像中借用缓冲区。
标签然后保留对像素图的引用——确保缓冲区的生命周期。 'bug' 似乎是 QPixmap.fromImage 捕获指向图像中数据的指针,但不保留对图像的引用,因此如果图像被垃圾收集(这可能是因为它很大对象,标签(和像素图)有一个指向未分配内存的指针。
[这 'pointer to the buffer' 纯粹是我的猜测,但最重要的是程序不再崩溃。]
以防其他人偶然发现:
我遇到了一个非常相似的问题,并且能够使用插槽和信号(基于 this great article)解决它,因为 .copy() 解决方案对我不起作用。我的解决方案是将 QPixmap 和 QLabel.setPixmap 的创建分成不同的函数。当创建 QPixmap 的函数完成时,它会发出一个信号,触发将像素图设置为标签的函数。
例如,如果您想使用线程:
class WorkerSignals(QObject):
ready = pyqtSignal()
class Worker(QRunnable):
def __init__(self, some_function, *args, **kwargs):
super(Worker, self).__init__()
self.some_function = some_function
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['ready'] = self.signals.ready
@pyqtSlot()
def run(self):
self.some_function(*self.args, **self.kwargs)
def make_pixmap(qimage, ready, **kwargs):
pixmap = QPixmap.fromImage(qimage) # qimage is some QImage
ready.emit()
def set_pixmap(pixmap):
label.setPixmap(pixmap) # 'label' is some QLabel
threadpool = QThreadPool
worker = Worker(make_pixmap, image)
worker.signals.ready.connect(set_pixmap)
threadpool.start(worker)
显然在这种情况下不需要使用线程,但这只是为了表明如果您想要处理和显示图像而不挂起 GUI 的其余部分是可能的。
编辑:崩溃又回来了,忽略上面的内容。正在处理另一个修复程序。