我使用QPainterdrawPixmap()
绘制了一幅图像,结果如下图B所示。
然后,我在 Windows 提供的图像查看器程序中打开同一张图片,并将其放大得更小,如下图A所示。
明显B比A大,但是没有A清晰,B有明显的混叠。
我尝试了setRenderHint(QPainter::Antialiasing)
一下setRenderHint(QPainter::SmoothPixmapTransform)
,有一定的效果,但不足以解决问题。
我尝试了不同的方式来显示同一幅图像(OpenGL、webEngine、QPainter.drawPixmap、graphicsView),结果各不相同,但它们的质量都低于 Windows 内置图像查看器。
我不认为这是图像清晰度的问题。当我使用 drawPixmap() 绘制图像并调用 QPainter 的 scale() 方法将其放大时,我可以看到图像的细节。
有人知道如何在 Qt 中绘制与内置 Windows 软件清晰度相同的图像吗?即使我缩小图像,我也想避免混叠问题。
- 编辑 -
最小可重现演示:
// canvaswidget.h
#ifndef CANVASWIDGET_H
#define CANVASWIDGET_H
#include <QWidget>
class CanvasWidget : public QWidget
{
Q_OBJECT
public:
explicit CanvasWidget(QImage img, QWidget *parent = nullptr);
void zoomIn();
void zoomOut();
signals:
protected:
QSize sizeHint();
void paintEvent(QPaintEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
qreal scale;
QPixmap pixmap;
};
#endif // CANVASWIDGET_H
// canvaswidget.cpp
#include "canvaswidget.h"
#include <QWheelEvent>
#include <QPainter>
#include <QPixmap>
CanvasWidget::CanvasWidget(QImage img, QWidget *parent)
: QWidget{parent}, scale(1.0)
{
// make sure high resolution source
pixmap = QPixmap::fromImage(img.scaled(img.size() * 10, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
void CanvasWidget::zoomIn() {
scale = fmin(scale + 0.1, 10);
update();
}
void CanvasWidget::zoomOut() {
scale = fmax(scale - 0.1, 0.1);
update();
}
void CanvasWidget::paintEvent(QPaintEvent *event) {
if(!pixmap) {
return QWidget::paintEvent(event);
}
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawPixmap(0,0,width() * scale,height() * scale, pixmap); // draw image
}
void CanvasWidget::wheelEvent(QWheelEvent *event)
{
if(event->modifiers() == Qt::ControlModifier) {
QPointF delta = event->angleDelta();
int v_delta = delta.y();
if(v_delta > 0) {
zoomIn();
} else {
zoomOut();
}
update();
adjustSize();
} else {
QWidget::wheelEvent(event);
}
}
QSize CanvasWidget::sizeHint()
{
return QSize(800,800);
}
下图左侧为本次演示的结果,右侧为Windows内置的图像查看器。
--- 编辑 2 ---
这是我在演示中使用的图像,它的格式是 PNG QImage::Format_ARGB32
,大小是QSize(1541, 1188)
25KB
造成您的问题的原因有两个:
缩放这样的锯齿图像通常会产生问题,即使缩放因子更接近 1(高于或低于),特别是当因子不是基于 2 的幂时。
简单的双线性插值将使这些伪影更加明显,因为其中一些“线”(仅由一个像素组成)将在下采样因子大于二时被完全忽略。
文档中没有关于这方面的警告,但我能够找到一个可靠的来源,来自 Allan Sandfeld Jensen(现任 Qt WebEngine 高级经理兼首席工程师)的话:
除了自行实现 QPixmap 转换的平台/配置外,QPixmap
QImage::smoothScaled()
在使用该Qt::SmoothTransformation
模式时确实会内部依赖。这是通过使用上面写的“框缩放”实现的,其概念类似,但使用源附近像素的较大“框”来计算目标每个缩小像素的值。
使用 OpenGL 的尝试没有奏效,因为在这种情况下,使用
SmoothPixmapTransform
画家的渲染提示是不够的(它确实是一个提示)。还必须正确配置表面,以便它明确设置多重采样时使用的每个像素的样本数(这是用于减少混叠的);请参阅此相关文章的所有答案。QtWebEngine 的部分故障可能取决于 Chromium 中设置的默认缩放算法(这也取决于 Qt 中使用的实际 Chromium 版本)。请注意,CSS 提供了该属性
image-rendering
,这可能会改善这种情况(不过,我还没有使用 QtWebEngine 对其进行测试)。QGraphicsView 方法的结果将是相同的,因为它的
paint()
功能依赖于所做的缩放drawPixmap()
。正如您已经发现的,对于此类图像和缩放因子,唯一合适的解决方案是使用
QPixmap::scaled()
,但请注意上述转换的性能问题QImage
:不仅 QPixmap 需要转换为 QImage 才能缩放,而且还必须转换回 QPixmap 才能进行渲染。这些转换相当广泛,特别是对于大型图像。
考虑到这一点,人们可能会倾向于只使用 QImage 作为源,然后仅使用源 QImage
drawImage()
的 进行调用scaled()
,但请记住,当 QPainter 用于 QPixmap 或 QWidget 时,无论如何drawImage()
都会将QImage 转换为 QPixmap。该选项可能是一种改进,但仍然不是最佳选择。更合适的解决方案是实际缩放图像到所需的大小,但仅在调整大小时才这样做;因为您已经在构造函数中获得了 QImage,所以这样更好:
scaled()
,并转换为 QPixmap;resizeEvent()
并调用上述函数;paintEvent()
缓存的 QPixmap 是否有效(如果无效则调用该函数),然后绘制它;或者,使像素图无效,
resizeEvent()
并为缩放的像素图创建一个函数获取器,如果该函数无效,则重新生成它。最后,正如评论中已经指出的那样,在构造函数中创建源的放大图像是完全不合适的:
paintEvent()
调用中都这样做,这是经常发生的事情;我的问题在某种意义上已经得到解决,让我总结一下,希望它可以帮助其他像我一样陷入困境的人。
我打算实现一个支持放大和缩小的图片查看器,我使用的缩小方法如下:
使用这种方法时,图像在缩小时会变得模糊。我尝试了几种方法,例如:• 在绘制图像之前缩放 QPainter:
painter.scale(scale, scale);
• 使用 显示图像QGraphicsView
• 使用QWebEngineView
显示图像在
QGraphicsView
和中QWebEngineView
,图像随视图调整大小。但是,当视图调整为较小尺寸时,图像仍然变得模糊。我甚至尝试将图像绘制为纹理,
QOpenGLWidget
但当调整到较小尺寸时图像仍然显得模糊。最后,我意识到这些方法在调整容器大小时会无意中缩小图像,导致其变得模糊。
我的实现不能被认为是“错误的”,它只是不符合我的要求。
Qt Doc说:
如果您想在缩小的同时保持图像的质量,您应该按照 Christian 所说的实施以下方法。