【Qt】png和jpg格式的图片(二)
[TOC]
背景
在上篇文章 png和jpg格式图片 中笔者就 jpg
和 png
两种格式进行了说明,但是关于 Qt 打开改后缀文件名之后图片的问题依然没有说明。要探究Qt为何不能打开改了后缀的图片文件,这个还是得从多方面去定位。前文说了,通过三种方式设置了 QLabel
的图片。
场景复现
这里还是先说明一下设置不成功的场景如何复现:
- 找一张
.jpg
的图片,修改后缀也就是文件属性为.png
- 通过以下三种方式中的任意一种去设置
QLabel
为图片。Qt 版本是
5.9
编译器试了
MSVC
和mingw
都不好使。
QLabel
设置图片方法
- 通过
QPixmap
设置QLabel
的图片
1 |
|
- 通过
QImage
设置QLabel
的图片
1 |
|
- 通过
QLabel.setStyleSheet()
的图片
1 |
|
当然还有第四种方法,通过 QSvgRenderer
设置:
1 |
|
场景是描述完了,可以动手尝试了
操作
接着就是去查看几种实现方式中的源码是如何是设置的了,这里先猜一下结论
就是
png
和jpg
格式的算法不同,格式问题导致的读取的算法不一致,因此Qt内部实现的读取图片的算法只能根据图片文件的后缀所对应的算法去读取算法,而setStyleSheet
算法也是基于这么一个逻辑,因此三种读取方式都不成功。关于图片算法的问题,在上篇文章 png和jpg格式图片 中也略微提到,这个我们不深做研究,只需知道
jpg
和png
不是同一种算法,也不通用即可。
初步验证
因为上述几种步骤笔者都做过尝试,因此在验证过程中我们不纠结于使用哪一种方式,直接看结果。
- 那如何去验证呢,我们还是看代码,这次我们在 Qt 的
qrc
文件中,去掉图片的后缀名,不带后缀属性去读取图片看看其是否可以读取成功。
运行结果: QLabel
读取成功
如上图所示,在 qrc
文件中去掉图片的后缀,读取图片设置到 QLabel
依然是成功的。
- 我们接着操作,在代码中修改图片后缀为
png
,看看这次能不能读取成功。
运行结果:读取失败
如上图所示,添加后缀后反而还展示不成功了。
初步验证的结果
这就基本上说明了:
在
Qt
的内部有很大的几率是通过文件的后缀去判断调用哪个图片读取算法的。也就是说,当你人为的修改了png->jpg
时,在Qt
中就会出现设置图片失败的问题。目前看来,在代码没有问题,但是图片设置后不显示的情况下,最好的方式是,就是去掉图片的后缀,让Qt
自己去判断调用哪个算法读取图片。
源码分析
这里有关于 Qt
图片 I\O
的描述可以看看Qt帮助手册中关于图像文件读写的文档。
源码部分,我们只需查看两个Qt
类就行,个人感觉看一个就知道了。两者识别图片的算法应该是一致的。包括 setStyleSheet()
接口中设置图片的接口应该都是一致的。
- QPixmap依赖于所在的平台的绘图引擎,故例如反锯齿等一些效果在不同的平台上可能会有不同的显示效果,QImage使用Qt自身的绘图引擎,可在不同平台上具有相同的显示效果
- 目前的Qt会把QPixmap都存储在graphics memory中,QImage是存储在客户端的,是独立于硬件的。在X11, Mac 以及 Symbian平台上,QPixmap 是存储在服务器端,而QImage则是存储在客户端,在Windows平台上,QPixmap和QImage都是存储在客户端,并不使用任何的GDI资源。
- 由于QImage是独立于硬件的,也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI线程中处理,使用这一方式可以很大幅度提高UI响应速度。
QPixmap
查询到QPixmap的源文件,一般存放在 Qt
安装目录下 ${安装目录}\5.9.9\Src\qtbase\src\gui\image
,文件名为 qpixmap.h qpixmap.cpp
先看一下 QPixmap
的 构造函数中读取图片文件的方法。
-
QPixmap::QPixmap(const QString& fileName, const char *format, Qt::ImageConversionFlags flags)
的源码1
2
3
4
5
6
7
8
9QPixmap::QPixmap(const QString& fileName, const char *format, Qt::ImageConversionFlags flags)
: QPaintDevice()
{
doInit(0, 0, QPlatformPixmap::PixmapType);
if (!qt_pixmap_thread_test())
return;
load(fileName, format, flags); // 这里看到调用了load()的接口,接着查看load是如何实现的
} -
load()
函数源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79/*!
Loads a pixmap from the file with the given \a fileName. Returns
true if the pixmap was successfully loaded; otherwise invalidates
the pixmap and returns \c false.
The loader attempts to read the pixmap using the specified \a
format. If the \a format is not specified (which is the default),
the loader probes the file for a header to guess the file format.
The file name can either refer to an actual file on disk or to one
of the application's embedded resources. See the
\l{resources.html}{Resource System} overview for details on how to
embed pixmaps and other resource files in the application's
executable.
If the data needs to be modified to fit in a lower-resolution
result (e.g. converting from 32-bit to 8-bit), use the \a flags to
control the conversion.
Note that QPixmaps are automatically added to the QPixmapCache
when loaded from a file; the key used is internal and can not
be acquired.
\sa loadFromData(), {QPixmap#Reading and Writing Image
Files}{Reading and Writing Image Files}
*/
/* 翻译过来就是
从给定的文件名的文件中加载一个像素图。如果pixmap成功加载,则为True;否则返回无效pixmap并返回\c false。
加载器尝试使用指定的\a读取pixmap格式。如果没有指定\a格式(这是默认的),
加载器探测文件的头,以猜测文件的格式。
文件名可以指向磁盘上的实际文件,也可以指向磁盘上的实际文件应用程序的嵌入式资源。看到\l{resources.html}{Resource System}概述如何嵌入pixmap和其他资源文件在应用程序的可执行文件。
如果数据需要修改以适应低分辨率结果(例如从32位转换到8位),使用\a标志来控制转换。
注意,qpixmap会自动添加到QPixmapCache中
当从文件加载时;使用的密钥是内部的,不能被收购。
\sa loadFromData(), {QPixmap#读写图像读写图像文件}
*/
// loadFromdata的源码我也补充到了文末
bool QPixmap::load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags)
{
if (!fileName.isEmpty()) {
QFileInfo info(fileName);
// Note: If no extension is provided, we try to match the
// file against known plugin extensions
if (info.completeSuffix().isEmpty() || info.exists()) {
QString key = QLatin1String("qt_pixmap")
% info.absoluteFilePath()
% HexString<uint>(info.lastModified().toSecsSinceEpoch())
% HexString<quint64>(info.size())
% HexString<uint>(data ? data->pixelType() : QPlatformPixmap::PixmapType);
if (QPixmapCache::find(key, this))
return true;
data = QPlatformPixmap::create(0, 0, data ? data->pixelType() : QPlatformPixmap::PixmapType);
if (data->fromFile(fileName, format, flags)) {
QPixmapCache::insert(key, *this);
return true;
}
}
}
if (!isNull()) {
if (isQBitmap())
*this = QBitmap();
else
data.reset();
}
return false;
}
一看源码是不是就清晰多了。在 load()
函数的实现中:①判断文件名是不是为空;②不为空时,首先就是读取文件的后缀。这里我们可以细细查一下第二个 if
判断中 QString
类型的 key
到底是进行了一个什么操作。
1 |
|
上述的 HexString<type>
就是 ASCII 的数组形式,16进制
的数组。上述代码中的 %
操作如果不是求余的话会是什么呢。如果是求余,QString
会报错才是呀???
这里最终还是通过设置和查看 QPixmap
的内置变量 data
中的标志去查看当前文件是不是图片,如果是图片,则会修改当前的 QPixmap
指针,如果不是就会返回 false。
1 |
|
这里补充一个QPixmapCache
的demo:
1 |
|
最终判断当前文件是不是图片应该是在 data->fromFile()
中实现的。
这个data
的定义:
1 |
|
未完成这篇文章还在编辑中
补充
补充一下
load()
的函数有必要读一下。
QPixmap::loadFromData()
的源码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*!
\fn bool QPixmap::loadFromData(const uchar *data, uint len, const char *format, Qt::ImageConversionFlags flags)
Loads a pixmap from the \a len first bytes of the given binary \a
data. Returns \c true if the pixmap was loaded successfully;
otherwise invalidates the pixmap and returns \c false.
The loader attempts to read the pixmap using the specified \a
format. If the \a format is not specified (which is the default),
the loader probes the file for a header to guess the file format.
If the data needs to be modified to fit in a lower-resolution
result (e.g. converting from 32-bit to 8-bit), use the \a flags to
control the conversion.
\sa load(), {QPixmap#Reading and Writing Image Files}{Reading and
Writing Image Files}
*/
bool QPixmap::loadFromData(const uchar *buf, uint len, const char *format, Qt::ImageConversionFlags flags)
{
if (len == 0 || buf == 0) {
data.reset();
return false;
}
data = QPlatformPixmap::create(0, 0, QPlatformPixmap::PixmapType);
if (data->fromData(buf, len, format, flags))
return true;
data.reset();
return false;
}