引入问题
我这里有一张图片,它是在是太大了以至于我将其设置到QLabel
中,我的屏幕完全装不下:
你可以看到,我的屏幕可能只装下了整张图片的四分之一。
上面的图片,我使用了如下的Qt代码对其进行展示:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QImage image(QString(":/images/5.jpg"));
QLabel w;
w.setFixedSize(image.size());
w.setPixmap(QPixmap::fromImage(image));
w.show();
return app.exec();
}
我就想了,既然图片太大,那我们缩小不就完了吗?问题是怎么缩小呢?猜测Qt中有相应的缩放接口。
果不其然,我在Qt对于QImage
的帮助文档中找到了一个名为scaled()
的函数。
QImage::scaled()
QImage QImage::scaled(
int width,
int height,
Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio,
Qt::TransformationMode transformMode = Qt::FastTransformation
) const;
Qt帮助文档中说,这个函数返回根据后面两个名字很长的参数,返回一个原本的QImage被压缩到长为height,宽为width的矩形中。
我不太懂啊,所以先试一下在说,先不用后面两个名字很长的参数,看看胡乱指定前面两个参数看看结果是什么样的先。
于是我在原本的代码中添加了这样一行:
image = image.scaled(1000, 200);
虽然我做到了缩小图片,让图片完全展示在屏幕里。但是这人都被压扁了!图片的缩放比例明显不正确,我需要的是等比例缩放,难道我还得自己定义计算这个等比例缩放的值?
所以我改善了原本随意设置宽和高的做法,转而通过采集原有图片的大小,然后等比例缩小这个大小从而达到缩放的目的:
QImage image("/path/to/your/image");
QSize size = image.size();
size = size / 5;
image = image.scaled(size.width(), size.height());
这样子缩放的图片展示的效果如下:
这下终于有个人样了,不是被“压扁的”了。
我在了解缩放功能之前,使用各种PDF阅读器的时候,经常会有缩放100%,缩放200%这样的数字出现,我之前一直不知道是什么意思。
ε=(´ο`*)))唉,我现在似乎理解了,莫非就是放缩后的size / 原本的size
得到的一个值?对于我们上面这个例子,这个值是1 / 5
也就是20%。
那么我们,后续只需要定义一个缩放因子zoomFactor
,将图片原本的size乘以这个因子,然后在使用scaled()
对图片进行缩放操作就可以了。
等等,其实我们还有一个问题没有解决,那么就是scaled()
后面两个参数有什么作用呢?
我们都学了一半了,再一半也没有什么难度的,就看看,说不定这个特性哪天就遇上了呢?
(1)Qt::AspectRatioMode
Qt::IgnoreAspectRatio
:大小可以自由缩放,不保留纵横比。这个是scaled()
默认指定的。Qt::KeepAspectRatio
:大小将缩放给定矩形内尽可能大的矩形,同时保持纵横比。你可以把它想象成一个本来非常大的图片,然后你指定了一个非常小的矩形,它逐渐等比例缩小,然后最终完全落在你给定的矩形之内,但是它不保证这个图片将你给定的矩形整个占满。Qt::KeepAspectRatioByExpanding
:大小将缩放为给定矩形之外尽可能小,同时保持横纵比。你可以把它想象成一个本来非常大的图片,然后你指定了一个非常小的矩形,它逐渐等比例缩小,然后最终达到一种情况:它如果在等比例缩小它就填不满你给定的这个矩形了。
(2)Qt::TransformationMode
Qt::FastTransformation
:转换将快速执行,没有平滑。Qt::SmoothTransformation
:生成的图像使用双线性筛选进行转换。
说真的,我也不知道这个转换模式平不平滑到底是啥意思?这里就先不管了。
实战演练
现在,我们有这样一个需求:我们前面不是说了因为屏幕装不下图片所以才需要进行缩放吗?现在我告诉你,屏幕装不下整个图片是我故意这么干的,因为我想要细致地,额,欣赏图片(嗯,没错)。但现在,图片只展示了左上角的区域,有没有办法把图片的中间区域完全展示在窗口上?
没有什么目的,我就只是单纯有这个需求而已,经常用pdf阅读器的同志们都知道,有的时候需要放大pdf以看得更清楚,然后可能会移动pdf页面查看图片不同区域的细节是吧。很正常的需求。怎么解决?
于是我完成了如下的代码,用来展示我想欣赏的区域:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 定义要展示区域的大小
QSize size(600, 600);
// 中心区域的展示画布
QPixmap pix(size);
// 原图
QImage image(QString(":/images/5.jpg"));
QRect rect = image.rect();
QRect srcRect(rect.center().x() - size.width() / 2,
rect.center().y() - size.height() / 2,
size.width(), size.height()); // 指定中心的矩形
QPainter painter(&pix);
painter.drawImage(QRect(QPoint(0, 0), size), image, srcRect);
QLabel w;
w.setFixedSize(size);
w.setPixmap(pix);
w.show();
return app.exec();
}
它的效果如下:
但是这串代码好像和缩放没有什么关系吧,啊喂!别着急,虽然我们将图片的中心区域600*600的图片展示了出来,但是现在这个情况就是,我们观察的太细致了以至于我们没有看到全貌,所以我们这时候就需要进行缩放,从局部到全局:
于是我们再次对图片进行缩放操作,这次的缩放比例先设置为75%:
image = image.scaled(image.width() * 0.75, image.height(), Qt::KeepAspectRatio);
新的效果如下:
还是不够全局,于是我将缩放比例调整到0.4:
这个效果是不是既有全局又有局部呢?
如果你想要看左上角一点,那么你就可以将srcRect
的中心点稍微往左上角移一下。如果你想要继续局部观察,那么你就可以将缩放因子进行放大,或者全局观察,将缩放因子缩小。
于是呢,你们从上面得出了什么结论呢?我就得出了这样的结论:
- 在窗口大小固定的情况下,我们可以通过调整drawImage中srcRect的中心点,达到展示图片不同的区域的效果。这个中心点的位置如果可以和鼠标事件相结合,那么就可以实现鼠标移动图片的效果。
- 在窗口大小固定的情况下,中心点如果固定,我们可以通过调整缩放因子的大小,从而实现从图片的某个位置缩放图片的效果。
这是什么?这不就图片查看器吗?上面那几串代码就可以作为图片查看器的核心代码封装起来,将其中的缩放因子搞成可以通过滚动调整的变量,中心点可以通过鼠标事件进行移动,再加上一些限制移动的逻辑,再添加一些旋转图片的按钮,甚至裁剪图片就是也可以做到(本质就是将选中的区域拿出来)。剩下的不就是一些额外业务逻辑的搭建了吗?
总结
我们从简单的缩放入手,逐步了解如何对图片进行缩放。然后实战演练一个查看图片的需求,推导出了实现图片查看器的核心代码,最终探讨了一下利用这串核心代码进行功能扩展的可能。
希望这篇文章对你了解图片的缩放有帮助。
非常简陋的代码实现
基于上述实验结论,我打算写一个图片查看器。
这个实现可以说非常简陋,只能说是能用,但绝对算不上好用,要想达到好用的程度可能需要我们往坐标计算上来努力(个人猜想)。问题主要出现在移动图片功能很奇怪,缩放功能只能往左上缩。
也许我们可以借助QAbstractScrollArea来辅助我们实现缩放功能,具体如何实现我还在研究中。
这里也是给出我非常简陋的代码实现,供大家参考:
#include <QWidget>
class ImageViewer : public QWidget
{
Q_OBJECT
public:
ImageViewer(const QString& filename, QWidget *parent = nullptr);
~ImageViewer();
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
void paintEvent(QPaintEvent *event);
private:
bool pressed = false;
QImage image;
QImage tempImage;
QPoint focusPos;
QPoint lastPos;
qreal zoomFactor;
};
QWidget::mouseMoveEvent(event);
}
void ImageViewer::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
pressed = false;
event->accept();
}
QCursor cursor;
cursor.setShape(Qt::ArrowCursor);
QApplication::setOverrideCursor(cursor);
QWidget::mouseReleaseEvent(event);
}
void ImageViewer::wheelEvent(QWheelEvent *event)
{
if (event->angleDelta().y() > 0) {
zoomFactor = qMin(zoomFactor + 0.1, 10.0);
event->accept();
}
else {
zoomFactor = qMax(zoomFactor - 0.1, 0.45);
event->accept();
}
update();
QWidget::wheelEvent(event);
}
void ImageViewer::paintEvent(QPaintEvent *event)
{
QImage tempImage = image.scaled(image.size() * zoomFactor, Qt::KeepAspectRatioByExpanding);
QRect srcRect(focusPos.x() - size().width() / 2,
focusPos.y() - size().height() / 2,
size().width(), size().height());
QPainter painter(this);
painter.drawImage(QRect(QPoint(0,0), size()), tempImage, srcRect);
event->accept();
QWidget::paintEvent(event);
}