From feb6d9b20f843cf015c612519cd4d9146b0499df Mon Sep 17 00:00:00 2001 From: Mirco Miranda Date: Wed, 7 Sep 2022 14:03:33 +0000 Subject: [PATCH] Fix image allocation with Qt 6 To make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit() it is necessary to allocate the image with QImageIOHandler::allocateImage(). Note that not all plugins have been changed and some others are not tested in the CI (maybe due to missing libraries). PS: the following message is printed by QImageIOHandler::allocateImage() if the size is exceeded: "qt.gui.imageio: QImageIOHandler: Rejecting image as it exceeds the current allocation limit of XXX megabytes" --- src/imageformats/avif.cpp | 4 +++- src/imageformats/exr.cpp | 3 ++- src/imageformats/hdr.cpp | 3 ++- src/imageformats/heif.cpp | 3 ++- src/imageformats/jxl.cpp | 4 +++- src/imageformats/pcx.cpp | 9 +++++---- src/imageformats/pic.cpp | 4 ++-- src/imageformats/psd.cpp | 3 +-- src/imageformats/ras.cpp | 4 +--- src/imageformats/rgb.cpp | 3 ++- src/imageformats/tga.cpp | 5 +++-- src/imageformats/util_p.h | 32 ++++++++++++++++++++++++++++++++ src/imageformats/xcf.cpp | 38 +++++++++++++++++++++++++++----------- 13 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/imageformats/avif.cpp b/src/imageformats/avif.cpp index 935b3a9..8205689 100644 --- a/src/imageformats/avif.cpp +++ b/src/imageformats/avif.cpp @@ -12,6 +12,8 @@ #include #include "avif_p.h" +#include "util_p.h" + #include QAVIFHandler::QAVIFHandler() @@ -235,8 +237,8 @@ bool QAVIFHandler::decode_one_frame() resultformat = QImage::Format_RGB32; } } - QImage result(m_decoder->image->width, m_decoder->image->height, resultformat); + QImage result = imageAlloc(m_decoder->image->width, m_decoder->image->height, resultformat); if (result.isNull()) { qWarning("Memory cannot be allocated"); return false; diff --git a/src/imageformats/exr.cpp b/src/imageformats/exr.cpp index dafb472..43c7dd1 100644 --- a/src/imageformats/exr.cpp +++ b/src/imageformats/exr.cpp @@ -8,6 +8,7 @@ */ #include "exr_p.h" +#include "util_p.h" #include #include @@ -191,7 +192,7 @@ bool EXRHandler::read(QImage *outImage) width = dw.max.x - dw.min.x + 1; height = dw.max.y - dw.min.y + 1; - QImage image(width, height, QImage::Format_RGB32); + QImage image = imageAlloc(width, height, QImage::Format_RGB32); if (image.isNull()) { qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height); return false; diff --git a/src/imageformats/hdr.cpp b/src/imageformats/hdr.cpp index 8a0f3b4..7d3225f 100644 --- a/src/imageformats/hdr.cpp +++ b/src/imageformats/hdr.cpp @@ -7,6 +7,7 @@ */ #include "hdr_p.h" +#include "util_p.h" #include #include @@ -93,7 +94,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i uchar code; // Create dst image. - img = QImage(width, height, QImage::Format_RGB32); + img = imageAlloc(width, height, QImage::Format_RGB32); if (img.isNull()) { qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32"; return false; diff --git a/src/imageformats/heif.cpp b/src/imageformats/heif.cpp index 15f1b6d..0d4579f 100644 --- a/src/imageformats/heif.cpp +++ b/src/imageformats/heif.cpp @@ -9,6 +9,7 @@ #include "heif_p.h" #include "libheif/heif_cxx.h" +#include "util_p.h" #include #include @@ -432,7 +433,7 @@ bool HEIFHandler::ensureDecoder() return false; } - m_current_image = QImage(imageSize, target_image_format); + m_current_image = imageAlloc(imageSize, target_image_format); if (m_current_image.isNull()) { m_parseState = ParseHeicError; qWarning() << "Unable to allocate memory!"; diff --git a/src/imageformats/jxl.cpp b/src/imageformats/jxl.cpp index 975d1b6..dbaf45c 100644 --- a/src/imageformats/jxl.cpp +++ b/src/imageformats/jxl.cpp @@ -10,6 +10,8 @@ #include #include "jxl_p.h" +#include "util_p.h" + #include #include #include @@ -359,7 +361,7 @@ bool QJpegXLHandler::decode_one_frame() return false; } - m_current_image = QImage(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format); + m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format); if (m_current_image.isNull()) { qWarning("Memory cannot be allocated"); m_parseState = ParseJpegXLError; diff --git a/src/imageformats/pcx.cpp b/src/imageformats/pcx.cpp index 3ecd825..ddde33e 100644 --- a/src/imageformats/pcx.cpp +++ b/src/imageformats/pcx.cpp @@ -6,6 +6,7 @@ */ #include "pcx_p.h" +#include "util_p.h" #include #include @@ -262,7 +263,7 @@ static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine, 0); - img = QImage(header.width(), header.height(), QImage::Format_Mono); + img = imageAlloc(header.width(), header.height(), QImage::Format_Mono); img.setColorCount(2); if (img.isNull()) { @@ -294,7 +295,7 @@ static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header) QByteArray buf(header.BytesPerLine * 4, 0); QByteArray pixbuf(header.width(), 0); - img = QImage(header.width(), header.height(), QImage::Format_Indexed8); + img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8); img.setColorCount(16); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); @@ -338,7 +339,7 @@ static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine, 0); - img = QImage(header.width(), header.height(), QImage::Format_Indexed8); + img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8); img.setColorCount(256); if (img.isNull()) { @@ -388,7 +389,7 @@ static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header) QByteArray g_buf(header.BytesPerLine, 0); QByteArray b_buf(header.BytesPerLine, 0); - img = QImage(header.width(), header.height(), QImage::Format_RGB32); + img = imageAlloc(header.width(), header.height(), QImage::Format_RGB32); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); diff --git a/src/imageformats/pic.cpp b/src/imageformats/pic.cpp index f2adb34..350044c 100644 --- a/src/imageformats/pic.cpp +++ b/src/imageformats/pic.cpp @@ -14,8 +14,8 @@ */ #include "pic_p.h" - #include "rle_p.h" +#include "util_p.h" #include #include @@ -238,7 +238,7 @@ bool SoftimagePICHandler::read(QImage *image) } } - QImage img(m_header.width, m_header.height, fmt); + QImage img = imageAlloc(m_header.width, m_header.height, fmt); if (img.isNull()) { qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt; return false; diff --git a/src/imageformats/psd.cpp b/src/imageformats/psd.cpp index 7c4c669..7447fa1 100644 --- a/src/imageformats/psd.cpp +++ b/src/imageformats/psd.cpp @@ -34,7 +34,6 @@ */ #include "psd_p.h" - #include "util_p.h" #include @@ -1013,7 +1012,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) return false; } - img = QImage(header.width, header.height, format); + img = imageAlloc(header.width, header.height, format); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height); return false; diff --git a/src/imageformats/ras.cpp b/src/imageformats/ras.cpp index ebbb482..fe4c574 100644 --- a/src/imageformats/ras.cpp +++ b/src/imageformats/ras.cpp @@ -8,7 +8,6 @@ */ #include "ras_p.h" - #include "util_p.h" #include @@ -152,8 +151,7 @@ static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img) } // Allocate image - img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32); - + img = imageAlloc(ras.Width, ras.Height, QImage::Format_ARGB32); if (img.isNull()) { return false; } diff --git a/src/imageformats/rgb.cpp b/src/imageformats/rgb.cpp index 20f2f43..621363f 100644 --- a/src/imageformats/rgb.cpp +++ b/src/imageformats/rgb.cpp @@ -20,6 +20,7 @@ */ #include "rgb_p.h" +#include "util_p.h" #include #include @@ -324,7 +325,7 @@ bool SGIImage::readImage(QImage &img) return false; } - img = QImage(_xsize, _ysize, QImage::Format_RGB32); + img = imageAlloc(_xsize, _ysize, QImage::Format_RGB32); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize); return false; diff --git a/src/imageformats/tga.cpp b/src/imageformats/tga.cpp index c6bb5e3..e067bc8 100644 --- a/src/imageformats/tga.cpp +++ b/src/imageformats/tga.cpp @@ -17,6 +17,7 @@ */ #include "tga_p.h" +#include "util_p.h" #include @@ -172,7 +173,7 @@ struct TgaHeaderInfo { static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img) { // Create image. - img = QImage(tga.width, tga.height, QImage::Format_RGB32); + img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height); return false; @@ -184,7 +185,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img) const int numAlphaBits = tga.flags & 0xf; // However alpha exists only in the 32 bit format. if ((tga.pixel_size == 32) && (tga.flags & 0xf)) { - img = QImage(tga.width, tga.height, QImage::Format_ARGB32); + img = imageAlloc(tga.width, tga.height, QImage::Format_ARGB32); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height); return false; diff --git a/src/imageformats/util_p.h b/src/imageformats/util_p.h index a5b6f87..7a0d5de 100644 --- a/src/imageformats/util_p.h +++ b/src/imageformats/util_p.h @@ -1,10 +1,42 @@ /* SPDX-FileCopyrightText: 2022 Albert Astals Cid + SPDX-FileCopyrightText: 2022 Mirco Miranda SPDX-License-Identifier: LGPL-2.0-or-later */ +#ifndef UTIL_P_H +#define UTIL_P_H + #include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#endif + // QVector uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira static constexpr int kMaxQVectorSize = std::numeric_limits::max() - 32; + +// On Qt 6 to make the plugins fail to allocate if the image size is greater than QImageReader::allocationLimit() +// it is necessary to allocate the image with QImageIOHandler::allocateImage(). +inline QImage imageAlloc(const QSize &size, const QImage::Format &format) +{ + QImage img; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + img = QImage(size, format); +#else + if (!QImageIOHandler::allocateImage(size, format, &img)) { + img = QImage(); // paranoia + } +#endif + return img; +} + +inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format) +{ + return imageAlloc(QSize(width, height), format); +} + +#endif // UTIL_P_H diff --git a/src/imageformats/xcf.cpp b/src/imageformats/xcf.cpp index 0a84f17..75ac3fa 100644 --- a/src/imageformats/xcf.cpp +++ b/src/imageformats/xcf.cpp @@ -6,6 +6,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "util_p.h" #include "xcf_p.h" #include @@ -17,6 +18,9 @@ #include #include #include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#endif #include #include @@ -1099,6 +1103,18 @@ bool XCFImageFormat::composeTiles(XCFImage &xcf_image) return false; } + // Qt 6 image allocation limit calculation: we have to check the limit here because the image is splitted in + // tiles of 64x64 pixels. The required memory to build the image is at least doubled because tiles are loaded + // and then the final image is created by copying the tiles inside it. + // NOTE: on Windows to open a 10GiB image the plugin uses 28GiB of RAM +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + qint64 channels = 1 + (layer.type == RGB_GIMAGE ? 2 : 0) + (layer.type == RGBA_GIMAGE ? 3 : 0); + if (qint64(layer.width) * qint64(layer.height) * channels * 2ll / 1024ll / 1024ll > QImageReader::allocationLimit()) { + qCDebug(XCFPLUGIN) << "Rejecting image as it exceeds the current allocation limit of" << QImageReader::allocationLimit() << "megabytes"; + return false; + } +#endif + layer.image_tiles.resize(layer.nrows); if (layer.type == GRAYA_GIMAGE || layer.type == INDEXEDA_GIMAGE) { @@ -1917,7 +1933,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) switch (layer.type) { case RGB_GIMAGE: if (layer.opacity == OPAQUE_OPACITY) { - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_RGB32); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_RGB32); if (image.isNull()) { return false; } @@ -1926,7 +1942,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) } // else, fall through to 32-bit representation Q_FALLTHROUGH(); case RGBA_GIMAGE: - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32); if (image.isNull()) { return false; } @@ -1935,7 +1951,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) case GRAY_GIMAGE: if (layer.opacity == OPAQUE_OPACITY) { - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8); image.setColorCount(256); if (image.isNull()) { return false; @@ -1946,7 +1962,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) } // else, fall through to 32-bit representation Q_FALLTHROUGH(); case GRAYA_GIMAGE: - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32); if (image.isNull()) { return false; } @@ -1967,7 +1983,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) // or two-color palette. Have to ask about this... if (xcf_image.num_colors <= 2) { - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB); image.setColorCount(xcf_image.num_colors); if (image.isNull()) { return false; @@ -1975,7 +1991,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) image.fill(0); setPalette(xcf_image, image); } else if (xcf_image.num_colors <= 256) { - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8); image.setColorCount(xcf_image.num_colors); if (image.isNull()) { return false; @@ -1993,7 +2009,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) xcf_image.palette[1] = xcf_image.palette[0]; xcf_image.palette[0] = qRgba(255, 255, 255, 0); - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_MonoLSB); image.setColorCount(xcf_image.num_colors); if (image.isNull()) { return false; @@ -2009,7 +2025,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) } xcf_image.palette[0] = qRgba(255, 255, 255, 0); - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_Indexed8); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_Indexed8); image.setColorCount(xcf_image.num_colors); if (image.isNull()) { return false; @@ -2020,7 +2036,7 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) // No room for a transparent color, so this has to be promoted to // true color. (There is no equivalent PNG representation output // from The GIMP as of v1.2.) - image = QImage(xcf_image.width, xcf_image.height, QImage::Format_ARGB32); + image = imageAlloc(xcf_image.width, xcf_image.height, QImage::Format_ARGB32); if (image.isNull()) { return false; } @@ -2031,11 +2047,11 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) if (xcf_image.x_resolution > 0 && xcf_image.y_resolution > 0) { const float dpmx = xcf_image.x_resolution * INCHESPERMETER; - if (dpmx > std::numeric_limits::max()) { + if (dpmx > float(std::numeric_limits::max())) { return false; } const float dpmy = xcf_image.y_resolution * INCHESPERMETER; - if (dpmy > std::numeric_limits::max()) { + if (dpmy > float(std::numeric_limits::max())) { return false; } image.setDotsPerMeterX((int)dpmx);