QML的图片加载,内存优化研究(一)
QML加载图片的两个控件
通常,qml加载图片的话,会用到Image和AnimatedImage这两种控件,本期只探讨一下Image加载图片,AnimatedImage下期进行探讨;
Image控件及其相关属性
asynchronous : bool //是否异步加载
autoTransform : bool //此属性保存图像是否应自动应用图像转换元数据。默认为 false。
cache : bool //是否使用缓存
fillMode : enumeration
horizontalAlignment : enumeration
mipmap : bool //此属性保存图像在缩放或变换时是否使用 mipmap 过滤,
mirror : bool //水平反转
paintedHeight : real
paintedWidth : real
progress : real //加载进度
smooth : bool //是否平滑过滤
source : url //路径,可是本地路径,也可是网络路径,也可使qrc资源路径
sourceSize : QSize //此属性保存全帧图像的缩放宽度和高度,优化内存时会有用
status : enumeration //图像加载的状态
verticalAlignment : enumeration
以上只是一些基础属性,我从官网搬过来简单翻译了一下,搬运的原因是确实有两个属性还蛮有用,并非是重点,每个人都是大自然的搬运工,如果要了解基础知识,看别人博客,不如去去官网看看官方文档,以免一个简单的问题,经过多个人传达,最后只能一知半解,以下是官网链接,如果不习惯看英文,就下载个浏览器翻译插件,比某些搬运工翻译的好多了。
参考链接: 官网.
先附上小弟的测试代码✌🏻:https://github.com/YQF66666666/QML_Project.测试的话,注意要把路径改成自己本地路径
Image加载图片的内存问题
如果不考虑内存,以及性能问题,以下内容就都可以不看了,因为以下都是作者在使用Image中实际掉好多次坑后总结的经验(笔者系统是MAC 12.4,Windows 10也做过测试,结论相同)。废话不多说,首先我们使用Image控件简单加载一张本地图片和一个空窗口的内存占用,
代码一:
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
}
可以看到,只打开一个window窗口内存占43mb,🐶!!!
代码二:
然后我们再尝试加载一个2MB图片看看效果
Image {
id: _image
width: 300
height: 300
anchors.centerIn: parent
smooth: true
//这个路径就是一个本地的全路径前面加了file://而已。···如果测试的话输入你自己的路径就可以
source: "file:///···/worker/123.jpg"
//sourceSize: Qt.size(imageWidth,imageHeight)
antialiasing: true
}
哇!🤩惊不惊喜,意不意外,(╯‵□′)╯︵┻━┻
加载了个2mb的图片,给我搞出了60mb的内存,简直是叔叔能忍,婶婶也不能忍,然后再看看它的官方文档sourceSize 的属性介绍:Unlike the width and height properties, which scale the painting of the image, this property sets the maximum number of pixels stored for the loaded image so that large images do not use more memory than necessary. !!!好的,我们开搞。
代码三:
{
···
sourceSize: Qt.size(300,300)
···
}
what?加了一个这个内存优化,内存比一个空窗口还小?(╯‵□′)╯︵┻━┻
👌🏻你以为到此结束了吗,hhh,并没有,接下来,我们给图片加个圆角,以下代码是从网上随便找了找贴过来的,我们看一下内存。
代码四:
···
Rectangle{
id : rect
width: 300
height: 300
anchors.centerIn: parent
Image {
id: image
width: 300
height: 300
anchors.centerIn: parent
smooth: true
visible: false
anchors.fill: parent
source: "file:///···/worker/123.jpg"
sourceSize: Qt.size(300,300)
antialiasing: true
}
Rectangle {
id: imageRect
color: "#FFFFFF"
anchors.fill: parent
radius: 10
visible: false
antialiasing: true
smooth: true
}
OpacityMask {
id: imageOpcityMask
anchors.fill: image
source: image
maskSource: imageRect
visible: true
antialiasing: true
}
}
···
有没有搞错,大哥,我就是要加个圆角,结果你又给我多搞出这么多mb内存,确定不是在搞我心态?
代码五:
以下代码有参考这个兄弟,这是这个兄弟的: 原文链接.
就是简单来说,自己继承自QQuickPaintedItem,重写了paint方法,注册进qml 里面,并且这样子的抗锯齿效果要比上面👆🏻的效果好。
关键代码:
void ImagePaint::paint(QPainter *painter)
{
QPixmap pixmap(m_source);
QRect rect(0,0,static_cast<int>(width()),static_cast<int>(height()));
QPainterPath path;
painter->setPen(QColor(208,208,208));
painter->setBrush(Qt::white);
painter->setRenderHint(QPainter::Antialiasing);
path.addRoundedRect(rect, m_radius, m_radius);
painter->setClipPath(path);
painter->drawEllipse(rect);
//SmoothTransformation平滑处理。
pixmap = pixmap.scaled(QSize(width() , height()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
painter->drawPixmap(QRect(0, 0, static_cast<int>(width()), static_cast<int>(height())), pixmap);
painter->restore();
}
来看看内存!!!,不错不错,这才是我们想要的内存效果。
注意!!!,这样做仅限于可加载本地,或者资源当中的图片,不可加载网络图片,以及没有Image的一些带的功能,不过,如果想要,我们可以通过C++自己实现它。
通过源码来分析
//源码路径:
Qt5.12.12/5.12.12/Src/qtdeclarative/src/quick/items/qquickpainteditem.cpp
/*!
\class QQuickPaintedItem
\brief The QQuickPaintedItem class provides a way to use the QPainter API in the
QML Scene Graph.
\inmodule QtQuick
The QQuickPaintedItem makes it possible to use the QPainter API with the
QML Scene Graph. It sets up a textured rectangle in the Scene Graph and
uses a QPainter to paint onto the texture. The render target can be either
a QImage or, when OpenGL is in use, a QOpenGLFramebufferObject. When the
render target is a QImage, QPainter first renders into the image then the
content is uploaded to the texture. When a QOpenGLFramebufferObject is
used, QPainter paints directly onto the texture. Call update() to trigger a
repaint.
To enable QPainter to do anti-aliased rendering, use setAntialiasing().
To write your own painted item, you first create a subclass of
QQuickPaintedItem, and then start by implementing its only pure virtual
public function: paint(), which implements the actual painting. The
painting will be inside the rectangle spanning from 0,0 to
width(),height().
\note It important to understand the performance implications such items
can incur. See QQuickPaintedItem::RenderTarget and
QQuickPaintedItem::renderTarget.
*/
/*!
\enum QQuickPaintedItem::RenderTarget
This enum describes QQuickPaintedItem's render targets. The render target is the
surface QPainter paints onto before the item is rendered on screen.
\value Image The default; QPainter paints into a QImage using the raster paint engine.
The image's content needs to be uploaded to graphics memory afterward, this operation
can potentially be slow if the item is large. This render target allows high quality
anti-aliasing and fast item resizing.
\value FramebufferObject QPainter paints into a QOpenGLFramebufferObject using the GL
paint engine. Painting can be faster as no texture upload is required, but anti-aliasing
quality is not as good as if using an image. This render target allows faster rendering
in some cases, but you should avoid using it if the item is resized often.
\value InvertedYFramebufferObject Exactly as for FramebufferObject above, except once
the painting is done, prior to rendering the painted image is flipped about the
x-axis so that the top-most pixels are now at the bottom. Since this is done with the
OpenGL texture coordinates it is a much faster way to achieve this effect than using a
painter transform.
\sa setRenderTarget()
*/
/*!
\enum QQuickPaintedItem::PerformanceHint
This enum describes flags that you can enable to improve rendering
performance in QQuickPaintedItem. By default, none of these flags are set.
\value FastFBOResizing Resizing an FBO can be a costly operation on a few
OpenGL driver implementations. To work around this, one can set this flag
to let the QQuickPaintedItem allocate one large framebuffer object and
instead draw into a subregion of it. This saves the resize at the cost of
using more memory. Please note that this is not a common problem.
*/
简单来说,我的理解就是QQuickPaintedItem 类提供了一种在 QML 场景图中使用 QPainter API 的方法。并且抗锯齿效果要更好,但是在一些情况下会绘制的慢一点,再多就不乱说了,毕竟源码面前了无秘密,而且源码中的注释写的又足够好。
然后通过阅读QML的Image源码不难发现,qml 的Image 提供了相对复杂的功能,包括图片的缓存,加载,优化。但是实践中我们很多时候并不需要如此复杂的功能,我们需要简单且轻量、并且足够小的内存占用🐶!!的控件来实现我们的应用。
下回有时间将分享一下GIF动态图的加载过程中遇到的各种神坑问题!!!
————本文严禁转载————