【Qt】png和jpg格式的图片(二)

背景

在上篇文章【Qt】png和jpg格式的图片(一) - 掘金 (juejin.cn)中笔者就 jpgpng 两种格式进行了说明,但是关于 Qt 打开改后缀文件名之后图片的问题依然没有说明。要探究Qt为何不能打开改了后缀的图片文件,这个还是得从多方面去定位。前文说了,通过三种方式设置了 QLabel 的图片。

场景复现

这里还是先说明一下设置不成功的场景如何复现:

  1. 找一张 .jpg 的图片,修改后缀也就是文件属性为 .png
  2. 通过以下三种方式中的任意一种去设置 QLabel 为图片。

Qt 版本是 5.9

编译器试了 MSVCmingw 都不好使。

QLabel 设置图片方法

  1. 通过 QPixmap设置 QLabel 的图片
QPixmap img(":/Win11.png");

ui->label->setPixmap(img);
ui->label->setScaledContents(true);
  1. 通过 QImage 设置 QLabel 的图片
QImage img;
img.load(":/Win11.png");
ui->label->setPixmap(QPixmap::fromImage(img))
  1. 通过 QLabel.setStyleSheet() 的图片
ui->label->setStyleSheet(QString("QLabel{"
                                 "border-image:url(:/Win11.png) 4 4 4 4 stretch stretch;"
                                 "}"));

当然还有第四种方法,通过 QSvgRenderer 设置:

#include <QSvgRenderer>

QSvgRenderer svgRender(QString(":/Win11.svg"));
QPixmap pixmap(20,20);
QPainter painter(&pixmap);
svgRender.render(&painter);
ui->label->setPixmap(pixmap);

场景是描述完了,可以动手尝试了

操作

接着就是去查看几种实现方式中的源码是如何是设置的了,这里先猜一下结论

就是 pngjpg 格式的算法不同,格式问题导致的读取的算法不一致,因此Qt内部实现的读取图片的算法只能根据图片文件的后缀所对应的算法去读取算法,而 setStyleSheet 算法也是基于这么一个逻辑,因此三种读取方式都不成功。

关于图片算法的问题,在上篇文章中也略微提到,这个我们不深做研究,只需知道 jpgpng 不是同一种算法,也不通用即可。

初步验证

因为上述几种步骤笔者都做过尝试,因此在验证过程中我们不纠结于使用哪一种方式,直接看结果。

  1. 那如何去验证呢,我们还是看代码,这次我们在 Qt 的 qrc 文件中,去掉图片的后缀名,不带后缀属性去读取图片看看其是否可以读取成功。

去掉后缀属性

运行结果: QLabel 读取成功

如上图所示,在 qrc 文件中去掉图片的后缀,读取图片设置到 QLabel 依然是成功的。

  1. 我们接着操作,在代码中修改图片后缀为 png,看看这次能不能读取成功。

修改后缀为png

运行结果:读取失败

如上图所示,添加后缀后反而还展示不成功了。

初步验证的结果

这就基本上说明了:

Qt 的内部有很大的几率是通过文件的后缀去判断调用哪个图片读取算法的。也就是说,当你人为的修改了 png->jpg 时,在 Qt 中就会出现设置图片失败的问题。目前看来,在代码没有问题,但是图片设置后不显示的情况下,最好的方式是,就是去掉图片的后缀,让 Qt 自己去判断调用哪个算法读取图片。

源码分析

这里有关于 Qt 图片 I\O 的描述可以看看Qt帮助手册中关于图像文件读写的文档。

读写图片文件

源码部分,我们只需查看两个Qt 类就行,个人感觉看一个就知道了。两者识别图片的算法应该是一致的。包括 setStyleSheet()接口中设置图片的接口应该都是一致的。

  1. QPixmap依赖于所在的平台的绘图引擎,故例如反锯齿等一些效果在不同的平台上可能会有不同的显示效果,QImage使用Qt自身的绘图引擎,可在不同平台上具有相同的显示效果
  2. 目前的Qt会把QPixmap都存储在graphics memory中,QImage是存储在客户端的,是独立于硬件的。在X11, Mac 以及 Symbian平台上,QPixmap 是存储在服务器端,而QImage则是存储在客户端,在Windows平台上,QPixmap和QImage都是存储在客户端,并不使用任何的GDI资源。
  3. 由于QImage是独立于硬件的,也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI线程中处理,使用这一方式可以很大幅度提高UI响应速度。

QPixmap

查询到QPixmap的源文件,一般存放在 Qt 安装目录下 ${安装目录}\5.9.9\Src\qtbase\src\gui\image,文件名为 qpixmap.h qpixmap.cpp

先看一下 QPixmap 的 构造函数中读取图片文件的方法。

  1. QPixmap::QPixmap(const QString& fileName, const char *format, Qt::ImageConversionFlags flags)的源码

    QPixmap::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是如何实现的
        }
    
  2. load()函数源码

    /*!
        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 到底是进行了一个什么操作。

QString key = QLatin1String("qt_pixmap")
                    % info.absoluteFilePath() // 返回文件名的绝对路径
                    % HexString<uint>(info.lastModified().toSecsSinceEpoch()) // 返回文件最后一次修改的日期和时间
                    % HexString<quint64>(info.size()) // 返回文件的大小
                    % HexString<uint>(data ? data->pixelType() : QPlatformPixmap::PixmapType); // 这里的data是成员变量,就是说如果设置了data的pixelType的值就读取,没设置的话就是默认值 QPlatformPixmap::PixmapType

上述的 HexString<type>就是 ASCII 的数组形式,16进制 的数组。上述代码中的 % 操作如果不是求余的话会是什么呢。如果是求余,QString 会报错才是呀???

这里最终还是通过设置和查看 QPixmap 的内置变量 data 中的标志去查看当前文件是不是图片,如果是图片,则会修改当前的 QPixmap 指针,如果不是就会返回 false

if (QPixmapCache::find(key, this))
                return true;

这里补充一个QPixmapCache的demo:

/*在缓存中查找与给定键关联的缓存pixmap。如果找到了pixmap,函数将pixmap设置为该pixmap并返回true;否则,它将保留pixmap并返回false。*/
QPixmap pm;
if (!QPixmapCache::find("my_big_image", &pm)) {
    
    
    pm.load("bigimage.png");
    QPixmapCache::insert("my_big_image", pm);
}
painter->drawPixmap(0, 0, pm);

最终判断当前文件是不是图片应该是在 data->fromFile() 中实现的。

这个data的定义:

QExplicitlySharedDataPointer<QPlatformPixmap> data;

下一篇,我们接着研究Qt内部读取图片的代码。

补充

补充一下

load()的函数有必要读一下。

QPixmap::loadFromData() 的源码。

/*!
    \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;
}

猜你喜欢

转载自blog.csdn.net/Fuel_Ming/article/details/124397625
今日推荐