信通快递 發表於 2025-10-24 11:05:00

关于 QImage 加载本地大图片的崩溃问题

<blockquote>
<p><strong>版权声明:</strong><br>
本文为原创内容,作者:。<br>
转载请注明出处:<br>
原博主主页:https://www.cnblogs.com/Yzi321<br>
本文链接:https://www.cnblogs.com/Yzi321/p/19162705<br>
许可协议:CC BY 4.0</p>
</blockquote>
<h2 id="更新">更新</h2>
<p>因为重新编译太过于繁琐,这里笔者把QImageReader源码挑选出部分核心功能,修改该qt bug,改为MXTImageReader模块。再将此模块作为源码模块放到需要的项目中,即可实现大图片的加载。</p>
<p>例程环境:VS2022 Qt5.11.2 MSVC2017_x64</p>
<p>这是文件下载链接1(GoogleDrive)、文件下载链接2(百度网盘)。</p>
<h2 id="目录">目录</h2>
<ul>
<li>问题场景</li>
<li>问题查找</li>
<li>问题解决</li>
<li>测试验证
<ul>
<li>执行结果</li>
</ul>
</li>
<li>拓展思考
<ul>
<li>其他类型图片的加载崩溃</li>
<li>代码可使用最大图片</li>
</ul>
</li>
<li>源代码</li>
</ul>
<blockquote>
<p>问题场景:<br>
图片格式:BMP<br>
图片颜色类型:Format_RGB888、Format_Grayscale8、Format_RGB32<br>
图片大小:50000*50000<br>
Qt版本:5.11.2<br>
编译平台:MSVC 2017 x64</p>
</blockquote>
<blockquote>
<p>(<code>...</code> 表示部分源码省略展示)</p>
</blockquote>
<h2 id="1问题查找">1、问题查找</h2>
<pre><code class="language-c++">QImage reloadImg;
reloadImg.load(fileName);
</code></pre>
<p>代码运行在 <code>load</code> 时崩溃,下面我们来看一下,<code>load</code> 函数做了什么事情,为什么会崩溃。</p>
<pre><code class="language-c++">bool QImage::load(const QString &amp;fileName, const char* format)
{
    QImage image = QImageReader(fileName, format).read();
    operator=(image);
    return !isNull();
}
</code></pre>
<p><code>QImageReader(fileName, format).read();</code> 调用了<code>QImageReader</code>的构造函数和read函数</p>
<pre><code class="language-c++">QImageReader::QImageReader(const QString &amp;fileName, const QByteArray &amp;format)
    : QImageReader(new QFile(fileName), format)
{
    d-&gt;deleteDevice = true;
}
QImageReader::QImageReader(QIODevice *device, const QByteArray &amp;format)
    : d(new QImageReaderPrivate(this))
{
    d-&gt;device = device;
    d-&gt;format = format;
}
</code></pre>
<p><code>QImageReader</code>的构造函数使用了委托构造,<code>QImageReaderPrivate</code>内部持有类的实际内容,而 <code>QIODevice *device</code> 形参传入的 <code>new QFile(fileName)</code> 仅仅是使用多态作为IO接口去加载文件。<br>
这里把<code>QImageReaderPrivate</code>的定义展示出来,笔者写了部分的注释。</p>
<pre><code class="language-c++">class QImageReaderPrivate
{
public:
    /// 构造函数:绑定外部的 QImageReader 实例
    QImageReaderPrivate(QImageReader *qq);

    /// 析构函数:释放资源(如 device、handler 等)
    ~QImageReaderPrivate();

    //
    // ==================== 设备与格式相关 ====================
    //

    /// 文件格式(如 "png"、"jpg"、"bmp" 等)
    QByteArray format;

    /// 是否自动检测图像格式(若为 true,则根据文件头判断格式)
    bool autoDetectImageFormat;

    /// 是否忽略文件格式与扩展名(例如:强制使用 handler 解析)
    bool ignoresFormatAndExtension;

    /// 当前绑定的输入设备(例如 QFile、QBuffer、QByteArray 等)
    QIODevice *device;

    /// 是否在析构时自动删除 device(例如用户未显式管理时)
    bool deleteDevice;

    /// 当前用于实际解码图像的处理器对象(QImageIOHandler 派生类)
    QImageIOHandler *handler;

    /// 初始化 handler(根据 format / device 自动选择解码插件)
    bool initHandler();

    //
    // ==================== 图像选项与读取参数 ====================
    //

    /// 读取时的裁剪区域(在图像坐标系中)
    QRect clipRect;

    /// 缩放后的目标尺寸(若为空则不缩放)
    QSize scaledSize;

    /// 缩放后再裁剪的区域(用于优化部分读取)
    QRect scaledClipRect;

    /// 图像质量参数(通常用于写入时;某些解码器可能会参考)
    int quality;

    /// 图像内嵌文本信息(键值对形式,如 Exif、注释、元数据)
    QMap&lt;QString, QString&gt; text;

    /// 从 handler 获取所有文本信息
    void getText();

    /// 自动应用图像变换的策略枚举
    enum {
      UsePluginDefault,   ///&lt; 使用插件默认行为(由解码器决定是否旋转)
      ApplyTransform,   ///&lt; 总是应用图像的 EXIF / 方向变换
      DoNotApplyTransform ///&lt; 不应用任何图像方向变换
    } autoTransform;

    //
    // ==================== 错误状态 ====================
    //

    /// 最近一次错误的类型(如 不支持的格式、读失败、内存不足 等)
    QImageReader::ImageReaderError imageReaderError;

    /// 最近一次错误的字符串描述
    QString errorString;

    //
    // ==================== 关联的外部对象 ====================
    //

    /// 指向外部的 QImageReader 公有接口类,用于回调与状态同步
    QImageReader *q;
};
</code></pre>
<p>这里的<code>QIODevice *device</code>是本地图片的IO设备指针,<code>QImageIOHandler *handler</code>用来进行关键的加载图片内容的处理操作,在下文中有提及。<br>
构造函数就这么多内容,看不出所以然。下面去看看 <code>QImageReader::read()</code></p>
<pre><code class="language-c++">QImage QImageReader::read()
{
    // Because failed image reading might have side effects, we explicitly
    // return a null image instead of the image we've just created.
    QImage image;
    return read(&amp;image) ? image : QImage();
}
bool QImageReader::read(QImage *image)
{
    ...

    if (!d-&gt;handler &amp;&amp; !d-&gt;initHandler())
      return false;
   
    ...

    // read the image
    if (!d-&gt;handler-&gt;read(image)) {
      d-&gt;imageReaderError = InvalidDataError;
      d-&gt;errorString = QImageReader::tr("Unable to read image data");
      return false;
    }

    ...
}
</code></pre>
<p><code>bool QImageReader::read(QImage *image)</code> 内部做了很多关于自动识别图片类型的操作,这里省略展示了这些代码,仅展示关键代码。</p>
<p>对于此文的BMP图片,<code>d-&gt;initHandler()</code>函数的实现了:定义<code>d-&gt;handler</code>变量为 <code>new QBmpHandler</code>,同时设置<code>handler-&gt;setDevice(device)</code>,使得<code>d-&gt;handler</code>可以持有图片文件的IO设备指针。</p>
<p>接下来是最关键的 <code>d-&gt;handler-&gt;read(image)</code> 加载图片数据。</p>
<pre><code class="language-c++">bool QBmpHandler::read(QImage *image)
{
    if (state == Error)
      return false;

    if (!image) {
      qWarning("QBmpHandler::read: cannot read into null pointer");
      return false;
    }

    if (state == Ready &amp;&amp; !readHeader()) {
      state = Error;
      return false;
    }

    QIODevice *d = device();
    QDataStream s(d);

    // Intel byte order
    s.setByteOrder(QDataStream::LittleEndian);

    // read image
    const bool readSuccess = m_format == BmpFormat ?
      read_dib_body(s, infoHeader, fileHeader.bfOffBits, startpos, *image) :
      read_dib_body(s, infoHeader, -1, startpos - BMP_FILEHDR_SIZE, *image);
    if (!readSuccess)
      return false;

    state = Ready;
    return true;
}
</code></pre>
<p><code>QBmpHandler::read(QImage *image)</code>函数内部的关键是 <code>QBmpHandler::readHeader()</code>函数和<code>read_dib_body()</code>函数。</p>
<pre><code class="language-c++">bool QBmpHandler::readHeader()
{
    state = Error;

    QIODevice *d = device();
    QDataStream s(d);
    startpos = d-&gt;pos();

    // Intel byte order
    s.setByteOrder(QDataStream::LittleEndian);

    // read BMP file header
    if (m_format == BmpFormat &amp;&amp; !read_dib_fileheader(s, fileHeader))
      return false;

    // read BMP info header
    if (!read_dib_infoheader(s, infoHeader))
      return false;

    state = ReadHeader;
    return true;
}
</code></pre>
<p>先来看看<code>QBmpHandler::readHeader()</code>,内部实现了从IO设备文件中,加载图片的文件头和消息头,可以根据下面BMP文件的存储格式查看。</p>
<pre><code class="language-sql">+-------------------------+
| BMP_FILEHDR (14 bytes)|--&gt; 文件头
+-------------------------+
| BMP_INFOHDR (40~124 B)|--&gt; 信息头(DIB Header)
+-------------------------+
| Color Table (可选)      |--&gt; 调色板(灰度/索引图时有)
+-------------------------+
| Pixel Array             |--&gt; 实际像素数据
+-------------------------+
</code></pre>
<p>这是qt内部定义的文件头和消息头结构体,加载存放到变量<code>BMP_FILEHDR fileHeader</code>和<code>BMP_INFOHDR infoHeader</code>中</p>
<pre><code class="language-c++">struct BMP_FILEHDR {
    char   bfType;      // 文件类型,必须是 'B','M'
    qint32 bfSize;         // 文件总大小(字节)
    qint16 bfReserved1;      // 保留,一般为 0
    qint16 bfReserved2;      // 保留,一般为 0
    qint32 bfOffBits;      // 图像数据起始偏移(从文件头开始的偏移)
};
struct BMP_INFOHDR {
    qint32biSize;          // 结构体大小(字节数)
    qint32biWidth;         // 图像宽度(像素)
    qint32biHeight;      // 图像高度(像素)
    qint16biPlanes;      // 平面数,固定为 1
    qint16biBitCount;      // 每像素位数 (1,4,8,16,24,32)
    qint32biCompression;   // 压缩方式(0=BI_RGB)
    qint32biSizeImage;   // 图像数据大小(字节)
    qint32biXPelsPerMeter; // 水平分辨率(像素/米)
    qint32biYPelsPerMeter; // 垂直分辨率(像素/米)
    qint32biClrUsed;       // 实际使用的调色板颜色数
    qint32biClrImportant;// 重要颜色数

    // 以下是 Windows V4/V5 扩展部分(Qt 兼容处理)
    quint32 biRedMask;       // 红通道掩码
    quint32 biGreenMask;   // 绿通道掩码
    quint32 biBlueMask;      // 蓝通道掩码
    quint32 biAlphaMask;   // 透明度掩码
    qint32biCSType;      // 色彩空间类型(如 LCS_sRGB)
    qint32biEndpoints;// 色彩空间端点
    qint32biGammaRed;
    qint32biGammaGreen;
    qint32biGammaBlue;
    qint32biIntent;      // 渲染意图(V5)
    qint32biProfileData;
    qint32biProfileSize;
    qint32biReserved;
};
</code></pre>
<blockquote>
<p>这里提一嘴,<code>BMP_FILEHDR</code>的<code>qint32 bfSize</code>最大值转为无符号类型的内存容量,仅有4096MB的大小,意味着图片大小超过4GB时,实际上的<code>bfSize</code>会越界,所以没有可信度,但是Qt这里并没有使用这个变量,应该也是考虑到了这个问题。<br>
这也解释了为什么有的看图软件无法打开比较大的图片。</p>
</blockquote>
<p>下面是关键的<code>static bool read_dib_body(QDataStream &amp;s, const BMP_INFOHDR &amp;bi, qint64 offset, qint64 startpos, QImage &amp;image)</code>函数</p>
<p>因为确认格式是<code>BmpFormat</code>而不是<code>DibFormat</code>,所以<code>QBmpHandler::read(QImage *image)</code>内部调用的逻辑,简化后是:</p>
<pre><code class="language-c++">const bool readSuccess = read_dib_body(s, infoHeader, fileHeader.bfOffBits, startpos, *image)
</code></pre>
<p>这里的传参,s为文件数据流,infoHeader为消息头,fileHeader.bfOffBits为图像数据起始偏移,startpos基本为0,代表文件头数据位置,*image为存放图片的对象</p>
<p>因为<code>read_dib_body</code>函数太长,这里仅展示<code>Format_RGB888</code>、<code>Format_Grayscale8</code>、<code>Format_RGB32</code>的内容,具体的源码可以看文章最后的源代码章节</p>
<pre><code class="language-c++">static bool read_dib_body(QDataStream &amp;s, const BMP_INFOHDR &amp;bi, qint64 offset, qint64 startpos, QImage &amp;image)
{
    ...
    QImage::Format format;
    switch (nbits) {
      case 32:
      case 24:
      case 16:
            depth = 32;
            format = transp ? QImage::Format_ARGB32 : QImage::Format_RGB32;
            break;
      case 8:
      case 4:
            depth = 8;
            format = QImage::Format_Indexed8;
            break;
      default:
            depth = 1;
            format = QImage::Format_Mono;
    }
    ...
    if (image.size() != QSize(w, h) || image.format() != format) {
      image = QImage(w, h, format);
      if (image.isNull())                        // could not create image
            return false;
      if (ncols)
            image.setColorCount(ncols);            // Ensure valid QImage
    }
    ...

    int bpl = image.bytesPerLine();
    uchar *data = image.bits();
    if (nbits == 1) {                              // 1 bit BMP image
      ...}
    else if (nbits == 4) {                        // 4 bit BMP image
      ...}
    else if (nbits == 8) {                        // 8 bit BMP image
      if (comp == BMP_RLE8) {                // run length compression
      ...
      } else if (comp == BMP_RGB) {                // uncompressed
            while (--h &gt;= 0) {
                if (d-&gt;read((char *)data + h*bpl, bpl) != bpl)
                  break;
            }
      }
    }
    else if (nbits == 16 || nbits == 24 || nbits == 32) { // 16,24,32 bit BMP image
      QRgb *p;
      QRgb*end;
      uchar *buf24 = new uchar;
      int    bpl24 = ((w*nbits+31)/32)*4;
      uchar *b;
      int c;

      while (--h &gt;= 0) {
            p = (QRgb *)(data + h*bpl);
            end = p + w;
            if (d-&gt;read((char *)buf24,bpl24) != bpl24)
                break;
            b = buf24;
            while (p &lt; end) {
                c = *(uchar*)b | (*(uchar*)(b+1)&lt;&lt;8);
                if (nbits &gt; 16)
                  c |= *(uchar*)(b+2)&lt;&lt;16;
                if (nbits &gt; 24)
                  c |= *(uchar*)(b+3)&lt;&lt;24;
                *p++ = qRgba(((c &amp; red_mask) &gt;&gt; red_shift) * red_scale,
                                        ((c &amp; green_mask) &gt;&gt; green_shift) * green_scale,
                                        ((c &amp; blue_mask) &gt;&gt; blue_shift) * blue_scale,
                                        transp ? ((c &amp; alpha_mask) &gt;&gt; alpha_shift) * alpha_scale : 0xff);
                b += nbits/8;
            }
      }
      delete[] buf24;
    }
    ...
}
</code></pre>
<p>因为BMP格式存放数据时,是从按行倒序存放的,所以这里加载时,也是高度的倒序加载。</p>
<p>下面分别讲一下,这三种颜色格式的调用流程:</p>
<ul>
<li>
<p><code>Format_RGB888</code></p>
<pre><code>nbits = 24; 表示RGB三个颜色   
depth = 32; 表示虽然你是24位的图片,但是QImage按照32位来申请内存空间和存放数据
image = QImage(w, h, format); 申请内存空间
int bpl = image.bytesPerLine(); 每行字节数,因为是32位深度,所以等于 4 * w
uchar *data = image.bits(); 目标存放数据的源地址的头指针
(QImage内部每个4字节,也就是一个int,存放一个像素的数据,顺序格式为ARGB,这里A通道不使用,为默认值0XFF)
循环加载每行的数据:
      QRgb *p = (QRgb *)(data + h*bpl);本质上是uint*,用来遍历每一行的像素,指向某一高度指针的头像素指针基地址
      每一行数据,本地文件内存放的(byte)字节格式为:BGR通道的循环
      所以,c = *(uchar*)b | (*(uchar*)(b+1)&lt;&lt;8); c |= *(uchar*)(b+2)&lt;&lt;16; 加载每一个像素
      然后传给p指针,并指针偏移
</code></pre>
</li>
<li>
<p><code>Format_RGB32</code></p>
<pre><code>nbits = 32; 表示ARGB四个颜色   
depth = 32; 表示QImage按照32位来申请内存空间和存放数据
image = QImage(w, h, format); 申请内存空间
int bpl = image.bytesPerLine(); 每行字节数,因为是32位深度,所以等于 4 * w
uchar *data = image.bits(); 目标存放数据的源地址的头指针
(QImage内部每个4字节,也就是一个int,存放一个像素的数据,顺序格式为ARGB)
循环加载每行的数据:
      QRgb *p = (QRgb *)(data + h*bpl);本质上是uint*,用来遍历每一行的像素,指向某一高度指针的头像素指针基地址
      每一行数据,本地文件内存放的(byte)字节格式为:BGRA通道的循环
      所以,c = *(uchar*)b | (*(uchar*)(b+1)&lt;&lt;8); c |= *(uchar*)(b+2)&lt;&lt;16; c |= *(uchar*)(b+3)&lt;&lt;24; 加载每一个像素
      然后传给p指针,并指针偏移
</code></pre>
</li>
<li>
<p><code>Format_Grayscale8</code></p>
<pre><code>nbits = 8; 表示黑白颜色   
depth = 8; 表示QImage按照8位来申请内存空间和存放数据
image = QImage(w, h, format); 申请内存空间
int bpl = image.bytesPerLine(); 每行字节数,因为是8位深度,所以等于 1 * w
uchar *data = image.bits(); 目标存放数据的源地址的头指针
黑白图会有调色板,这里不详细说明过程
(每个1字节,存放一个像素的数据)
循环加载每行的数据:
      因为没有顺序的要求,可以直接按行加载每一行即可。
      (char *)data + h*bpl为每一行的头像素指针地址。
      while (--h &gt;= 0) {
          if (d-&gt;read((char *)data + h*bpl, bpl) != bpl)
            break;
      }
</code></pre>
</li>
</ul>
<blockquote>
<p>可以看到<code>Format_RGB888</code>和<code>Format_RGB32</code>大致上差不多</p>
</blockquote>
<p>好了,我们费了九牛二虎之力,终于到了崩溃的地方,也就是:</p>
<pre><code class="language-c++">    // 黑白图崩溃行
    if (d-&gt;read((char *)data + h*bpl, bpl) != bpl)
   
    // 彩色图崩溃行
    *p++ = qRgba(((c &amp; red_mask) &gt;&gt; red_shift) * red_scale,
                            ((c &amp; green_mask) &gt;&gt; green_shift) * green_scale,
                            ((c &amp; blue_mask) &gt;&gt; blue_shift) * blue_scale,
                            transp ? ((c &amp; alpha_mask) &gt;&gt; alpha_shift) * alpha_scale : 0xff);
</code></pre>
<p>那么这里有什么问题呢,我们回想一下崩溃的一个特定条件:大图,50000*50000的宽度</p>
<p>结合崩溃的提示信息,p指针越界和read内部指针越界,那么我们可以猜测到,可能是p指针的计算的问题。</p>
<p>现在我们仔细回味一下p指针的计算,一个是<code>(char *)data + h*bpl</code>,另一个是<code>(QRgb *)data + h*bpl</code></p>
<p>此时已经发现不对了,看一下h和bpl的定义,分别为<code>int h = bi.biHeight;</code>、<code>int bpl = image.bytesPerLine();</code></p>
<p>以黑白图举例,假设为第一行,h为49999,bpl为50000,计算的偏移为<code>2499950000</code>,转为二进制<code>0b 1001 0101 0000 0010 0011 0101 1011 0000</code></p>
<p>好,那么好,水落石出。</p>
<p>计算的结果为int型,第一个bit为符号位,实际作为偏移时,是作为负数<code>-1795017296</code>来参与计算的,也就是以为了实际的p指针是在向前偏移,所以会出现指针越界的问题。</p>
<p>这里也就找到了问题所在,去检查了一下其他不同通道数<code>nbits</code>的加载过程,都是由一样的处理,这里观察了一下计算指针时的变量,发现都是用了<code>bpl</code>这个变量</p>
<p>那么只需将类型修改为qint64即可,这样计算偏移时,会自动转为qint64的长度,也就不会越界。如下:</p>
<pre><code class="language-c++">    qint64 bpl = image.bytesPerLine();
</code></pre>
<h2 id="2问题解决">2、问题解决</h2>
<p>打开 <code>.\qt-everywhere-src-5.11.2\qtbase\src\gui\image\qbmphandler.cpp</code> 文件</p>
<p>将其中 <code>read_dib_body</code> 函数的 <code>int bpl = image.bytesPerLine();</code> ,修改为 <code>qint64 bpl = image.bytesPerLine();</code></p>
<p>重新编译即可。</p>
<h2 id="3验证测试例程">3、验证测试例程</h2>
<p>下面为测试代码,验证修改后的可行性。</p>
<pre><code class="language-c++">#include &lt;QCoreApplication&gt;
#include &lt;QImage&gt;
#include &lt;QDebug&gt;
#include &lt;QString&gt;

int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);

    int width = 40000;    // starting width
    int height = 40000;   // starting height
    const int step = 10000; // step to increase size each iteration
    const int maxTry = 10;// maximum attempts
    QString fileName = QString("test.bmp");

    for (int i = 0; i &lt; maxTry; ++i)
    {
      qDebug().noquote() &lt;&lt; QString("Trying to create QImage: %1 x %2").arg(width).arg(height);

      try
      {
            {
                QImage img(width, height, QImage::Format_Grayscale8);
                if (img.isNull()) {
                  qDebug().noquote() &lt;&lt; "Creation failed, QImage returned null";
                  break;
                }

                // Fill with gradient pattern (pseudo black &amp; white)
                for (int y = 0; y &lt; height; ++y) {
                  uchar* line = img.scanLine(y);
                  for (int x = 0; x &lt; width; ++x) {
                        line = (x + y) % 256;// Grayscale value
                  }
                }

                // Save as BMP
                if (!img.save(fileName)) {
                  qDebug().noquote() &lt;&lt; "Failed to save: " + fileName;
                  break;
                }
                else {
                  qDebug().noquote() &lt;&lt; "Saved successfully: " + fileName;
                }
            }

            // Reload image
            QImage reloadImg;
            if (!reloadImg.load(fileName)) {
                qDebug().noquote() &lt;&lt; "Failed to reload: " + fileName;
                break;
            }
            else {
                qDebug().noquote() &lt;&lt; QString("Reloaded successfully: %1 Size: %2 x %3 Bytes: %4")
                  .arg(fileName)
                  .arg(reloadImg.width())
                  .arg(reloadImg.height())
                  .arg(reloadImg.sizeInBytes());
            }

      }
      catch (std::bad_alloc&amp; e)
      {
            qDebug().noquote() &lt;&lt; QString("Memory allocation failed: %1").arg(e.what());
            break;
      }

      try
      {
            {
                QImage img(width, height, QImage::Format_RGB888);
                if (img.isNull()) {
                  qDebug().noquote() &lt;&lt; "Creation failed, QImage returned null";
                  break;
                }

                // Fill with pseudo-color pattern
                for (int y = 0; y &lt; height; ++y) {
                  uchar* line = img.scanLine(y);
                  for (int x = 0; x &lt; width; ++x) {
                        line = (x + y) % 256;      // R
                        line = (2 * x + y) % 256;// G
                        line = (x + 2 * y) % 256;// B
                  }
                }

                // Save as BMP
                if (!img.save(fileName)) {
                  qDebug().noquote() &lt;&lt; "Failed to save: " + fileName;
                  break;
                }
                else {
                  qDebug().noquote() &lt;&lt; "Saved successfully: " + fileName;
                }
            }
            // Reload image
            QImage reloadImg;
            if (!reloadImg.load(fileName)) {
                qDebug().noquote() &lt;&lt; "Failed to reload: " + fileName;
                break;
            }
            else {
                qDebug().noquote() &lt;&lt; QString("Reloaded successfully: %1 Size: %2 x %3 Bytes: %4")
                  .arg(fileName)
                  .arg(reloadImg.width())
                  .arg(reloadImg.height())
                  .arg(reloadImg.sizeInBytes());
            }

      }
      catch (std::bad_alloc&amp; e)
      {
            qDebug().noquote() &lt;&lt; QString("Memory allocation failed: %1").arg(e.what());
            break;
      }

      // Increase size
      width += step;
      height += step;
    }

    return 0;
}

</code></pre>
<h4 id="执行结果">执行结果</h4>
<pre><code class="language-bash">Trying to create QImage: 40000 x 40000
Saved successfully: test.bmp
Reloaded successfully: test.bmp Size: 40000 x 40000 Bytes: 1600000000
Saved successfully: test.bmp
Reloaded successfully: test.bmp Size: 40000 x 40000 Bytes: 6400000000
Trying to create QImage: 50000 x 50000
Saved successfully: test.bmp
Reloaded successfully: test.bmp Size: 50000 x 50000 Bytes: 2500000000
Saved successfully: test.bmp
Reloaded successfully: test.bmp Size: 50000 x 50000 Bytes: 10000000000
Trying to create QImage: 60000 x 60000
Saved successfully: test.bmp
Reloaded successfully: test.bmp Size: 60000 x 60000 Bytes: 3600000000
Saved successfully: test.bmp
Reloaded successfully: test.bmp Size: 60000 x 60000 Bytes: 14400000000
Trying to create QImage: 70000 x 70000
Saved successfully: test.bmp
Reloaded successfully: test.bmp Size: 70000 x 70000 Bytes: 4900000000
QImage: out of memory, returning null image
Saved successfully: test.bmp
Failed to reload: test.bmp
</code></pre>
<p>因为笔者的工作机内存仅有16G,过大的图片无法存放在电脑内存,就不再后续测试了</p>
<p>可以看到,相比之前是有优化效果的。</p>
<h2 id="拓展思考">拓展思考</h2>
<h3 id="1其他类型图片的加载崩溃">(1)其他类型图片的加载崩溃</h3>
<p>其他类型图片的加载崩溃,笔者没有测试过,如果出现了,有概率和这种问题差不多,应当去同类型的<code>QImageIOHandler</code>派生中去查找问题,如<code>QPngHandler</code>,<code>QXpmHandler</code>,<code>QXbmHandler</code>,<code>QPpmHandler</code></p>
<h3 id="2代码可使用最大图片">(2)代码可使用最大图片</h3>
<pre><code class="language-c++">struct Q_GUI_EXPORT QImageData {      // internal image data
    QImageData();
    ~QImageData();
    static QImageData *create(const QSize &amp;size, QImage::Format format);
    static QImageData *create(uchar *data, int w, int h,int bpl, QImage::Format format, bool readOnly, QImageCleanupFunction cleanupFunction = 0, void *cleanupInfo = 0);

    QAtomicInt ref;

    int width;
    int height;
    int depth;
    qsizetype nbytes;               // number of bytes data
    qreal devicePixelRatio;
    QVector&lt;QRgb&gt; colortable;
    uchar *data;
    QImage::Format format;
    qsizetype bytes_per_line;
    int ser_no;               // serial number
    int detach_no;

    qrealdpmx;                // dots per meter X (or 0)
    qrealdpmy;                // dots per meter Y (or 0)
    QPointoffset;         // offset in pixels

    uint own_data : 1;
    uint ro_data : 1;
    uint has_alpha_clut : 1;
    uint is_cached : 1;
    uint is_locked : 1;

    QImageCleanupFunction cleanupFunction;
    void* cleanupInfo;

    bool checkForAlphaPixels() const;

    // Convert the image in-place, minimizing memory reallocation
    // Return false if the conversion cannot be done in-place.
    bool convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags);

    QMap&lt;QString, QString&gt; text;

    bool doImageIO(const QImage *image, QImageWriter* io, int quality) const;

    QPaintEngine *paintEngine;
};
</code></pre>
<p>这是 <code>QImage</code> 的实际存放数据的类,调用 <code>create</code> 函数时,实际申请内存的部分代码为:</p>
<pre><code class="language-c++">
QImageData * QImageData::create(const QSize &amp;size, QImage::Format format)
{
    if (!size.isValid() || format == QImage::Format_Invalid)
      return 0;                              // invalid parameter(s)

    uint width = size.width();
    uint height = size.height();
    uint depth = qt_depthForFormat(format);

    const int bytes_per_line = ((width * depth + 31) &gt;&gt; 5) &lt;&lt; 2; // bytes per scanline (must be multiple of 4)

    ...

    d-&gt;bytes_per_line = bytes_per_line;

    d-&gt;nbytes = d-&gt;bytes_per_line*height;
    d-&gt;data= (uchar *)malloc(d-&gt;nbytes);

    ...
}
</code></pre>
<p><code>...</code> 表示部分源码省略展示,可以看到 <code>d-&gt;nbytes</code> 为实际的可以申请的内存大小,其类型为 <code>qsizetype nbytes; </code> , 在 64 位系统上:<code>qsizetype</code> = <code>long long</code>, 所以不考虑电脑性能,实际可以申请和使用的最大的图片的容量为:</p>
<pre><code>(2^63-1) / 1024 / 1024 / 1024 / 1024 ≈ 8388608 TB
</code></pre>
<p>可见,不考虑加载图片,仅是代码申请QImage情况下,可用性是够够的...</p>
<h2 id="源代码">源代码</h2>
<h3 id="read_dib_body">read_dib_body</h3>
<pre><code class="language-c++">static bool read_dib_body(QDataStream &amp;s, const BMP_INFOHDR &amp;bi, qint64 offset, qint64 startpos, QImage &amp;image)
{
    QIODevice* d = s.device();
    if (d-&gt;atEnd())                              // end of stream/file
      return false;
#if 0
    qDebug("offset...........%lld", offset);
    qDebug("startpos.........%lld", startpos);
    qDebug("biSize...........%d", bi.biSize);
    qDebug("biWidth..........%d", bi.biWidth);
    qDebug("biHeight.........%d", bi.biHeight);
    qDebug("biPlanes.........%d", bi.biPlanes);
    qDebug("biBitCount.......%d", bi.biBitCount);
    qDebug("biCompression....%d", bi.biCompression);
    qDebug("biSizeImage......%d", bi.biSizeImage);
    qDebug("biXPelsPerMeter..%d", bi.biXPelsPerMeter);
    qDebug("biYPelsPerMeter..%d", bi.biYPelsPerMeter);
    qDebug("biClrUsed........%d", bi.biClrUsed);
    qDebug("biClrImportant...%d", bi.biClrImportant);
#endif
    int w = bi.biWidth,         h = bi.biHeight,nbits = bi.biBitCount;
    int t = bi.biSize,         comp = bi.biCompression;
    uint red_mask = 0;
    uint green_mask = 0;
    uint blue_mask = 0;
    uint alpha_mask = 0;
    int red_shift = 0;
    int green_shift = 0;
    int blue_shift = 0;
    int alpha_shift = 0;
    int red_scale = 0;
    int green_scale = 0;
    int blue_scale = 0;
    int alpha_scale = 0;

    if (!d-&gt;isSequential())
      d-&gt;seek(startpos + BMP_FILEHDR_SIZE + bi.biSize); // goto start of colormap or masks

    if (bi.biSize &gt;= BMP_WIN4) {
      red_mask = bi.biRedMask;
      green_mask = bi.biGreenMask;
      blue_mask = bi.biBlueMask;
      alpha_mask = bi.biAlphaMask;
    } else if (comp == BMP_BITFIELDS &amp;&amp; (nbits == 16 || nbits == 32)) {
      if (d-&gt;read((char *)&amp;red_mask, sizeof(red_mask)) != sizeof(red_mask))
            return false;
      if (d-&gt;read((char *)&amp;green_mask, sizeof(green_mask)) != sizeof(green_mask))
            return false;
      if (d-&gt;read((char *)&amp;blue_mask, sizeof(blue_mask)) != sizeof(blue_mask))
            return false;
    }

    bool transp = (comp == BMP_BITFIELDS) &amp;&amp; alpha_mask;
    int ncols = 0;
    int depth = 0;
    QImage::Format format;
    switch (nbits) {
      case 32:
      case 24:
      case 16:
            depth = 32;
            format = transp ? QImage::Format_ARGB32 : QImage::Format_RGB32;
            break;
      case 8:
      case 4:
            depth = 8;
            format = QImage::Format_Indexed8;
            break;
      default:
            depth = 1;
            format = QImage::Format_Mono;
    }

    if (depth != 32) {
      ncols = bi.biClrUsed ? bi.biClrUsed : 1 &lt;&lt; nbits;
      if (ncols &lt; 1 || ncols &gt; 256) // sanity check - don't run out of mem if color table is broken
            return false;
    }

    if (bi.biHeight &lt; 0)
      h = -h;                  // support images with negative height

    if (image.size() != QSize(w, h) || image.format() != format) {
      image = QImage(w, h, format);
      if (image.isNull())                        // could not create image
            return false;
      if (ncols)
            image.setColorCount(ncols);            // Ensure valid QImage
    }

    image.setDotsPerMeterX(bi.biXPelsPerMeter);
    image.setDotsPerMeterY(bi.biYPelsPerMeter);

    if (ncols &gt; 0) {                              // read color table
      image.setColorCount(ncols);
      uchar rgb;
      int   rgb_len = t == BMP_OLD ? 3 : 4;
      for (int i=0; i&lt;ncols; i++) {
            if (d-&gt;read((char *)rgb, rgb_len) != rgb_len)
                return false;
            image.setColor(i, qRgb(rgb,rgb,rgb));
            if (d-&gt;atEnd())                        // truncated file
                return false;
      }
    } else if (comp == BMP_BITFIELDS &amp;&amp; (nbits == 16 || nbits == 32)) {
      red_shift = calc_shift(red_mask);
      if (((red_mask &gt;&gt; red_shift) + 1) == 0)
            return false;
      red_scale = 256 / ((red_mask &gt;&gt; red_shift) + 1);
      green_shift = calc_shift(green_mask);
      if (((green_mask &gt;&gt; green_shift) + 1) == 0)
            return false;
      green_scale = 256 / ((green_mask &gt;&gt; green_shift) + 1);
      blue_shift = calc_shift(blue_mask);
      if (((blue_mask &gt;&gt; blue_shift) + 1) == 0)
            return false;
      blue_scale = 256 / ((blue_mask &gt;&gt; blue_shift) + 1);
      alpha_shift = calc_shift(alpha_mask);
      if (((alpha_mask &gt;&gt; alpha_shift) + 1) == 0)
            return false;
      alpha_scale = 256 / ((alpha_mask &gt;&gt; alpha_shift) + 1);
    } else if (comp == BMP_RGB &amp;&amp; (nbits == 24 || nbits == 32)) {
      blue_mask = 0x000000ff;
      green_mask = 0x0000ff00;
      red_mask = 0x00ff0000;
      blue_shift = 0;
      green_shift = 8;
      red_shift = 16;
      blue_scale = green_scale = red_scale = 1;
    } else if (comp == BMP_RGB &amp;&amp; nbits == 16) {
      blue_mask = 0x001f;
      green_mask = 0x03e0;
      red_mask = 0x7c00;
      blue_shift = 0;
      green_shift = 2;
      red_shift = 7;
      red_scale = 1;
      green_scale = 1;
      blue_scale = 8;
    }

#if 0
    qDebug("Rmask: %08x Rshift: %08x Rscale:%08x", red_mask, red_shift, red_scale);
    qDebug("Gmask: %08x Gshift: %08x Gscale:%08x", green_mask, green_shift, green_scale);
    qDebug("Bmask: %08x Bshift: %08x Bscale:%08x", blue_mask, blue_shift, blue_scale);
    qDebug("Amask: %08x Ashift: %08x Ascale:%08x", alpha_mask, alpha_shift, alpha_scale);
#endif

    // offset can be bogus, be careful
    if (offset&gt;=0 &amp;&amp; startpos + offset &gt; d-&gt;pos()) {
      if (!d-&gt;isSequential())
            d-&gt;seek(startpos + offset);                // start of image data
    }

    qint64             bpl = image.bytesPerLine();
    uchar *data = image.bits();

    if (nbits == 1) {                              // 1 bit BMP image
      while (--h &gt;= 0) {
            if (d-&gt;read((char*)(data + h*bpl), bpl) != bpl)
                break;
      }
      if (ncols == 2 &amp;&amp; qGray(image.color(0)) &lt; qGray(image.color(1)))
            swapPixel01(&amp;image);                // pixel 0 is white!
    }

    else if (nbits == 4) {                        // 4 bit BMP image
      int    buflen = ((w+7)/8)*4;
      uchar *buf    = new uchar;
      if (comp == BMP_RLE4) {                // run length compression
            int x=0, y=0, c, i;
            quint8 b;
            uchar *p = data + (h-1)*bpl;
            const uchar *endp = p + w;
            while (y &lt; h) {
                if (!d-&gt;getChar((char *)&amp;b))
                  break;
                if (b == 0) {                        // escape code
                  if (!d-&gt;getChar((char *)&amp;b) || b == 1) {
                        y = h;                // exit loop
                  } else switch (b) {
                        case 0:                        // end of line
                            x = 0;
                            y++;
                            p = data + (h-y-1)*bpl;
                            break;
                        case 2:                        // delta (jump)
                        {
                            quint8 tmp;
                            d-&gt;getChar((char *)&amp;tmp);
                            x += tmp;
                            d-&gt;getChar((char *)&amp;tmp);
                            y += tmp;
                        }

                            // Protection
                            if ((uint)x &gt;= (uint)w)
                              x = w-1;
                            if ((uint)y &gt;= (uint)h)
                              y = h-1;

                            p = data + (h-y-1)*bpl + x;
                            break;
                        default:                // absolute mode
                            // Protection
                            if (p + b &gt; endp)
                              b = endp-p;

                            i = (c = b)/2;
                            while (i--) {
                              d-&gt;getChar((char *)&amp;b);
                              *p++ = b &gt;&gt; 4;
                              *p++ = b &amp; 0x0f;
                            }
                            if (c &amp; 1) {
                              unsigned char tmp;
                              d-&gt;getChar((char *)&amp;tmp);
                              *p++ = tmp &gt;&gt; 4;
                            }
                            if ((((c &amp; 3) + 1) &amp; 2) == 2)
                              d-&gt;getChar(0);      // align on word boundary
                            x += c;
                  }
                } else {                        // encoded mode
                  // Protection
                  if (p + b &gt; endp)
                        b = endp-p;

                  i = (c = b)/2;
                  d-&gt;getChar((char *)&amp;b);                // 2 pixels to be repeated
                  while (i--) {
                        *p++ = b &gt;&gt; 4;
                        *p++ = b &amp; 0x0f;
                  }
                  if (c &amp; 1)
                        *p++ = b &gt;&gt; 4;
                  x += c;
                }
            }
      } else if (comp == BMP_RGB) {                // no compression
            memset(data, 0, h*bpl);
            while (--h &gt;= 0) {
                if (d-&gt;read((char*)buf,buflen) != buflen)
                  break;
                uchar *p = data + h*bpl;
                uchar *b = buf;
                for (int i=0; i&lt;w/2; i++) {      // convert nibbles to bytes
                  *p++ = *b &gt;&gt; 4;
                  *p++ = *b++ &amp; 0x0f;
                }
                if (w &amp; 1)                        // the last nibble
                  *p = *b &gt;&gt; 4;
            }
      }
      delete [] buf;
    }

    else if (nbits == 8) {                        // 8 bit BMP image
      if (comp == BMP_RLE8) {                // run length compression
            int x=0, y=0;
            quint8 b;
            uchar *p = data + (h-1)*bpl;
            const uchar *endp = p + w;
            while (y &lt; h) {
                if (!d-&gt;getChar((char *)&amp;b))
                  break;
                if (b == 0) {                        // escape code
                  if (!d-&gt;getChar((char *)&amp;b) || b == 1) {
                            y = h;                // exit loop
                  } else switch (b) {
                        case 0:                        // end of line
                            x = 0;
                            y++;
                            p = data + (h-y-1)*bpl;
                            break;
                        case 2:                        // delta (jump)
                            {
                              quint8 tmp;
                              d-&gt;getChar((char *)&amp;tmp);
                              x += tmp;
                              d-&gt;getChar((char *)&amp;tmp);
                              y += tmp;
                            }

                            // Protection
                            if ((uint)x &gt;= (uint)w)
                              x = w-1;
                            if ((uint)y &gt;= (uint)h)
                              y = h-1;

                            p = data + (h-y-1)*bpl + x;
                            break;
                        default:                // absolute mode
                            // Protection
                            if (p + b &gt; endp)
                              b = endp-p;

                            if (d-&gt;read((char *)p, b) != b)
                              return false;
                            if ((b &amp; 1) == 1)
                              d-&gt;getChar(0);      // align on word boundary
                            x += b;
                            p += b;
                  }
                } else {                        // encoded mode
                  // Protection
                  if (p + b &gt; endp)
                        b = endp-p;

                  char tmp;
                  d-&gt;getChar(&amp;tmp);
                  memset(p, tmp, b); // repeat pixel
                  x += b;
                  p += b;
                }
            }
      } else if (comp == BMP_RGB) {                // uncompressed
            while (--h &gt;= 0) {
                if (d-&gt;read((char *)data + h*bpl, bpl) != bpl)
                  break;
            }
      }
    }

    else if (nbits == 16 || nbits == 24 || nbits == 32) { // 16,24,32 bit BMP image
      QRgb *p;
      QRgb*end;
      uchar *buf24 = new uchar;
      int    bpl24 = ((w*nbits+31)/32)*4;
      uchar *b;
      int c;

      while (--h &gt;= 0) {
            p = (QRgb *)(data + h*bpl);
            end = p + w;
            if (d-&gt;read((char *)buf24,bpl24) != bpl24)
                break;
            b = buf24;
            while (p &lt; end) {
                c = *(uchar*)b | (*(uchar*)(b+1)&lt;&lt;8);
                if (nbits &gt; 16)
                  c |= *(uchar*)(b+2)&lt;&lt;16;
                if (nbits &gt; 24)
                  c |= *(uchar*)(b+3)&lt;&lt;24;
                *p++ = qRgba(((c &amp; red_mask) &gt;&gt; red_shift) * red_scale,
                                        ((c &amp; green_mask) &gt;&gt; green_shift) * green_scale,
                                        ((c &amp; blue_mask) &gt;&gt; blue_shift) * blue_scale,
                                        transp ? ((c &amp; alpha_mask) &gt;&gt; alpha_shift) * alpha_scale : 0xff);
                b += nbits/8;
            }
      }
      delete[] buf24;
    }

    if (bi.biHeight &lt; 0) {
      // Flip the image
      uchar *buf = new uchar;
      h = -bi.biHeight;
      for (int y = 0; y &lt; h/2; ++y) {
            memcpy(buf, data + y*bpl, bpl);
            memcpy(data + y*bpl, data + (h-y-1)*bpl, bpl);
            memcpy(data + (h-y-1)*bpl, buf, bpl);
      }
      delete [] buf;
    }

    return true;
}
</code></pre>
<blockquote>
<p>© 原创作者:<br>
原文链接:https://www.cnblogs.com/Yzi321/p/19162705<br>
转载请注明出处。<br>
协议:CC BY 4.0</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/Yzi321/p/19162705
頁: [1]
查看完整版本: 关于 QImage 加载本地大图片的崩溃问题