【Qt】图片绘制不清晰的问题

news/2024/11/30 5:10:29/

背景

实现一个图片浏览器,可以支持放大/缩小查看图片。主要组件如下:

// 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)
{pixmap = QPixmap::fromImage(img);
}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.scale(scale, scale);p.drawPixmap(0,0,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);
}

问题

在这种实现方式下,缩小图片时,图片会变得非常模糊,有非常明显的锯齿问题。
如下图所示,A是Windows自带图片查看器的效果,B是上述实现的效果。可以看出虽然B比A更大,但却更不清晰,有明显的锯齿。

在这里插入图片描述

尝试解决

为了解决这个不清晰的问题,尝试了很多种方案,方案及其实现方法如下:

不scale QPainter,而是在指定区域绘制Pixmap

p.drawPixmap(0,0,pixmap.size().width() * scale, pixmap.size().height * scale, pixmap);

使用QGraphicsView绘制图片

    QPixmap pixmap("/path/to/image.png");QGraphicsScene scene;QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);scene.addItem(item);QGraphicsView view;view.resize(800,600);view.setScene(&scene);// Optionally set view propertiesview.setRenderHint(QPainter::Antialiasing);   // Improve rendering qualityview.setDragMode(QGraphicsView::ScrollHandDrag); // Enable draggingview.setAlignment(Qt::AlignCenter);           // Center the imageview.fitInView(item, Qt::KeepAspectRatio);    // Scale to fit the view// Show the viewview.show();

使用QWebEngineView绘制图片

    QWebEngineView web_view;QString htmlContent = R"(<!DOCTYPE html><html><head><style>body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; }img { max-width: 100%; max-height: 100%; }</style></head><body><img src="/path/to/image.png" alt="Image Not Found"></body></html>)";web_view.setHtml(htmlContent, QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + "/"));web_view.resize(800, 600);web_view.show();

将图片作为texture在QOpenGLWidget中绘制图片

#ifndef OPENGLIMAGE_H
#define OPENGLIMAGE_H#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <memory>class OpenGLImage : public QOpenGLWidget, protected QOpenGLFunctions
{Q_OBJECT
public:explicit OpenGLImage(QWidget *parent = nullptr);~OpenGLImage();QSize minimumSizeHint() const override;QSize sizeHint() const override;void loadImage(QString& path);QMatrix4x4 getViewMatrix() const;QMatrix4x4 getModelMatrix() const;protected:void initializeGL() override;void paintGL() override;void resizeGL(int width, int height) override;void wheelEvent(QWheelEvent *event) override;void mouseMoveEvent(QMouseEvent *event) override;void mousePressEvent(QMouseEvent *event) override;void mouseReleaseEvent(QMouseEvent *event) override;void keyPressEvent(QKeyEvent *event) override;void keyReleaseEvent(QKeyEvent* event) override;private:void setupDefaultShaderProgram();void setupDefaultTransform();void drawImage();void moveImage(const QPointF& cursorPos);void rotateImage(const QPointF& cursorPos);std::unique_ptr<QOpenGLShaderProgram> shaderProgram;std::unique_ptr<QOpenGLTexture> texture;std::unique_ptr<QImage> image;QOpenGLBuffer vbo;QOpenGLVertexArrayObject vao;QOpenGLBuffer ebo;bool isTextureSync;QColor clearColor;float norm_h;QSize viewSize;QVector3D cameraPos;QVector3D imagePos;QVector3D imageAngle;float viewAngle;float focalLength;QPointF lastClickPos;bool isRotMode;
};#endif // OPENGLIMAGE_H
#include "glimageview.h"
#include <vector>
#include <QtMath>
#include <iostream>
#include <QResizeEvent>#define PROGRAM_VERTEX_ATTRIBUTE 0
#define PROGRAM_TEXCOORD_ATTRIBUTE 1#define DEFAULT_CAMERA_POS_X (0.0f)
#define DEFAULT_CAMERA_POS_Y (0.0f)
#define DEFAULT_CAMERA_POS_Z (-2.0f)#define CLIP_NEAR (0.01f)
#define CLIP_FAR (100.0f)#define MIN_FOCAL 1.0f
#define MAX_FOCAL 150.0fOpenGLImage::OpenGLImage(QWidget *parent): QOpenGLWidget(parent),shaderProgram(nullptr),texture(nullptr),image(nullptr),isTextureSync(false),clearColor(Qt::gray),norm_h(-1.0f),viewSize(640,640),ebo(QOpenGLBuffer::Type::IndexBuffer),viewAngle(45.0f),isRotMode(false)
{focalLength = 1/qTan(qDegreesToRadians(viewAngle/2.0f));
}OpenGLImage::~OpenGLImage()
{
}void OpenGLImage::initializeGL()
{initializeOpenGLFunctions();setupDefaultShaderProgram();
}void OpenGLImage::paintGL()
{glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blueF(), clearColor.alphaF());glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);drawImage();
}void OpenGLImage::resizeGL(int width, int height)
{viewSize = QSize(width, height);
}QSize OpenGLImage::minimumSizeHint() const
{int min_h = (int)(320.0f * norm_h);return QSize(320, min_h);
}QSize OpenGLImage::sizeHint() const
{return viewSize;
}void OpenGLImage::wheelEvent(QWheelEvent *event)
{QPoint numDegrees = event->angleDelta() / 8;float degree = (float)numDegrees.y() * -1.0f;degree /= 2.0f;if (viewAngle+degree > MIN_FOCAL && viewAngle+degree < MAX_FOCAL) {viewAngle += degree;focalLength = 1/qTan(qDegreesToRadians(viewAngle/2.0f));}event->accept();update();
}void OpenGLImage::drawImage() {if (image.get() == nullptr) return;glViewport(0, 0, viewSize.width(), viewSize.height());// qDebug() << viewSize.width() << ", " << viewSize.height() << "\n";// setup vertex array objectif (!vao.isCreated()){vao.create();}vao.bind();// setup vertex buffer objectstd::vector<GLfloat> coords;// bottom left;coords.push_back(-1.0f);coords.push_back(-1.0f * norm_h);coords.push_back(0.0f);// tex coordinatecoords.push_back(0.0f);coords.push_back(0.0f);// bottom rightcoords.push_back(1.0f);coords.push_back(-1.0f * norm_h);coords.push_back(0.0f);// tex coordinatecoords.push_back(1.0f);coords.push_back(0.0f);// top rightcoords.push_back(1.0f);coords.push_back(1.0f * norm_h);coords.push_back(0.0f);// tex coordinatecoords.push_back(1.0f);coords.push_back(1.0f);// top leftcoords.push_back(-1.0f);coords.push_back(1.0f * norm_h);coords.push_back(0.0f);// tex coordinatecoords.push_back(0.0f);coords.push_back(1.0f);if (!vbo.isCreated()){vbo.create();}vbo.bind();vbo.allocate(coords.data(), coords.size()*sizeof(GLfloat));// setup vertex element object// [bl, br, tr, tl]static const std::vector<GLuint> indices {0, 1, 2,2, 3, 0};if (!ebo.isCreated()){ebo.create();}ebo.bind();ebo.allocate(indices.data(), indices.size()*sizeof(GLuint));// associate vertex and buffershaderProgram->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);shaderProgram->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);shaderProgram->setAttributeBuffer(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat));shaderProgram->setAttributeBuffer(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat));// assign transform matricesQMatrix4x4 projection; // projection matrxi must update everytime!float ratio = ((float)viewSize.width())/((float)viewSize.height());projection.perspective(viewAngle, ratio, CLIP_NEAR, CLIP_FAR);QMatrix4x4 model = getModelMatrix();model.rotate(imageAngle.x(), 0.0f, 1.0f, 0.0f);model.rotate(imageAngle.y()*-1.0f, 1.0f, 0.0f, 0.0f);shaderProgram->setUniformValue("model", model);QMatrix4x4 viewMat = getViewMatrix();shaderProgram->setUniformValue("view", viewMat);shaderProgram->setUniformValue("projection", projection);// setup textureif (texture.get() == nullptr || !isTextureSync) {QImage& img = *image.get();texture = std::unique_ptr<QOpenGLTexture>(new QOpenGLTexture(img));isTextureSync = true;}texture->bind();glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0 );
}void OpenGLImage::setupDefaultShaderProgram()
{QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);const char *vsrc ="attribute highp vec3 vertex;\n""uniform mediump mat4 model;\n""uniform mediump mat4 view;\n""uniform mediump mat4 projection;\n""\n""attribute mediump vec2 texCoord;\n""varying mediump vec2 texc;\n""void main(void)\n""{\n""    gl_Position = projection * view * model * vec4(vertex, 1.0f);\n""    texc = texCoord;\n""}\n";vshader->compileSourceCode(vsrc);QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);const char *fsrc ="uniform sampler2D texture;\n""varying mediump vec2 texc;\n""void main(void)\n""{\n""    gl_FragColor = texture2D(texture, texc);\n""}\n";fshader->compileSourceCode(fsrc);shaderProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram(this));shaderProgram->addShader(vshader);shaderProgram->addShader(fshader);// assign locations of vertex and texture coordinatesshaderProgram->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE);shaderProgram->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE);shaderProgram->link();shaderProgram->bind();shaderProgram->setUniformValue("texture", 0);
}void OpenGLImage::setupDefaultTransform() {cameraPos = QVector3D(DEFAULT_CAMERA_POS_X, DEFAULT_CAMERA_POS_Y, DEFAULT_CAMERA_POS_Z);imagePos = QVector3D();imageAngle = QVector3D();
}void OpenGLImage::loadImage(QString& path) {QImage* p = new QImage(QImage(path).mirrored());image = std::unique_ptr<QImage>(p);isTextureSync = false;norm_h = (float)((float)image->height()/(float)image->width());int h = (int)((float)viewSize.width()*norm_h);viewSize = QSize(viewSize.width(), h);resize(viewSize);setupDefaultTransform();
}QMatrix4x4 OpenGLImage::getViewMatrix() const {QVector3D up(0.0f, 1.0f, 0.0f);QMatrix4x4 ret;ret.translate(cameraPos);QVector3D center(cameraPos.x(), cameraPos.y(), imagePos.z());ret.lookAt(QVector3D(), center, up);return ret;
}QMatrix4x4 OpenGLImage::getModelMatrix() const {QMatrix4x4 ret;ret.translate(imagePos);return ret;
}void OpenGLImage::mousePressEvent(QMouseEvent *event) {lastClickPos = event->localPos();qDebug() << lastClickPos;
}// movement is weird somehow...
void OpenGLImage::mouseMoveEvent(QMouseEvent *event) {if (isRotMode) {rotateImage(event->localPos());} else {moveImage(event->localPos());}lastClickPos = event->pos();event->accept();update();
}void OpenGLImage::moveImage(const QPointF &cursorPos) {QPointF delta = cursorPos-lastClickPos;float factor = qAbs(imagePos.z()-cameraPos.z()) / focalLength;factor /= (qMax(viewSize.width(), viewSize.height()));factor *= 3.5f;qDebug() << "dx=" << delta.x();qDebug() << "dy=" << delta.y();qDebug() << "L=" << (imagePos.z()-cameraPos.z());qDebug() << "focalLength=" << focalLength;qDebug() << "factor" << factor;delta *= factor;imagePos += QVector3D(delta.x(), -1.0f*delta.y(), 0.0f);
}void OpenGLImage::rotateImage(const QPointF &cursorPos) {QPointF delta = cursorPos-lastClickPos;delta.setX(delta.x() / (qreal)viewSize.width());delta.setX(delta.x() * 180.0f);delta.setY(delta.y() / (qreal)viewSize.height());delta.setY(delta.y() * -180.0f);qDebug() << delta;imageAngle += QVector3D(delta.x(), delta.y(), 0.0f);
}void OpenGLImage::mouseReleaseEvent(QMouseEvent *event) {
}void OpenGLImage::keyPressEvent(QKeyEvent *event) {if (event->key() == Qt::Key_Control) {qDebug() << "ctrl is pressed";isRotMode = true;} else {// call base class method as event is not handled.QOpenGLWidget::keyPressEvent(event);}
}void OpenGLImage::keyReleaseEvent(QKeyEvent *event) {if (event->key() == Qt::Key_Control) {qDebug() << "ctrl is released";isRotMode = false;} else {// call base class method as event is not handled.QOpenGLWidget::keyReleaseEvent(event);}
}

如下图所示,不同方案的效果略有不同,但所有方案都会出现缩小后图片变模糊的问题:
在这里插入图片描述

问题所在

最终在网友们的帮助下,发现了问题所在:这些实现方法在修改图片大小时都会对图片进行压缩。

比如void QPainter::drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source),在指定矩形区域内绘制图片,如果指定的矩形区域比图片本身尺寸小,绘制过程中就会对图片进行压缩,导致图片变得模糊。

如果想要将图片变小的同时,保持图片的清晰度,应该直接使用QPixmap的scaled函数:

p.drawPixmap(0,0,pixmap.scaled(pixmap.size() * scale, Qt::KeepAspectRatio, Qt::SmoothTransformation));

效果如下,左边是新的实现方法的效果,右边是Windows自带的图片查看软件的效果:
在这里插入图片描述

其实我一开始的实现方法不算错,甚至是官方建议的,在QPixmap的文档中提到:

In some cases it can be more beneficial to draw the pixmap to a painter with a scale set rather than scaling the pixmap. This is the case when the painter is for instance based on OpenGL or when the scale factor changes rapidly.

图片查看器其实就会频繁改变scale按照建议就是应该采用修改QPainter的scale的方法,但这种方法确实会导致图片清晰度变低,出现模糊的问题。


http://www.ppmy.cn/news/1551111.html

相关文章

猜一个0到10之间的数字 C#

生成随机数、使用循环和判断比较大小&#xff0c;最后猜出正确的数字 主要是生成随机数&#xff0c;固定步骤。 using System;class Program {static void Main(string[] args){//Random生成随机数的类//new用于创建对象的实例//Random()内可以填入种子&#xff0c;生成伪随机…

docker私有仓库的介绍以及 Docker registry 安装

文章目录 什么是 Docker Registry镜像仓库工作机制常用的镜像仓库快速部署 Docker Registry上传镜像下载镜像部署域名地址的Docker registry 什么是 Docker Registry 镜像仓库&#xff08;Docker Registry&#xff09;负责存储、管理和分发镜像&#xff0c;并且提供了登录认证…

开发需求总结18-vue el-table合计功能, 前几列使用合计求和,最后一列的百分比数据,计算出平均百分比

需求&#xff1a; 在element UI的el-table组件中&#xff0c;可以通过show-summary属性和summary-method属性来实现合计功能。如果需要在合计行中显示特定列的百分比数据&#xff0c;可以在summary-method中自定义合计逻辑。 代码实现&#xff1a; <template><el-ta…

Excel如何设置超出单元格的内容不显示?

如图&#xff0c;在使用EXCEL时经常出现超出单元格显示的情况&#xff1a; 如果想要把超出单元格的部分隐藏&#xff0c;需要进行以下设置&#xff1a; 选中想要设置的单元格&#xff0c;然后点击对齐方式右边的按钮&#xff0c;对齐设置&#xff0c;选择“对齐”选项卡&#…

Hive构建日搜索引擎日志数据分析系统

1.数据预处理 根据自己或者学校系统预制的数据 使用less sogou.txt可查看 wc -l sogou.txt 能够查看总行数 2.数据扩展部分 我的数据位置存放在 /data/bigfiles 点击q退出 将一个文件的内容传递到另一个目录文件下 原数据在 /data/bigfiles ->传递 到/data/workspac…

为什么PHP爬虫抓取失败?解析cURL常见错误原因

豆瓣电影评分作为中国电影市场的重要参考指标&#xff0c;凭借其广泛覆盖的观众反馈和真实评分&#xff0c;成为电影市场推广和策略优化的核心依据之一。通过精准获取这些评分数据&#xff0c;电影制作方和发行方可以更好地理解观众需求&#xff0c;优化宣传策略&#xff0c;并…

Jenkins-基于 SSH 实现 Jenkins 分布式

工作原理 主节点与从节点通信&#xff1a; 主节点通过 SSH 登录到从节点&#xff0c;并启动 Jenkins Agent 程序&#xff08;Agent.jar&#xff09;Agent 程序通过主节点指定的端口&#xff08;默认为 50000&#xff09;与主节点保持通信 任务分发&#xff1a; 主节点根据任务配…

GaussianDreamer: Fast Generation from Text to 3D Gaussians——点云论文阅读(11)

此内容是论文总结&#xff0c;重点看思路&#xff01;&#xff01; 文章概述 本文提出了一种快速从文本生成3D资产的新方法&#xff0c;通过结合3D高斯点表示、3D扩散模型和2D扩散模型的优势&#xff0c;实现了高效生成。该方法利用3D扩散模型生成初始几何&#xff0c;通过噪声…