Qt QImage scaled方法缩放中的问题

news2024/12/25 18:43:51

最近在某些测试中发现,QImage 先按照一定的比例进行缩放,在对QImage对象进行绘制等操作后,使用以下的方式将其恢复到其原来的尺寸。

图像的缩放是这样的:

void ImageBaseWidget::zoomImage(QMouseEvent *event)
{
	if (event->type() == QEvent::MouseButtonPress)
	{
		m_brushPt = event->pos();
	}
	else if (event->type() == QEvent::MouseMove)
	{
		QPointF pt = event->pos() - m_brushPt;
        m_dScale = m_dScale - pt.y() * 0.0032;
        if (m_dScale <= 0.1)
        {
            m_dScale = 0.1;
        }

        QPointF tmp = transWindowPosToPicture(event->pos());
        m_paintPt = event->pos() - tmp * m_dScale;

        DrawTool::ImageShow img = m_img;
        m_show = img.img.scaled(img.size * m_dScale, Qt::KeepAspectRatio, Qt::FastTransformation);
        m_mask = m_maskOri.scaled(img.size * m_dScale, Qt::KeepAspectRatio, Qt::FastTransformation);
        update();

        m_brushPt = event->pos();
	}
	else if (event->type() == QEvent::MouseButtonRelease)
	{
		m_brushPt = QPoint(0, 0);
	}
}

图像的还原是这样的:

DrawTool::ImageShow img = m_img;
m_maskOri = m_mask.scaled(img.size(), Qt::KeepAspectRatio, Qt::FastTransformation);

恢复的过程中发现一个这样的问题:

  • 当原始图像长宽相同,即图像是正方形时,图像能够正常恢复
  • 当原始图像长宽不同,即图像是矩形时,恢复之后的图像要宽高度、要么长度会少 1个像素

经过测试,这样每次都会少一个像素,重复多次操作之后,图像跟原始图像相差会很大。

首先第一个想到的肯定是先去帮助文档看看有没有什么用法是不是用错了。

QImage QImage::scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation) const

Returns a copy of the image scaled to a rectangle defined by the given size according to the given aspectRatioMode and transformMode.

如果只是单纯的看这个方法的说明的话,好像没什么问题。
在这里插入图片描述

Constant                        Value           Description
Qt::IgnoreAspectRatio             0             The size is scaled freely. The aspect ratio is not preserved.

Qt::KeepAspectRatio               1             The size is scaled to a rectangle as large as possible inside a given rectangle, preserving the aspect ratio.

Qt::KeepAspectRatioByExpanding    2             The size is scaled to a rectangle as small as possible outside a given rectangle, preserving the aspect ratio.

然后看了看Qt的源码,发现源码中是这样的。

QImage QImage::scaled(const QSize& s, Qt::AspectRatioMode aspectMode, Qt::TransformationMode mode) const
{
    if (!d) {
        qWarning("QImage::scaled: Image is a null image");
        return QImage();
    }
    if (s.isEmpty())
        return QImage();

    QSize newSize = size();
    newSize.scale(s, aspectMode);
    newSize.rwidth() = qMax(newSize.width(), 1);
    newSize.rheight() = qMax(newSize.height(), 1);
    if (newSize == size())
        return *this;

    QTransform wm = QTransform::fromScale((qreal)newSize.width() / width(), (qreal)newSize.height() / height());
    QImage img = transformed(wm, mode);
    return img;
}

它是首先根据图像的 size 、需要缩放之后的比例以及是否保持长宽比生成一个新的 size,再用这个size和图像的size生成一个转换矩阵,最后根据矩阵转换进行图像缩放。

通过我对这部分代码单步测试,最后发现在执行 newSize.scale(s, aspectMode); 这行代码之后,尺寸已经别缩减了1个像素。

然后追根究底去看看QSize的scale方法。源码如下:

QSize QSize::scaled(const QSize &s, Qt::AspectRatioMode mode) const Q_DECL_NOTHROW
{
    if (mode == Qt::IgnoreAspectRatio || wd == 0 || ht == 0) {
        return s;
    } else {
        bool useHeight;
        qint64 rw = qint64(s.ht) * qint64(wd) / qint64(ht);

        if (mode == Qt::KeepAspectRatio) {
            useHeight = (rw <= s.wd);
        } else { // mode == Qt::KeepAspectRatioByExpanding
            useHeight = (rw >= s.wd);
        }

        if (useHeight) {
            return QSize(rw, s.ht);
        } else {
            return QSize(s.wd,
                         qint32(qint64(s.wd) * qint64(ht) / qint64(wd)));
        }
    }
}

这个方法的主要功能是根据是否保持长宽比的模式,计算出缩放后的size大小。当然如果选择是不保持长宽比,就直接返回。

判断的方式是首先按照现有尺寸的长宽比,和给定size的高,计算出缩放之后的长。根据在给定矩形之内还是之外保持长宽比来判断是否使用缩放后的尺寸的宽。

对这部分代码进行测试,最后发现在 qint64 rw = qint64(s.ht) * qint64(wd) / qint64(ht); 这行代码之后,rw的值和我们预期有了误差。或许你已经发现问题了,因为我们前面进行图像的缩小时,使用缩放比例是根据鼠标在屏幕上的位移计算得来的,这个缩放比是个double类型的数值。

但恢复之后会将double类型的数值强制转换为 qint64 类型。而正是这部分的转换出现了误差。

所以,我根据这两个函数修改了部分代码,实现了放大时按照自己给定的尺寸进行准确的放大而不缺斤少两。修改之后如下:

QImage ImageBaseWidget::imgScaled(const QSize& size, const QImage& img, Qt::AspectRatioMode aspectMode, Qt::TransformationMode mode)
{
    QSize newSize = img.size();

    qint64 rw = qint64(size.height()) * qint64(newSize.width()) / qint64(newSize.height());
	bool useHeight = aspectMode == Qt::KeepAspectRatio ? (rw <= size.width()) : (rw >= size.width());

	if (useHeight)
	{
		newSize = QSize(rw, qint64(newSize.height()));
	}
	else
	{
		newSize = QSize(size.width(), qint32(qint64(size.width()) * qint64(newSize.height()) / qint64(newSize.width())));
	}

    //newSize.scale(size, aspectMode);

    newSize.rwidth() = qMax(size.width(), qMax(newSize.width(), 1));
    newSize.rheight() = qMax(size.height(), qMax(newSize.height(), 1));

    QTransform wm = QTransform::fromScale((qreal)newSize.width() / img.width(), (qreal)newSize.height() / img.height());
    QImage t = img.transformed(wm, mode);
    return t;
}

修改方法也是比较简单的,只是单纯的在计算之后,判断一下调用了 scale 之后的QSize和预期的size的大小,并且选择了较大者。

就在我测试之后并暗暗得意的时候,我突然发现,这不就是 QImage 以忽略长宽比的方式进行缩放的功能,也就是下面这样。

DrawTool::ImageShow img = m_img;
m_maskOri = m_mask.scaled(img.size(), Qt::IgnoreAspectRatio, Qt::FastTransformation);

对比一下,只是一个简单的数值的修改,可比写了一个函数的方法简单多了, 而我,在计算了长宽比之后,还是选择了最初的size,多走了很多路。

这就像,你埋头赶路的时候,一定不要忘了抬头看天。

哎,又是瞎忙的一天。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/421010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

传输层协议——TCP协议

传输层协议——TCP协议认识IP地址TCP/IP的分层管理TCP/IP分层通信示例&#xff08;发送数据包&#xff09;认识IP地址 IP地址属于网络层地址 在计算机通信中&#xff0c;为了识别通信对端&#xff0c;必须有一个类似于地址的识别码进行标识。IP地址用于在连接到网络中的所有主…

利用seaborn、statannotations库绘制显著性标注

如何使用Python-SeabornSeaborn进行显著性统计图表绘制&#xff0c;详细内容如下&#xff1a; Python-Seaborn自定义函数绘制Python-statannotations库添加显著性标注 1、Python-Seaborn 自定义函数绘制 import matplotlib.pylab as plt import numpy as np import seaborn as…

Spring Cloud组件源码之LoadBalancer源码分析

" Spring 到底是春天的来临万物复苏&#xff0c;还是春转夏的干燥又炎热呢&#xff1f;" Spring的来临让JavaEE走向了另一个高度。便捷的开发&#xff0c;完美的生态。物极必反&#xff0c;学习Spring的成本越来越低&#xff0c;导致Java程序员越来越密集&#xff0…

1、Windows下编译并搭建AzerothCore服务端

目录前言一、AzerothCore下载二、mysql安装三、boost安装四、OpenSSL安装五、CMake下载六、CMake编译1 - CMake生成vs项目2 - vs项目设置3 - 生成解决方案4 - 安装AzerothCore5 - 添加账号6 - 修改服务器名称7 - 修改客户端的服务器地址前言 客户端对应版本&#xff1a;魔兽世…

CANopen | 对象字典OD 07 - 创建对象字典变量,变量变化时发送TPDO1,滤波时间200ms

文章目录一、前言二、实验目的三、对象字典OD四、TPDO1数据变化发送&#xff0c;滤波时间200ms4.1、main.c4.2、让CANopen从站进入操作状态4.3、TPDO1的CAN数据包一、前言 该笔记的程序&#xff1a;github 二、实验目的 CANopen从站有一个变量tx_Value&#xff0c;映射到T…

我调用第三方接口遇到的13大坑

前言 在实际工作中&#xff0c;我们经常需要在项目中调用第三方API接口&#xff0c;获取数据&#xff0c;或者上报数据&#xff0c;进行数据交换和通信。 那么&#xff0c;调用第三方API接口会遇到哪些问题&#xff1f;如何解决这些问题呢&#xff1f; 这篇文章就跟大家一起…

ubuntu防火墙命令介绍

ubuntu在开启ufw防火墙前&#xff0c;为了避免与iptables现有规则冲突&#xff0c;建议先清空iptables的所有规则。相关命令如下&#xff1a; iptables -F 更改iptables规则链默认操作命令如下&#xff1a; iptables -P INPUT ACCEPTiptables -P FORWARD ACCEPTiptables -P …

【PyTorch】第一节:张量(Tensor)的定义

作者&#x1f575;️‍♂️&#xff1a;让机器理解语言か 专栏&#x1f387;&#xff1a;PyTorch 描述&#x1f3a8;&#xff1a;PyTorch 是一个基于 Torch 的 Python 开源机器学习库。 寄语&#x1f493;&#xff1a;&#x1f43e;没有白走的路&#xff0c;每一步都算数&#…

云原生网络之微隔离

本博客地址&#xff1a;https://security.blog.csdn.net/article/details/130044619 一、微隔离介绍 1.1、微隔离概念 在主体执行动作时&#xff0c;对主体权限和行为进行判断&#xff0c;最常见的是网络访问控制&#xff0c;也就是零信任网络访问&#xff08;ZTNA&#xff…

TP5 解决如何实现生成并导出Word文档功能

今天连续更新两篇文章&#xff0c;上一篇讲了一下如何生成PDF并导出文件的功能 接下来我们就来拼一拼怎么实现生成并导出word文档的功能 话不多说 我们直接上流程&#xff1a; 1.下载安装phpword插件&#xff1a;composer require phpoffice/phpword 2.安装成功后该插件在我们项…

Linux——高级I/O操作(三)

目录 I/O多路复用 异步I/O I/O多路复用 阻塞型I/O 相对于非阻塞型 I/O 来说&#xff0c;最大的优点就是在设备的资源不可用时&#xff0c;进程主动放弃 CPU&#xff0c;让其他的进程运行&#xff0c;而不用不停地轮询&#xff0c;有助于提高整个系统的效率。但是其缺点也是比…

Sharding-JDBC之水平分表

目录一、简介1.1、垂直分表1.2、水平分表二、maven依赖三、数据库3.1、创建数据库3.2、创建表四、配置&#xff08;二选一&#xff09;4.1、properties配置4.2、yml配置五、实现5.1、实体5.2、持久层5.3、服务层5.4、测试类5.4.1、保存数据5.4.2、查询数据一、简介 1.1、垂直分…

Java入坑之注解和反射

一、注解概念0 1.1基本定义 Java注解是附加在代码中的一些元信息&#xff0c;用于一些工具在编译、运行时进行解析和使用&#xff0c;起到说明、配置的功能 1。它们可以用来标记类、方法、变量、参数和包等 简而言之&#xff0c;注解就是对于代码中某些鲜活个体的贴上去的一张…

企业如何开发自己的小程序

小程序是一种轻量级的应用程序&#xff0c;被广泛用于社交娱乐、电商购物等领域。对于企业而言&#xff0c;开发自己的小程序可以为客户提供更加个性化的服务&#xff0c;提高品牌认知度和用户忠诚度。本文将介绍企业如何开发自己的小程序&#xff0c;并通过一个具体的案例来说…

【CSS】图片底部空白缝隙处理 ( 使用居中对齐 / 顶部对齐 / 底部对齐 | 将行内元素 / 行内块元素转为块级元素 )

文章目录一、图片底部空白缝隙问题二、图片底部空白缝隙问题解决方案一 ( 使用居中对齐 / 顶部对齐 / 底部对齐 )三、图片底部空白缝隙问题解决方案二 ( 将行内元素 / 行内块元素转为块级元素 )一、图片底部空白缝隙问题 在上一篇博客中 , 使用默认的基线对齐 , 会发现 行内块级…

java 利用正则来分析日志(IT枫斗者)

利用正则来分析日志&#xff08;IT枫斗者&#xff09; 环境接口的历史并发数&#xff0c;然而运维并没有做相关的统计&#xff0c;没办法&#xff0c;只能拿到服务器近一个月的 Nginx access 日志&#xff0c;根据正则匹配所有我的接口服务的日志&#xff0c;然后统计每一秒内…

《低代码PaaS驱动集团企业数字化创新白皮书》-平台化加低代码提供破解之道(2)

平台化加低代码提供破解之道 低代码向业务的赋能&#xff1a;以效率和创新为核心&#xff0c;提升组织效率&#xff0c;促进创新&#xff0c;优化体验 通过IDC对大型企业的调研发现&#xff0c;当前拥有100个及以上应用数量的企业已经高达70%&#xff1b;IDC预测 ,2025年&…

〖Python网络爬虫实战⑮〗- pyquery的使用

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付费…

数据结构——队列(C语言实现)

队列的概念与结构 队列是一种特殊的线性结构&#xff0c;数据只能在一端插入&#xff0c;数据也只能在另一端进行删除。插入数据的那一端称之为队尾&#xff0c;插入数据的动作称之为入队。删除数据的那一端称之为队头&#xff0c;删除数据的动作称之为出列。队列遵守的是FIFO…

LeetCode 189.轮转数组

文章目录&#x1f4a1;题目分析&#x1f4a1;解题思路&#x1f6a9;思路1:暴力求解 --- 旋转k次&#x1f514;接口源码&#xff1a;&#x1f6a9;思路2:额外开数组&#x1f514;接口源码&#xff1a;&#x1f6a9;思路3:三段逆置&#x1f4cd;算法设计&#x1f514;接口源码&am…