動態

詳情 返回 返回

關於 QImage 加載本地大圖片的崩潰問題 - 動態 詳情

版權聲明:
本文為原創內容,作者:[Yzi321]。
轉載請註明出處:
原博主主頁:https://www.cnblogs.com/Yzi321
本文鏈接:https://www.cnblogs.com/Yzi321/p/19162705
許可協議:CC BY 4.0

更新

因為重新編譯太過於繁瑣,這裏筆者把QImageReader源碼挑選出部分核心功能,修改該qt bug,改為MXTImageReader模塊。再將此模塊作為源碼模塊放到需要的項目中,即可實現大圖片的加載。

例程環境:VS2022 Qt5.11.2 MSVC2017_x64

這是文件下載鏈接1(GoogleDrive)、文件下載鏈接2(百度網盤)。

目錄

  • 問題場景
  • 問題查找
  • 問題解決
  • 測試驗證
    • 執行結果
  • 拓展思考
    • 其他類型圖片的加載崩潰
    • 代碼可使用最大圖片
  • 源代碼

問題場景:
圖片格式: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

Add a new 評論

Some HTML is okay.