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

版权声明:
本文为原创内容,作者:[Yzi321]。
转载请注明出处:
原博主主页:https://www.cnblogs.com/Yzi321
本文链接:https://www.cnblogs.com/Yzi321/p/19162705
许可协议:CC BY 4.0

目录

问题场景:
图片格式:BMP
图片颜色类型:Format_RGB888、Format_Grayscale8、Format_RGB32
图片大小:50000*50000
Qt版本:5.11.2
编译平台:MSVC 2017 x64

(... 表示部分源码省略展示)

1、问题查找

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、注释、元数据)
 QMap<QString, 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 we've 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 fileHeaderBMP_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; // 压缩方式(0=BI_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_FILEHDRqint32 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_RGB888Format_Grayscale8Format_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*nbits+31)/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*)(b+1)<<8);
 if (nbits > 16)
 c |= *(uchar*)(b+2)<<16;
 if (nbits > 24)
 c |= *(uchar*)(b+3)<<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;
 }
 ...
}

因为BMP格式存放数据时,是从按行倒序存放的,所以这里加载时,也是高度的倒序加载。

下面分别讲一下,这三种颜色格式的调用流程:

  • Format_RGB888

     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)<<8); c |= *(uchar*)(b+2)<<16; 加载每一个像素 
     然后传给p指针,并指针偏移 
    
  • Format_RGB32

     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)<<8); c |= *(uchar*)(b+2)<<16; c |= *(uchar*)(b+3)<<24; 加载每一个像素 
     然后传给p指针,并指针偏移 
    
  • Format_Grayscale8

     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 >= 0) {
     if (d->read((char *)data + h*bpl, bpl) != bpl)
     break;
     }
    

可以看到Format_RGB888Format_RGB32大致上差不多

好了,我们费了九牛二虎之力,终于到了崩溃的地方,也就是:

 // 黑白图崩溃行
 if (d->read((char *)data + h*bpl, bpl) != bpl)
 
 // 彩色图崩溃行
 *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);

那么这里有什么问题呢,我们回想一下崩溃的一个特定条件:大图,50000*50000的宽度

结合崩溃的提示信息,p指针越界和read内部指针越界,那么我们可以猜测到,可能是p指针的计算的问题。

现在我们仔细回味一下p指针的计算,一个是(char *)data + h*bpl,另一个是(QRgb *)data + h*bpl

此时已经发现不对了,看一下h和bpl的定义,分别为int h = bi.biHeight;int bpl = image.bytesPerLine();

以黑白图举例,假设为第一行,h为49999,bpl为50000,计算的偏移为2499950000,转为二进制0b 1001 0101 0000 0010 0011 0101 1011 0000

好,那么好,水落石出。

计算的结果为int型,第一个bit为符号位,实际作为偏移时,是作为负数-1795017296来参与计算的,也就是以为了实际的p指针是在向前偏移,所以会出现指针越界的问题。

这里也就找到了问题所在,去检查了一下其他不同通道数nbits的加载过程,都是由一样的处理,这里观察了一下计算指针时的变量,发现都是用了bpl这个变量

那么只需将类型修改为qint64即可,这样计算偏移时,会自动转为qint64的长度,也就不会越界。如下:

 qint64 bpl = image.bytesPerLine();

2、问题解决

打开 .\qt-everywhere-src-5.11.2\qtbase\src\gui\image\qbmphandler.cpp 文件

将其中 read_dib_body 函数的 int bpl = image.bytesPerLine(); ,修改为 qint64 bpl = image.bytesPerLine();

重新编译即可。

3、验证测试例程

下面为测试代码,验证修改后的可行性。

#include <QCoreApplication>
#include <QImage>
#include <QDebug>
#include <QString>
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 < maxTry; ++i)
 {
 qDebug().noquote() << 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() << "Creation failed, QImage returned null";
 break;
 }
 // Fill with gradient pattern (pseudo black & white)
 for (int y = 0; y < height; ++y) {
 uchar* line = img.scanLine(y);
 for (int x = 0; x < width; ++x) {
 line[x] = (x + y) % 256; // Grayscale value
 }
 }
 // Save as BMP
 if (!img.save(fileName)) {
 qDebug().noquote() << "Failed to save: " + fileName;
 break;
 }
 else {
 qDebug().noquote() << "Saved successfully: " + fileName;
 }
 }
 // Reload image
 QImage reloadImg;
 if (!reloadImg.load(fileName)) {
 qDebug().noquote() << "Failed to reload: " + fileName;
 break;
 }
 else {
 qDebug().noquote() << 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& e)
 {
 qDebug().noquote() << QString("Memory allocation failed: %1").arg(e.what());
 break;
 }
 try
 {
 {
 QImage img(width, height, QImage::Format_RGB888);
 if (img.isNull()) {
 qDebug().noquote() << "Creation failed, QImage returned null";
 break;
 }
 // Fill with pseudo-color pattern
 for (int y = 0; y < height; ++y) {
 uchar* line = img.scanLine(y);
 for (int x = 0; x < width; ++x) {
 line[x * 3 + 0] = (x + y) % 256; // R
 line[x * 3 + 1] = (2 * x + y) % 256; // G
 line[x * 3 + 2] = (x + 2 * y) % 256; // B
 }
 }
 // Save as BMP
 if (!img.save(fileName)) {
 qDebug().noquote() << "Failed to save: " + fileName;
 break;
 }
 else {
 qDebug().noquote() << "Saved successfully: " + fileName;
 }
 }
 // Reload image
 QImage reloadImg;
 if (!reloadImg.load(fileName)) {
 qDebug().noquote() << "Failed to reload: " + fileName;
 break;
 }
 else {
 qDebug().noquote() << 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& e)
 {
 qDebug().noquote() << QString("Memory allocation failed: %1").arg(e.what());
 break;
 }
 // Increase size
 width += step;
 height += step;
 }
 return 0;
}

执行结果

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

因为笔者的工作机内存仅有16G,过大的图片无法存放在电脑内存,就不再后续测试了

可以看到,相比之前是有优化效果的。

拓展思考

(1)其他类型图片的加载崩溃

其他类型图片的加载崩溃,笔者没有测试过,如果出现了,有概率和这种问题差不多,应当去同类型的QImageIOHandler派生中去查找问题,如QPngHandler,QXpmHandler,QXbmHandler,QPpmHandler

(2)代码可使用最大图片

struct Q_GUI_EXPORT QImageData { // internal image data
 QImageData();
 ~QImageData();
 static QImageData *create(const QSize &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<QRgb> colortable;
 uchar *data;
 QImage::Format format;
 qsizetype bytes_per_line;
 int ser_no; // serial number
 int detach_no;
 qreal dpmx; // dots per meter X (or 0)
 qreal dpmy; // dots per meter Y (or 0)
 QPoint offset; // 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<QString, QString> text;
 bool doImageIO(const QImage *image, QImageWriter* io, int quality) const;
 QPaintEngine *paintEngine;
};

这是 QImage 的实际存放数据的类,调用 create 函数时,实际申请内存的部分代码为:


QImageData * QImageData::create(const QSize &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) >> 5) << 2; // bytes per scanline (must be multiple of 4)
 ...
 d->bytes_per_line = bytes_per_line;
 d->nbytes = d->bytes_per_line*height;
 d->data = (uchar *)malloc(d->nbytes);
 ...
}

... 表示部分源码省略展示,可以看到 d->nbytes 为实际的可以申请的内存大小,其类型为 qsizetype nbytes; , 在 64 位系统上:qsizetype = long long, 所以不考虑电脑性能,实际可以申请和使用的最大的图片的容量为:

(2^63-1) / 1024 / 1024 / 1024 / 1024 ≈ 8388608 TB

可见,不考虑加载图片,仅是代码申请QImage情况下,可用性是够够的...

源代码

read_dib_body

static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset, qint64 startpos, QImage &image)
{
 QIODevice* d = s.device();
 if (d->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->isSequential())
 d->seek(startpos + BMP_FILEHDR_SIZE + bi.biSize); // goto start of colormap or masks
 if (bi.biSize >= BMP_WIN4) {
 red_mask = bi.biRedMask;
 green_mask = bi.biGreenMask;
 blue_mask = bi.biBlueMask;
 alpha_mask = bi.biAlphaMask;
 } else if (comp == BMP_BITFIELDS && (nbits == 16 || nbits == 32)) {
 if (d->read((char *)&red_mask, sizeof(red_mask)) != sizeof(red_mask))
 return false;
 if (d->read((char *)&green_mask, sizeof(green_mask)) != sizeof(green_mask))
 return false;
 if (d->read((char *)&blue_mask, sizeof(blue_mask)) != sizeof(blue_mask))
 return false;
 }
 bool transp = (comp == BMP_BITFIELDS) && 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 << nbits;
 if (ncols < 1 || ncols > 256) // sanity check - don't run out of mem if color table is broken
 return false;
 }
 if (bi.biHeight < 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 > 0) { // read color table
 image.setColorCount(ncols);
 uchar rgb[4];
 int rgb_len = t == BMP_OLD ? 3 : 4;
 for (int i=0; i<ncols; i++) {
 if (d->read((char *)rgb, rgb_len) != rgb_len)
 return false;
 image.setColor(i, qRgb(rgb[2],rgb[1],rgb[0]));
 if (d->atEnd()) // truncated file
 return false;
 }
 } else if (comp == BMP_BITFIELDS && (nbits == 16 || nbits == 32)) {
 red_shift = calc_shift(red_mask);
 if (((red_mask >> red_shift) + 1) == 0)
 return false;
 red_scale = 256 / ((red_mask >> red_shift) + 1);
 green_shift = calc_shift(green_mask);
 if (((green_mask >> green_shift) + 1) == 0)
 return false;
 green_scale = 256 / ((green_mask >> green_shift) + 1);
 blue_shift = calc_shift(blue_mask);
 if (((blue_mask >> blue_shift) + 1) == 0)
 return false;
 blue_scale = 256 / ((blue_mask >> blue_shift) + 1);
 alpha_shift = calc_shift(alpha_mask);
 if (((alpha_mask >> alpha_shift) + 1) == 0)
 return false;
 alpha_scale = 256 / ((alpha_mask >> alpha_shift) + 1);
 } else if (comp == BMP_RGB && (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 && 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>=0 && startpos + offset > d->pos()) {
 if (!d->isSequential())
 d->seek(startpos + offset); // start of image data
 }
 qint64 bpl = image.bytesPerLine();
 uchar *data = image.bits();
 if (nbits == 1) { // 1 bit BMP image
 while (--h >= 0) {
 if (d->read((char*)(data + h*bpl), bpl) != bpl)
 break;
 }
 if (ncols == 2 && qGray(image.color(0)) < qGray(image.color(1)))
 swapPixel01(&image); // pixel 0 is white!
 }
 else if (nbits == 4) { // 4 bit BMP image
 int buflen = ((w+7)/8)*4;
 uchar *buf = new uchar[buflen];
 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 < h) {
 if (!d->getChar((char *)&b))
 break;
 if (b == 0) { // escape code
 if (!d->getChar((char *)&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->getChar((char *)&tmp);
 x += tmp;
 d->getChar((char *)&tmp);
 y += tmp;
 }
 // Protection
 if ((uint)x >= (uint)w)
 x = w-1;
 if ((uint)y >= (uint)h)
 y = h-1;
 p = data + (h-y-1)*bpl + x;
 break;
 default: // absolute mode
 // Protection
 if (p + b > endp)
 b = endp-p;
 i = (c = b)/2;
 while (i--) {
 d->getChar((char *)&b);
 *p++ = b >> 4;
 *p++ = b & 0x0f;
 }
 if (c & 1) {
 unsigned char tmp;
 d->getChar((char *)&tmp);
 *p++ = tmp >> 4;
 }
 if ((((c & 3) + 1) & 2) == 2)
 d->getChar(0); // align on word boundary
 x += c;
 }
 } else { // encoded mode
 // Protection
 if (p + b > endp)
 b = endp-p;
 i = (c = b)/2;
 d->getChar((char *)&b); // 2 pixels to be repeated
 while (i--) {
 *p++ = b >> 4;
 *p++ = b & 0x0f;
 }
 if (c & 1)
 *p++ = b >> 4;
 x += c;
 }
 }
 } else if (comp == BMP_RGB) { // no compression
 memset(data, 0, h*bpl);
 while (--h >= 0) {
 if (d->read((char*)buf,buflen) != buflen)
 break;
 uchar *p = data + h*bpl;
 uchar *b = buf;
 for (int i=0; i<w/2; i++) { // convert nibbles to bytes
 *p++ = *b >> 4;
 *p++ = *b++ & 0x0f;
 }
 if (w & 1) // the last nibble
 *p = *b >> 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 < h) {
 if (!d->getChar((char *)&b))
 break;
 if (b == 0) { // escape code
 if (!d->getChar((char *)&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->getChar((char *)&tmp);
 x += tmp;
 d->getChar((char *)&tmp);
 y += tmp;
 }
 // Protection
 if ((uint)x >= (uint)w)
 x = w-1;
 if ((uint)y >= (uint)h)
 y = h-1;
 p = data + (h-y-1)*bpl + x;
 break;
 default: // absolute mode
 // Protection
 if (p + b > endp)
 b = endp-p;
 if (d->read((char *)p, b) != b)
 return false;
 if ((b & 1) == 1)
 d->getChar(0); // align on word boundary
 x += b;
 p += b;
 }
 } else { // encoded mode
 // Protection
 if (p + b > endp)
 b = endp-p;
 char tmp;
 d->getChar(&tmp);
 memset(p, tmp, b); // repeat pixel
 x += b;
 p += b;
 }
 }
 } 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*nbits+31)/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*)(b+1)<<8);
 if (nbits > 16)
 c |= *(uchar*)(b+2)<<16;
 if (nbits > 24)
 c |= *(uchar*)(b+3)<<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;
 }
 if (bi.biHeight < 0) {
 // Flip the image
 uchar *buf = new uchar[bpl];
 h = -bi.biHeight;
 for (int y = 0; y < 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;
}

© 原创作者:[Yzi321]
原文链接:https://www.cnblogs.com/Yzi321/p/19162705
转载请注明出处。
协议:CC BY 4.0

作者:Yzi321原文地址:https://www.cnblogs.com/Yzi321/p/19162705

%s 个评论

要回复文章请先登录注册