> 文章列表 > Qt QImage scaled方法缩放中的问题

Qt QImage scaled方法缩放中的问题

Qt QImage scaled方法缩放中的问题

最近在某些测试中发现,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) constReturns 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::KeepAspectRatioByExpandinguseHeight = (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,多走了很多路。

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

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