问题查找QImage reloadImg; reloadImg.load(fileName);代码运行在load时崩溃下面我们来看一下load函数做了什么事情为什么会崩溃。bool QImage::load(const QString fileName, const char* format) { QImage image QImageReader(fileName, format).read(); operator(image); return !isNull(); }QImageReader(fileName, format).read();调用了QImageReader的构造函数和read函数QImageReader::QImageReader(const QString fileName, const QByteArray format) : QImageReader(new QFile(fileName), format) { d-deleteDevice true; } QImageReader::QImageReader(QIODevice *device, const QByteArray format) : d(new QImageReaderPrivate(this)) { d-device device; d-format format; }QImageReader的构造函数使用了委托构造QImageReaderPrivate内部持有类的实际内容而QIODevice *device形参传入的new QFile(fileName)仅仅是使用多态作为IO接口去加载文件。这里把QImageReaderPrivate的定义展示出来笔者写了部分的注释。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、注释、元数据 QMapQString, QString text; /// 从 handler 获取所有文本信息 void getText(); /// 自动应用图像变换的策略枚举 enum { UsePluginDefault, /// 使用插件默认行为由解码器决定是否旋转 ApplyTransform, /// 总是应用图像的 EXIF / 方向变换 DoNotApplyTransform /// 不应用任何图像方向变换 } autoTransform; // // 错误状态 // /// 最近一次错误的类型如 不支持的格式、读失败、内存不足 等 QImageReader::ImageReaderError imageReaderError; /// 最近一次错误的字符串描述 QString errorString; // // 关联的外部对象 // /// 指向外部的 QImageReader 公有接口类用于回调与状态同步 QImageReader *q; };这里的QIODevice *device是本地图片的IO设备指针QImageIOHandler *handler用来进行关键的加载图片内容的处理操作在下文中有提及。构造函数就这么多内容看不出所以然。下面去看看QImageReader::read()QImage QImageReader::read() { // Because failed image reading might have side effects, we explicitly // return a null image instead of the image weve just created. QImage image; return read(image) ? image : QImage(); } bool QImageReader::read(QImage *image) { ... if (!d-handler !d-initHandler()) return false; ... // read the image if (!d-handler-read(image)) { d-imageReaderError InvalidDataError; d-errorString QImageReader::tr(Unable to read image data); return false; } ... }bool QImageReader::read(QImage *image)内部做了很多关于自动识别图片类型的操作这里省略展示了这些代码仅展示关键代码。对于此文的BMP图片d-initHandler()函数的实现了定义d-handler变量为new QBmpHandler同时设置handler-setDevice(device),使得d-handler可以持有图片文件的IO设备指针。接下来是最关键的d-handler-read(image)加载图片数据。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 !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; }QBmpHandler::read(QImage *image)函数内部的关键是QBmpHandler::readHeader()函数和read_dib_body()函数。bool QBmpHandler::readHeader() { state Error; QIODevice *d device(); QDataStream s(d); startpos d-pos(); // Intel byte order s.setByteOrder(QDataStream::LittleEndian); // read BMP file header if (m_format BmpFormat !read_dib_fileheader(s, fileHeader)) return false; // read BMP info header if (!read_dib_infoheader(s, infoHeader)) return false; state ReadHeader; return true; }先来看看QBmpHandler::readHeader()内部实现了从IO设备文件中加载图片的文件头和消息头可以根据下面BMP文件的存储格式查看。------------------------- | BMP_FILEHDR (14 bytes) | -- 文件头 ------------------------- | BMP_INFOHDR (40~124 B) | -- 信息头DIB Header ------------------------- | Color Table (可选) | -- 调色板灰度/索引图时有 ------------------------- | Pixel Array | -- 实际像素数据 -------------------------这是qt内部定义的文件头和消息头结构体加载存放到变量BMP_FILEHDR fileHeader和BMP_INFOHDR infoHeader中struct BMP_FILEHDR { char bfType[2]; // 文件类型必须是 B,M qint32 bfSize; // 文件总大小字节 qint16 bfReserved1; // 保留一般为 0 qint16 bfReserved2; // 保留一般为 0 qint32 bfOffBits; // 图像数据起始偏移从文件头开始的偏移 }; struct BMP_INFOHDR { qint32 biSize; // 结构体大小字节数 qint32 biWidth; // 图像宽度像素 qint32 biHeight; // 图像高度像素 qint16 biPlanes; // 平面数固定为 1 qint16 biBitCount; // 每像素位数 (1,4,8,16,24,32) qint32 biCompression; // 压缩方式0BI_RGB qint32 biSizeImage; // 图像数据大小字节 qint32 biXPelsPerMeter; // 水平分辨率像素/米 qint32 biYPelsPerMeter; // 垂直分辨率像素/米 qint32 biClrUsed; // 实际使用的调色板颜色数 qint32 biClrImportant; // 重要颜色数 // 以下是 Windows V4/V5 扩展部分Qt 兼容处理 quint32 biRedMask; // 红通道掩码 quint32 biGreenMask; // 绿通道掩码 quint32 biBlueMask; // 蓝通道掩码 quint32 biAlphaMask; // 透明度掩码 qint32 biCSType; // 色彩空间类型如 LCS_sRGB qint32 biEndpoints[9]; // 色彩空间端点 qint32 biGammaRed; qint32 biGammaGreen; qint32 biGammaBlue; qint32 biIntent; // 渲染意图V5 qint32 biProfileData; qint32 biProfileSize; qint32 biReserved; };这里提一嘴BMP_FILEHDR的qint32 bfSize最大值转为无符号类型的内存容量仅有4096MB的大小意味着图片大小超过4GB时实际上的bfSize会越界所以没有可信度但是Qt这里并没有使用这个变量应该也是考虑到了这个问题。这也解释了为什么有的看图软件无法打开比较大的图片。下面是关键的static bool read_dib_body(QDataStream s, const BMP_INFOHDR bi, qint64 offset, qint64 startpos, QImage image)函数因为确认格式是BmpFormat而不是DibFormat所以QBmpHandler::read(QImage *image)内部调用的逻辑简化后是:const bool readSuccess read_dib_body(s, infoHeader, fileHeader.bfOffBits, startpos, *image)这里的传参s为文件数据流infoHeader为消息头fileHeader.bfOffBits为图像数据起始偏移startpos基本为0代表文件头数据位置*image为存放图片的对象因为read_dib_body函数太长这里仅展示Format_RGB888、Format_Grayscale8、Format_RGB32的内容具体的源码可以看文章最后的源代码章节static bool read_dib_body(QDataStream s, const BMP_INFOHDR bi, qint64 offset, qint64 startpos, QImage 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 0) { if (d-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[bpl]; int bpl24 ((w*nbits31)/32)*4; uchar *b; int c; while (--h 0) { p (QRgb *)(data h*bpl); end p w; if (d-read((char *)buf24,bpl24) ! bpl24) break; b buf24; while (p end) { c *(uchar*)b | (*(uchar*)(b1)8); if (nbits 16) c | *(uchar*)(b2)16; if (nbits 24) c | *(uchar*)(b3)24; *p qRgba(((c red_mask) red_shift) * red_scale, ((c green_mask) green_shift) * green_scale, ((c blue_mask) blue_shift) * blue_scale, transp ? ((c alpha_mask) alpha_shift) * alpha_scale : 0xff); b nbits/8; } } delete[] buf24; } ... }