QPainter::drawImage() Qt 5.10 中的故障
QPainter::drawImage() glitches in Qt 5.10
我的应用程序将 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 中重建完全相同的代码会导致奇怪的故障。两个帧的部分同时可见。这不是临时效果(例如延迟绘制),因为如果我停止移动鼠标,它实际上会持续显示两帧的部分。下面是一个示例(为清晰起见,even/odd 帧使用交替的灰色和白色背景):
我尝试在 QImage
ringbuffer 中使用 image.copy()
创建深层副本,看看这是否是 destruction/reuse 和 QImage
之间的某个 race-condition 和一些 deferred/delayed 绘画,但这无济于事。
显然 QPainter
的机制在 Qt 5.9 和 Qt 5.10 之间发生了变化,但我无法在文档中找到任何线索。
请指教
更新
玩过几个 window 尺寸后,我得出的结论是,故障以某种方式对应于 upper-left 角与 "S" 对齐的相同尺寸矩形 "Section dimensions"标签。但是,正如您在屏幕截图中看到的那样,白色背景的框架在正确的位置绘制了圆锥体的那一部分。所以图像不是 shifted,而是 clipped 不知何故?!
我试过添加
painter.setClipping(false);
painter.setClipRect(QRect(0, 0, size().width(), size().height()));
但这没有效果。我的 Render3dModel
偶尔会从它的 parents 得到 paintEvent
吗?
更新 2
向 Kuba Ober 下面的最小示例添加 mouseMoveEvent
处理程序,在 MacBook(OS X 10.12.6,Intel HD Graphics 515)上重现该问题:
这可能是 Qt 5.10 中的回归,因为我无法在 Qt 5.9 中重现它吗?我应该提交错误报告吗?
更新 3
这似乎确实是 Qt 5.10 在 OS X 上的回归。已提交错误报告 https://bugreports.qt.io/browse/QTBUG-67998
单线程代码中不能存在竞争条件。没有delayed/deferred绘画之类的东西。到 paintEvent
returns 时,该小部件的绘制已完成。
所见即所得。 getImage()
方法可能 returns 垃圾 - 也许是尺寸过小的图像 - 然后你画那个垃圾。将 getImage()
替换为简单的交替填充,您会发现效果很好:
// https://github.com/KubaO/Whosebugn/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 = QPixmap{1,1}.toImage().format();
QImage image{size, 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();
QPainter{this}.drawImage(QPoint{0, 0}, image);
}
};
int main(int argc, char **argv) {
QApplication app{argc, 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 tabLayout{tabs.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> widgets{{new QLabel{text}, 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();
}
我测试了@KubaOber 提供的最小示例,按照 OP 的建议添加了 mouseMoveEvent() 处理程序,但我无法在我的平台上重现该问题(Qt 5.10.1 - Linux).
在我看来,这可能是一个特定于平台的问题。
Qt 5.9 和 Qt 5.10 之间的 cocoa 平台插件有很多差异,特别是 QCocoaBackingStore 及其刷新( ) 方法 (changelog here).
如果最小的完整示例很好地显示了问题,我建议将其报告给 Qt 的错误跟踪器 https://bugreports.qt.io。
这是你的错误:
void Render3dModel::paintEvent(QPaintEvent *event) {
// Update model and render new image
m_model.calculate();
...
}
您不应将模型更新与绘画事件联系起来。 paint 事件应该绘制模型的当前状态,并且您可能会获得多个后续 and/or 部分 paint 事件,例如揭示 window.
的部分内容
您在应用程序中看到的是部分公开事件,在本例中是 Qt 错误的结果,但通常您可能会收到部分公开事件并且应该准备好处理它们。
我的应用程序将 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 中重建完全相同的代码会导致奇怪的故障。两个帧的部分同时可见。这不是临时效果(例如延迟绘制),因为如果我停止移动鼠标,它实际上会持续显示两帧的部分。下面是一个示例(为清晰起见,even/odd 帧使用交替的灰色和白色背景):
我尝试在 QImage
ringbuffer 中使用 image.copy()
创建深层副本,看看这是否是 destruction/reuse 和 QImage
之间的某个 race-condition 和一些 deferred/delayed 绘画,但这无济于事。
显然 QPainter
的机制在 Qt 5.9 和 Qt 5.10 之间发生了变化,但我无法在文档中找到任何线索。
请指教
更新
玩过几个 window 尺寸后,我得出的结论是,故障以某种方式对应于 upper-left 角与 "S" 对齐的相同尺寸矩形 "Section dimensions"标签。但是,正如您在屏幕截图中看到的那样,白色背景的框架在正确的位置绘制了圆锥体的那一部分。所以图像不是 shifted,而是 clipped 不知何故?!
我试过添加
painter.setClipping(false);
painter.setClipRect(QRect(0, 0, size().width(), size().height()));
但这没有效果。我的 Render3dModel
偶尔会从它的 parents 得到 paintEvent
吗?
更新 2
向 Kuba Ober 下面的最小示例添加 mouseMoveEvent
处理程序,在 MacBook(OS X 10.12.6,Intel HD Graphics 515)上重现该问题:
这可能是 Qt 5.10 中的回归,因为我无法在 Qt 5.9 中重现它吗?我应该提交错误报告吗?
更新 3
这似乎确实是 Qt 5.10 在 OS X 上的回归。已提交错误报告 https://bugreports.qt.io/browse/QTBUG-67998
单线程代码中不能存在竞争条件。没有delayed/deferred绘画之类的东西。到 paintEvent
returns 时,该小部件的绘制已完成。
所见即所得。 getImage()
方法可能 returns 垃圾 - 也许是尺寸过小的图像 - 然后你画那个垃圾。将 getImage()
替换为简单的交替填充,您会发现效果很好:
// https://github.com/KubaO/Whosebugn/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 = QPixmap{1,1}.toImage().format();
QImage image{size, 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();
QPainter{this}.drawImage(QPoint{0, 0}, image);
}
};
int main(int argc, char **argv) {
QApplication app{argc, 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 tabLayout{tabs.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> widgets{{new QLabel{text}, 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();
}
我测试了@KubaOber 提供的最小示例,按照 OP 的建议添加了 mouseMoveEvent() 处理程序,但我无法在我的平台上重现该问题(Qt 5.10.1 - Linux).
在我看来,这可能是一个特定于平台的问题。 Qt 5.9 和 Qt 5.10 之间的 cocoa 平台插件有很多差异,特别是 QCocoaBackingStore 及其刷新( ) 方法 (changelog here).
如果最小的完整示例很好地显示了问题,我建议将其报告给 Qt 的错误跟踪器 https://bugreports.qt.io。
这是你的错误:
void Render3dModel::paintEvent(QPaintEvent *event) {
// Update model and render new image
m_model.calculate();
...
}
您不应将模型更新与绘画事件联系起来。 paint 事件应该绘制模型的当前状态,并且您可能会获得多个后续 and/or 部分 paint 事件,例如揭示 window.
的部分内容您在应用程序中看到的是部分公开事件,在本例中是 Qt 错误的结果,但通常您可能会收到部分公开事件并且应该准备好处理它们。