Qt 5.10 中的 QPainter::drawImage() 故障
Posted
技术标签:
【中文标题】Qt 5.10 中的 QPainter::drawImage() 故障【英文标题】:QPainter::drawImage() glitches in Qt 5.10 【发布时间】:2018-04-19 21:19:06 【问题描述】:我的应用程序将 3D 渲染绘制到离线 QImage
(由于历史原因没有使用 OpenGL),然后在这样的小部件中绘制 QImage
:
void Render3dModel::paintEvent(QPaintEvent *event)
// Update model and render new image
m_model.calculate();
QImage image = m_model.getImage();
// Draw image in widget
QPainter painter(this);
painter.drawImage(QPointF(0, 0), image);
使用 QWidget::update() 根据鼠标移动重新绘制图像:
void Render3dModel::mouseMoveEvent(QMouseEvent *event)
m_model.rotate(event);
update();
这种方法在 Qt 5.6 和 Qt 5.9 中运行迅速且完美无缺。然而,在 Qt 5.10 中重建完全相同的代码会导致奇怪的故障。两个帧的一部分同时可见。这不是临时效果(例如延迟绘图),因为如果我停止移动鼠标,它实际上会一直显示两帧部分。下面是一个例子(为了清晰起见,使用交替的灰色和白色背景来表示偶数/奇数帧):
我尝试在QImage
环形缓冲区中使用image.copy()
创建深层副本,以查看这是否是QImage
的销毁/重用和一些延迟/延迟绘制之间的某种竞争条件,但这无济于事。
显然QPainter
的机制在 Qt 5.9 和 Qt 5.10 之间发生了变化,但我在文档中找不到任何线索。
请指教
更新
在玩了几个窗口大小之后,我得出的结论是,故障以某种方式对应于左上角与“截面尺寸”标签的“S”对齐的相同大小的矩形。但是,正如您在屏幕截图中看到的那样,具有白色背景的框架将圆锥的那部分绘制在正确的位置。所以图像不是移位,而是以某种方式剪辑?!
我已经尝试添加
painter.setClipping(false);
painter.setClipRect(QRect(0, 0, size().width(), size().height()));
但这没有任何效果。我的Render3dModel
偶尔会从它的父母那里得到paintEvent
吗?
更新 2
在下面 Kuba Ober 的最小示例中添加 mouseMoveEvent
处理程序,可以在 MacBook(OS X 10.12.6,Intel HD Graphics 515)上重现该问题:
这可能是 Qt 5.10 中的回归,因为我无法在 Qt 5.9 中重现它吗?我应该提交错误报告吗?
更新 3
这似乎确实是 OS X 上 Qt 5.10 中的回归。已提交错误报告 https://bugreports.qt.io/browse/QTBUG-67998
【问题讨论】:
不,Render3dModel
没有从它的父母那里获得绘画事件。如果是这样,那么您已经破坏了进程的执行状态。 Qt 不做这些事情。
我想我知道您的问题可能是什么:您在某处使用了错误的尺寸。检查图像和 Render3dModel
的大小是否正确!
它只需重复发出update()
即可重现,无需鼠标事件。这确实是一个 Qt 5.10 回归。
【参考方案1】:
我测试了@KubaOber 提供的最小示例,按照 OP 的建议添加了 mouseMoveEvent() 处理程序,但我无法在我的平台上重现该问题(Qt 5.10. 1 - Linux)。
在我看来,这可能是一个特定于平台的问题。 Qt 5.9 和 Qt 5.10 之间的 cocoa 平台插件有很多不同之处,特别是关于 QCocoaBackingStore 及其 flush() 方法 (changelog here)。
如果最小、完整的示例很好地显示了问题,我建议将其报告给 Qt 的错误跟踪器https://bugreports.qt.io。
【讨论】:
【参考方案2】:单线程代码中不能有竞争条件。没有延迟/延迟绘画之类的东西。当paintEvent
返回时,该小部件的绘制就完成了。
所见即所得。 getImage()
方法可能会返回垃圾 - 可能是尺寸过小的图像 - 然后你绘制那个垃圾。将getImage()
替换为简单的交替填充,您会发现它工作正常:
// https://github.com/KubaO/***n/tree/master/questions/glitchy-paint-49930405
#include <QtWidgets>
#include <array>
class Render3dModel : public QWidget
struct Model
mutable int counter = ;
QSize size;
QFont font"Helvetica", 48;
QImage getImage() const
static auto const format = QPixmap1,1.toImage().format();
QImage imagesize, format;
image.fill((counter & 1) ? Qt::blue : Qt::yellow);
QPainter p(&image);
p.setFont(font);
p.setPen((counter & 1) ? Qt::yellow : Qt::blue);
p.drawText(image.rect(), QString::number(counter));
counter++;
return image;
m_model;
void paintEvent(QPaintEvent *) override
m_model.size = size();
auto image = m_model.getImage();
QPainterthis.drawImage(QPoint0, 0, image);
;
int main(int argc, char **argv)
QApplication appargc, argv;
QWidget win;
QVBoxLayout topLayout&win;
QTabWidget tabs;
topLayout.addWidget(&tabs);
// Tabs
for (auto text : "Shape", "Dimensions", "Layout") tabs.addTab(new QWidget, text);
tabs.setCurrentIndex(1);
QHBoxLayout tabLayouttabs.currentWidget();
QGroupBox dims"Section Dimensions", model"3D Model";
QGridLayout dimsLayout&dims, modelLayout&model;
for (auto w : &dims, &model) tabLayout.addWidget(w);
// Section Dimensions
for (auto text : "Diameter 1", "Diameter 2", "Length")
auto row = dimsLayout.rowCount();
std::array<QWidget*, 3> widgetsnew QLabeltext, new QDoubleSpinBox, new QLabel"inch";
for (auto *w : widgets)
dimsLayout.addWidget(w, row, dimsLayout.count() % widgets.size());
tabLayout.setAlignment(&dims, Qt::AlignLeft | Qt::AlignTop);
dims.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
// Model
Render3dModel render;
modelLayout.addWidget(&render);
model.setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
model.setMinimumSize(250, 250);
win.show();
return app.exec();
【讨论】:
感谢您的建议。不幸的是,尝试您的代码同时给出蓝色和黄色矩形,类似于我的示例。所以我们可以排除m_model.getImage()。 Qt 或我使用它的方式似乎确实存在问题。 @JasonSmith 太棒了。我只能告诉你的是,它可以在一个独立的例子中工作。您的代码中还有一些其他的东西把事情搞砸了,可能是写入了不应该写入的内存,等等。如果您使用多线程,请记住给定的QImage
实例一次只能从一个线程使用!将它从一个线程传递到另一个线程是有效的,但只能作为(浅)副本。
Qt 的隐式共享是线程安全的,因此只要两个线程在QImage
的单独实例上操作,即使一个是从另一个分配的,它也能正常工作。但是,任何访问都必须通过 QImage
实例。如果您持有指向图像中位的任何指针,则如果图像已对其进行复制,则无法对它们进行操作。在将第二张图像传递到其他地方之前,您必须分离并因此深拷贝第二张图像。这是隐式共享的一个常见警告:对象本身必须知道写入,并且会阻止第一个写入,直到它可以分离(如果共享,则为深拷贝)。
感谢您的精心回复。请将 mouseMoveEvent() 处理程序添加到您的最小示例中,您将看到问题重现!我已经用相应的屏幕截图更新了我的问题。【参考方案3】:
这是你的错误:
void Render3dModel::paintEvent(QPaintEvent *event)
// Update model and render new image
m_model.calculate();
...
您不应将模型更新与绘制事件联系起来。绘制事件应该绘制模型的当前状态,并且您可能会获得多个后续和/或部分绘制事件,例如露出窗户的一部分。
您在应用程序中看到的是部分公开事件,在这种情况下是 Qt 中的错误导致的,但通常您可能会收到部分公开事件并且应该准备好处理它们。
【讨论】:
感谢您对此进行调查。与这个最小示例不同,实际应用程序在mouseMoveEvent
中进行模型更新。但是即使我们通过将counter++
移动到mouseMoveEvent()
处理程序来更新最小示例,问题仍然存在。所以接收多个后续和/或部分事件似乎不是问题。
对,修复代码后,您现在可以点击bugreports.qt.io/browse/QTBUG-68023以上是关于Qt 5.10 中的 QPainter::drawImage() 故障的主要内容,如果未能解决你的问题,请参考以下文章
使用 Python (PyQT 5.10) 在 QT 5.10 中加载自己的字体
如何在Windows XP上部署Qt 5.10 Quick 2应用程序?
Qt 5.10 QGraphicsView 无法将 QGraphicsScene 缩放到全屏