Compare commits

..

1 Commits

Author SHA1 Message Date
Nicolas Fella
31038071fa Enable LSAN in CI
To check for leaks
2026-05-27 16:46:37 +02:00
17 changed files with 107 additions and 276 deletions

View File

@@ -9,3 +9,4 @@ Options:
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
cmake-options: "-DKIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
per-test-timeout: 90
enable-lsan: True

View File

@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.29)
set(KF_VERSION "6.28.0") # handled by release scripts
set(KF_DEP_VERSION "6.27.0") # handled by release scripts
set(KF_VERSION "6.27.0") # handled by release scripts
set(KF_DEP_VERSION "6.26.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.27.0 NO_MODULE)
find_package(ECM 6.26.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@@ -107,7 +107,7 @@ add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR
ecm_set_disabled_deprecation_versions(
QT 6.11.0
KF 6.27.0
KF 6.26.0
)
add_subdirectory(src)

View File

@@ -244,7 +244,7 @@ RGB.
Where possible, plugins support large images. By convention, many of the
large image plugins are limited to a maximum of 300,000 x 300,000 pixels.
Anyway, all plugins are also limited by the
`QImageReader::allocationLimit()`.
`QImageIOReader::allocationLimit()`.
> [!note]
> You can change the maximum limit of 300000 pixels by setting the constant
@@ -300,18 +300,8 @@ consumption proportional to the size of the image to be saved.
Normally this is not a source of problems because the affected plugins
are limited to maximum images of 2GiB or less.
Note that the value of `QImageReader::allocationLimit()` is only used when
allocating a new `QImage`. Since this parameter was created to limit damage
caused by corrupted files, any conversion of `QImage` (for example, with
`QImage::convertTo()`) is not subject to this limit.
On plugins for formats that support large images, progressive conversion has
been used or the maximum size of the image that can be saved has been limited.
Plugins that use external libraries don't always allow progressive decoding
(e.g., the JPEG series). In these cases, the memory required for reading
may be much larger than the entire decoded image. When the external library has
a maximum memory limit function, the value of `QImageReader::allocationLimit()`
is set.
### Non-RGB formats
@@ -459,13 +449,8 @@ plugin:
> [!caution]
> The plugin disabled by default due to security issues in [jxrlib](https://github.com/4creators/jxrlib):
> the upstream jxrlib is dead and there is no "hope" they will fix the issues.
>
> **You should not enable it unless you know what you are doing.**
> [!note]
> Security issues in the jxrlib discovered by the [KImageFormats OSS-Fuzz project](https://github.com/google/oss-fuzz/tree/master/projects/kimageformats)
> should be fixed in this [jxrlib fork](https://github.com/mircomir/jxrlib).
The following defines can be defined in cmake to modify the behavior of the
plugin:
- `JXR_DENY_FLOAT_IMAGE`: disables the use of float images and consequently
@@ -478,6 +463,9 @@ plugin:
it only wants (P)BGRA32bpp files (a format not supported by Qt). Only for
this format an hack is activated to guarantee total compatibility of the
plugin with Windows.
- `JXR_ENABLE_ADVANCED_METADATA`: enable metadata support (e.g. XMP). Some
distributions use an incomplete JXR library that does not allow reading
metadata, causing compilation errors.
### The KRA plugin

View File

@@ -63,7 +63,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
int argc = 0;
QCoreApplication a(argc, nullptr);
QImageReader::setAllocationLimit(512);
QImageReader::setAllocationLimit(2000);
QImageIOHandler* handler = new HANDLER();

View File

@@ -157,7 +157,7 @@ bool QAVIFHandler::ensureDecoder()
return true;
}
m_rawData = deviceRead(device(), kMaxQVectorSize);
m_rawData = device()->readAll();
m_rawAvifData.data = reinterpret_cast<const uint8_t *>(m_rawData.constData());
m_rawAvifData.size = m_rawData.size();
@@ -1181,7 +1181,7 @@ bool QAVIFHandler::jumpToNextImage()
if (m_decoder->imageIndex >= 0) {
if (m_decoder->imageCount < 2) {
m_parseState = ParseAvifSuccess;
return false;
return true;
}
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning

View File

@@ -3756,7 +3756,9 @@ qint32 DPELChunk::count() const
return 0;
}
auto cnt = i32(data(), 0);
if (cnt < 0 || cnt > KIF_MAX_IMAGE_CHANNELS) {
if (cnt < 0 || cnt > 128) {
// an image should have 3, 4 or 5 channels:
// 128 is enough to give an error.
cnt = 0;
}
return cnt;

View File

@@ -14,7 +14,6 @@
#include <libheif/heif_properties.h>
#include <QColorSpace>
#include <QImageReader>
#include <QLoggingCategory>
#include <QPointF>
#include <QSysInfo>
@@ -65,58 +64,6 @@ bool HEIFHandler::m_hej2_decoder_available = false;
bool HEIFHandler::m_hej2_encoder_available = false;
bool HEIFHandler::m_avci_decoder_available = false;
/*!
* \brief create_heif_reader_for_qiodevice
* Create a heif_reader structure that wraps a QIODevice for streaming
* \return heif_reader structure with callbacks delegating to QIODevice
*/
static heif_reader create_heif_reader_for_qiodevice()
{
heif_reader reader = {};
reader.reader_api_version = 1;
reader.get_position = [](void* userdata) -> int64_t {
QIODevice* device = static_cast<QIODevice*>(userdata);
return device->pos();
};
reader.read = [](void* data, size_t size, void* userdata) -> int {
QIODevice* device = static_cast<QIODevice*>(userdata);
qint64 bytesRead = device->read(static_cast<char*>(data), size);
if (bytesRead == -1) {
return -1; // Error
}
if (bytesRead != size) {
// We expected to read 'size' bytes but got less.
// This is an error because we should have known the size.
return -1; // Error
}
return 0; // Success
};
reader.seek = [](int64_t position, void* userdata) -> int {
QIODevice* device = static_cast<QIODevice*>(userdata);
return device->seek(position) ? 0 : -1;
};
reader.wait_for_file_size = [](int64_t target_size, void* userdata) -> heif_reader_grow_status {
QIODevice* device = static_cast<QIODevice*>(userdata);
if (target_size <= device->size()) {
return heif_reader_grow_status_size_reached;
}
return heif_reader_grow_status_size_beyond_eof;
};
// Version 2 functions set to NULL as we don't need them for simple streaming
reader.request_range = nullptr;
reader.preload_range_hint = nullptr;
reader.release_file_range = nullptr;
reader.release_error_msg = nullptr; // We use static error strings
return reader;
}
extern "C" {
static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata)
{
@@ -674,48 +621,17 @@ bool HEIFHandler::ensureDecoder()
return false;
}
QIODevice *dev = device();
if (dev == nullptr) {
qCCritical(LOG_HEIFPLUGIN) << "create_heif_reader_for_qiodevice error: device is null";
m_parseState = ParseHeicError;
return false;
}
QByteArray buffer = dev->peek(28);
const QByteArray buffer = device()->readAll();
if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer) && !HEIFHandler::isSupportedAVCI(buffer)) {
m_parseState = ParseHeicError;
return false;
}
struct heif_context *ctx = heif_context_alloc();
struct heif_error err;
struct heif_error err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
#if LIBHEIF_HAVE_VERSION(1, 19, 1)
if (auto heif_limits = heif_context_get_security_limits(ctx)) {
heif_limits->max_image_size_pixels = quint64(HEIF_MAX_IMAGE_WIDTH) * HEIF_MAX_IMAGE_HEIGHT;
heif_limits->max_memory_block_size = quint64(QImageReader::allocationLimit()) * 1024 * 1024;
#if LIBHEIF_HAVE_VERSION(1, 20, 0)
heif_limits->max_total_memory = heif_limits->max_memory_block_size;
#endif
err = heif_context_set_security_limits(ctx, heif_limits);
if (err.code) {
qCWarning(LOG_HEIFPLUGIN) << "heif_context_set_security_limits error:" << err.message;
heif_context_free(ctx);
m_parseState = ParseHeicError;
return false;
}
}
#endif
if (dev->isSequential()) {
buffer = deviceRead(dev, kMaxQVectorSize);
err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
} else {
heif_reader reader = create_heif_reader_for_qiodevice();
err = heif_context_read_from_reader(ctx, &reader, dev, nullptr);
}
if (err.code) {
qCWarning(LOG_HEIFPLUGIN) << "heif_context_read_from_reader error:" << err.message;
qCWarning(LOG_HEIFPLUGIN) << "heif_context_read_from_memory error:" << err.message;
heif_context_free(ctx);
m_parseState = ParseHeicError;
return false;

View File

@@ -12,6 +12,7 @@
#include <QColorSpace>
#include <QIODevice>
#include <QImage>
#include <QImageReader>
#include <QLoggingCategory>
#include <QThread>
@@ -186,7 +187,7 @@ public:
bool isImageValid(const opj_image_t *i) const
{
return i && i->comps && i->numcomps > 0 && i->numcomps < 256;
return i && i->comps && i->numcomps > 0;
}
void enableThreads(opj_codec_t *codec) const
@@ -358,9 +359,15 @@ public:
}
// OpenJPEG uses a shadow copy @32-bit/channel so we need to do a check
if (!checkImageSize(width, height, nchannels * 4)) {
qCCritical(LOG_JP2PLUGIN) << "Rejecting image as it exceeds the current allocation limit.";
return false;
const int allocationLimit = QImageReader::allocationLimit();
if (allocationLimit > 0) {
auto maxBytes = qint64(allocationLimit) * 1024 * 1024;
auto neededBytes = qint64(width) * height * nchannels * 4;
if (maxBytes > 0 && neededBytes > maxBytes) {
qCCritical(LOG_JP2PLUGIN) << "Allocation limit set to" << (maxBytes / 1024 / 1024) << "MiB but" << (neededBytes / 1024 / 1024)
<< "MiB are needed!";
return false;
}
}
return true;
@@ -377,11 +384,6 @@ public:
if (isImageValid(m_jp2_image)) {
auto &&c0 = m_jp2_image->comps[0];
auto tmp = QSize(c0.w, c0.h);
for (quint32 c = 1; c < m_jp2_image->numcomps; ++c) {
auto &&cc = m_jp2_image->comps[c];
if (QSize(cc.w, cc.h) != tmp)
tmp = QSize();
}
if (checkSizeLimits(tmp, m_jp2_image->numcomps))
sz = tmp;
}

View File

@@ -16,7 +16,6 @@
#include <jxl/cms.h>
#include <jxl/encode.h>
#include <jxl/memory_manager.h>
#include <jxl/thread_parallel_runner.h>
#include <string.h>
@@ -57,21 +56,6 @@ Q_LOGGING_CATEGORY(LOG_JXLPLUGIN, "kf.imageformats.plugins.jxl", QtWarningMsg)
#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
#endif
void *QtJXLMemoryManagerAlloc(void *opaque, size_t size)
{
if (opaque) {
size_t maxBytes = *(size_t*)opaque;
if (maxBytes && size > maxBytes)
return NULL;
}
return malloc(size);
}
void QtJXLMemoryManagerFree(void *, void *address)
{
free(address);
}
QJpegXLHandler::QJpegXLHandler()
: m_parseState(ParseJpegXLNotParsed)
, m_quality(90)
@@ -86,7 +70,6 @@ QJpegXLHandler::QJpegXLHandler()
, m_alpha_channel_id(0)
, m_input_image_format(QImage::Format_Invalid)
, m_target_image_format(QImage::Format_Invalid)
, m_maxBytes(size_t(QImageReader::allocationLimit()) * 1024 * 1024)
{
}
@@ -170,7 +153,7 @@ bool QJpegXLHandler::ensureDecoder()
return true;
}
m_rawData = deviceRead(device(), kMaxQVectorSize);
m_rawData = device()->readAll();
if (m_rawData.isEmpty()) {
return false;
@@ -182,14 +165,7 @@ bool QJpegXLHandler::ensureDecoder()
return false;
}
// Creating a simple memory manager
JxlMemoryManager memory_manager = {
.opaque = &m_maxBytes,
.alloc = QtJXLMemoryManagerAlloc,
.free = QtJXLMemoryManagerFree
};
// Creating the decoder (it makes a deep copy of memory manager)
m_decoder = JxlDecoderCreate(&memory_manager);
m_decoder = JxlDecoderCreate(nullptr);
if (!m_decoder) {
qCWarning(LOG_JXLPLUGIN, "ERROR: JxlDecoderCreate failed");
m_parseState = ParseJpegXLError;

View File

@@ -89,8 +89,6 @@ private:
QImage::Format m_target_image_format;
JxlPixelFormat m_input_pixel_format;
size_t m_maxBytes;
};
class QJpegXLPlugin : public QImageIOPlugin

View File

@@ -43,39 +43,41 @@ Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
#endif
/*!
* \brief JXR_DENY_FLOAT_IMAGE
* Support for float images
*
* When defined, disables the support for float images.
* \note Float images have values greater than 1 so they need an additional in place conversion.
* NOTE: Float images have values greater than 1 so they need an additional in place conversion.
*/
#ifndef JXR_DENY_FLOAT_IMAGE
// #define JXR_DENY_FLOAT_IMAGE // default commented
#endif
/*!
* \brief JXR_DISABLE_DEPTH_CONVERSION
*
* When defined, removes the needs of additional memory by disabling the conversion between
* Remove the needs of additional memory by disabling the conversion between
* different color depths (e.g. RGBA64bpp to RGBA32bpp).
* \note Leaving depth conversion enabled (default) ensures maximum read compatibility.
*
* NOTE: Leaving deptch conversion enabled (default) ensures maximum read compatibility.
*/
#ifndef JXR_DISABLE_DEPTH_CONVERSION
// #define JXR_DISABLE_DEPTH_CONVERSION // default commented
#endif
/*!
* \brief JXR_DISABLE_BGRA_HACK
*
* When defined, disables Windows compatibility for BGRs.
*
* Windows displays and opens JXR files correctly out of the box. Unfortunately it doesn't
* seem to open (P)RGBA @32bpp files as it only wants (P)BGRA32bpp files (a format not supported by Qt).
* Only for this format, an hack is activated to guarantee total compatibility of the plugin with Windows,
* at the cost of some overhead.
* Only for this format an hack is activated to guarantee total compatibility of the plugin with Windows.
*/
#ifndef JXR_DISABLE_BGRA_HACK
// #define JXR_DISABLE_BGRA_HACK // default commented
#endif
/*!
* The following functions are present in the Debian headers but not in the SUSE ones even if the source version is 1.0.1 on both.
*
* - ERR PKImageDecode_GetXMPMetadata_WMP(PKImageDecode *pID, U8 *pbXMPMetadata, U32 *pcbXMPMetadata);
* - ERR PKImageDecode_GetEXIFMetadata_WMP(PKImageDecode *pID, U8 *pbEXIFMetadata, U32 *pcbEXIFMetadata);
* - ERR PKImageDecode_GetGPSInfoMetadata_WMP(PKImageDecode *pID, U8 *pbGPSInfoMetadata, U32 *pcbGPSInfoMetadata);
* - ERR PKImageDecode_GetIPTCNAAMetadata_WMP(PKImageDecode *pID, U8 *pbIPTCNAAMetadata, U32 *pcbIPTCNAAMetadata);
* - ERR PKImageDecode_GetPhotoshopMetadata_WMP(PKImageDecode *pID, U8 *pbPhotoshopMetadata, U32 *pcbPhotoshopMetadata);
*
* As a result, their use is disabled by default. It is possible to activate their use by defining the
* JXR_ENABLE_ADVANCED_METADATA preprocessor directive
*/
// #define JXR_ENABLE_ADVANCED_METADATA
/* *** JXR_MAX_IMAGE_WIDTH and JXR_MAX_IMAGE_HEIGHT ***
* The maximum size in pixel allowed by the plugin.
@@ -87,23 +89,11 @@ Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
#define JXR_MAX_IMAGE_HEIGHT JXR_MAX_IMAGE_WIDTH
#endif
/*!
* \brief JXR_MAX_METADATA_SIZE
*
* XMP and EXIF maximum size in bytes.
*/
#ifndef JXR_MAX_METADATA_SIZE
#define JXR_MAX_METADATA_SIZE (4 * 1024 * 1024)
#endif
/*
* Compatibility with older libraries
/*!
* XMP and EXIF maximum size.
*/
#ifndef JXR_MAKEVERSION
#define JXR_MAKEVERSION(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))
#endif
#ifndef JXR_VERSION
#define JXR_VERSION JXR_MAKEVERSION(1, 1, 0)
#define JXR_MAX_METADATA_SIZE (4 * 1024 * 1024)
#endif
class JXRHandlerPrivate : public QSharedData
@@ -471,7 +461,7 @@ public:
if (pDecoder == nullptr) {
return xmp;
}
#if JXR_VERSION >= JXR_MAKEVERSION(1, 4, 0)
#ifdef JXR_ENABLE_ADVANCED_METADATA
quint32 size = 0;
if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, nullptr, &size) && size > 0 && size < JXR_MAX_METADATA_SIZE) {
QByteArray ba(size, 0);
@@ -973,12 +963,6 @@ private:
if (pCodecFactory == nullptr) {
return false;
}
#if JXR_VERSION >= JXR_MAKEVERSION(1, 4, 0)
// Prevents the library from making single large memory allocations.
// Note that it may still exceed it with multiple allocations.
PKAlloc_SetLimit(size_t(QImageReader::allocationLimit()) * 1024 * 1024);
#endif
if (auto err = pCodecFactory->CreateDecoderFromFile(qUtf8Printable(fileName()), &pDecoder)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::initDecoder() unable to create decoder:" << err;
return false;

View File

@@ -728,7 +728,7 @@ static bool IsValid(const PSDHeader &header)
}
// Specs tells: "Supported range is 1 to 56" but when the alpha channel is present the limit is 57:
// Photoshop does not make you add more (see also 53alphas.psd test case).
if (header.channel_count < 1 || header.channel_count > std::min(57, KIF_MAX_IMAGE_CHANNELS)) {
if (header.channel_count < 1 || header.channel_count > 57) {
qCDebug(LOG_PSDPLUGIN) << "PSD header: invalid number of channels" << header.channel_count;
return false;
}

View File

@@ -105,7 +105,7 @@ static bool IsSupported(const QoiHeader &head)
return false;
}
// Check if the header is a valid QOI header
if (head.Width == 0 || head.Height == 0 || head.Channels < 3 || head.Channels > 4 || head.Colorspace > 1) {
if (head.Width == 0 || head.Height == 0 || head.Channels < 3 || head.Colorspace > 1) {
return false;
}
// Set a reasonable upper limit

View File

@@ -89,14 +89,12 @@ const auto supported_formats = QSet<QByteArray>{
* \brief rawImageSize
* \return The size in pixels of the RAW image.
*/
static QSize rawImageSize(LibRaw *rawProcessor, qint32 *bytesPerPixel = nullptr)
static QSize rawImageSize(LibRaw *rawProcessor)
{
int w = 0, h = 0, c = 0, b = 0;
rawProcessor->get_mem_image_format(&w, &h, &c, &b);
if (bytesPerPixel) {
*bytesPerPixel = std::max(1, b * c / 8);
}
return QSize(w, h);
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
auto h = libraw_get_iheight(&rawProcessor->imgdata);
// flip & 4: taken from LibRaw code
return (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
}
inline int raw_scanf_one(const QByteArray &ba, const char *fmt, void *val)
@@ -383,9 +381,9 @@ QString createTag(libraw_gps_info_t gps, const char *tag)
if (gps.latref != '\0') {
auto lc = QLocale::c();
auto value = QStringLiteral("%1,%2%3")
.arg(lc.toString(gps.latitude[0], 'f', 0),
lc.toString(gps.latitude[1] + gps.latitude[2] / 60, 'f', 4),
QChar::fromLatin1(gps.latref));
.arg(lc.toString(gps.latitude[0], 'f', 0))
.arg(lc.toString(gps.latitude[1] + gps.latitude[2] / 60, 'f', 4))
.arg(QChar::fromLatin1(gps.latref));
return createTag(value, tag);
}
}
@@ -393,9 +391,9 @@ QString createTag(libraw_gps_info_t gps, const char *tag)
if (gps.longref != '\0') {
auto lc = QLocale::c();
auto value = QStringLiteral("%1,%2%3")
.arg(lc.toString(gps.longitude[0], 'f', 0),
lc.toString(gps.longitude[1] + gps.longitude[2] / 60, 'f', 4),
QChar::fromLatin1(gps.longref));
.arg(lc.toString(gps.longitude[0], 'f', 0))
.arg(lc.toString(gps.longitude[1] + gps.longitude[2] / 60, 'f', 4))
.arg(QChar::fromLatin1(gps.longref));
return createTag(value, tag);
}
}
@@ -690,7 +688,7 @@ bool LoadTHUMB(QImageIOHandler *handler, QImage &img)
return false;
}
#else
auto all = deviceRead(device(), kMaxQVectorSize);
auto all = device->readAll();
if (rawProcessor->open_buffer(all.data(), all.size()) != LIBRAW_SUCCESS) {
return false;
}
@@ -753,23 +751,18 @@ bool LoadRAW(QImageIOHandler *handler, QImage &img)
return false;
}
#else
auto ba = deviceRead(device(), kMaxQVectorSize);
auto ba = device->readAll();
if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) {
return false;
}
#endif
// *** Limiting the maximum image size on a reasonable size
qint32 bytesPerPixel = 0;
auto size = rawImageSize(rawProcessor.get(), &bytesPerPixel);
auto size = rawImageSize(rawProcessor.get());
if (size.width() >= RAW_MAX_IMAGE_WIDTH || size.height() >= RAW_MAX_IMAGE_HEIGHT) {
qCWarning(LOG_RAWPLUGIN) << "The maximum image size is limited to" << (RAW_MAX_IMAGE_WIDTH - 1) << "x" << (RAW_MAX_IMAGE_HEIGHT - 1) << "px";
return false;
}
if (!checkImageSize(size, bytesPerPixel)) {
qCWarning(LOG_RAWPLUGIN) << "Rejecting image as it exceeds the current allocation limit.";
return false;
}
// *** Unpacking selected image
if (rawProcessor->unpack() != LIBRAW_SUCCESS) {
@@ -1063,7 +1056,7 @@ bool RAWHandler::canRead(QIODevice *device)
LibRaw_QIODevice stream(device);
auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS;
#else
auto ba = deviceRead(device(), kMaxQVectorSize);
auto ba = device->readAll();
auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS;
#endif

View File

@@ -300,17 +300,21 @@ bool SGIImagePrivate::readImage(QImage &img)
return false;
}
if (_zsize > KIF_MAX_IMAGE_CHANNELS) {
qCDebug(LOG_RGBPLUGIN) << "Too many channels: the plugin is limited to" << KIF_MAX_IMAGE_CHANNELS << "channels";
return false;
}
img = imageAlloc(size(), format());
if (img.isNull()) {
qCWarning(LOG_RGBPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize);
return false;
}
if (_zsize > 4) {
// qCDebug(LOG_RGBPLUGIN) << "using first 4 of " << _zsize << " channels";
// Only let this continue if it won't cause a int overflow later
// this is most likely a broken file anyway
if (_ysize > std::numeric_limits<int>::max() / _zsize) {
return false;
}
}
_numrows = _ysize * _zsize;
if (_rle) {
@@ -349,7 +353,7 @@ bool SGIImagePrivate::readImage(QImage &img)
return false;
}
_data = deviceRead(_dev, kMaxQVectorSize);
_data = _dev->readAll();
// sanity check
if (_rle) {

View File

@@ -12,15 +12,9 @@
#include <QImage>
#include <QImageIOHandler>
#include <QImageReader>
#include <QIODevice>
#include <QPixelFormat>
// Default maximum number of channels (do not exceed 256).
#ifndef KIF_MAX_IMAGE_CHANNELS
#define KIF_MAX_IMAGE_CHANNELS 60
#endif
// Default maximum width and height for the large image plugins.
#ifndef KIF_LARGE_IMAGE_PIXEL_LIMIT
#define KIF_LARGE_IMAGE_PIXEL_LIMIT 300000
@@ -94,7 +88,7 @@ enum class ImageInitToZero
* \brief imageAlloc
* Helper function to initialize framework images.
* \param size The image size.
* \param format The image format.
* \param format The image format,
* \param init Whether and which images should be initialized to zero.
* \return The allocated image or a null image on error.
*/
@@ -109,60 +103,21 @@ inline QImage imageAlloc(const QSize &size, const QImage::Format &format, const
auto isFloat = pixelFormat.typeInterpretation() == QPixelFormat::FloatingPoint;
auto isPremul = pixelFormat.premultiplied();
if (init == ImageInitToZero::All) {
img.fill(Qt::black);
img.fill(0);
} else if (isFloat && (init == ImageInitToZero::FPOnly || init == ImageInitToZero::FPAndPremul)) {
img.fill(Qt::black);
img.fill(0);
} else if (isPremul && (init == ImageInitToZero::PremulOnly || init == ImageInitToZero::FPAndPremul)) {
img.fill(Qt::black);
img.fill(0);
}
}
return img;
}
/*!
* \brief imageAlloc
* Helper function to initialize framework images.
* \param width The image width.
* \param height The image height.
* \param format The image format.
* \param init Whether and which images should be initialized to zero.
* \return The allocated image or a null image on error.
*/
inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format, const ImageInitToZero& init = ImageInitToZero::None)
{
return imageAlloc(QSize(width, height), format, init);
}
/*!
* \brief checkImageSize
* Helper function to make sure the image size does not exceed the limit set in Qt.
* \param width The image width.
* \param height The image height.
* \param bytesPerPixel The number of bytes for each pixel of the image.
* \return True if the limit is respected, false otherwise.
*/
inline bool checkImageSize(qint32 width, qint32 height, qint32 bytesPerPixel)
{
size_t maxBytes = size_t(QImageReader::allocationLimit()) * 1024 * 1024;
if (maxBytes == 0) {
return true;
}
size_t bytes = size_t(width) * height * bytesPerPixel;
return bytes <= maxBytes;
}
/*!
* \brief checkImageSize
* Helper function to make sure the image size does not exceed the limit set in Qt.
* \param size The image size.
* \param bytesPerPixel The number of bytes for each pixel of the image.
* \return True if the limit is respected, false otherwise.
*/
inline bool checkImageSize(const QSize& size, qint32 bytesPerPixel)
{
return checkImageSize(size.width(), size.height(), bytesPerPixel);
}
template<class TI, class SF> // SF = source FP, TI = target INT
TI qRoundOrZero_T(SF d, bool *ok = nullptr)
{
@@ -244,7 +199,7 @@ static QByteArray deviceRead(QIODevice *d, qint64 maxSize)
return{};
}
const qint64 blockSize = 1024 * 1024;
const qint64 blockSize = 32 * 1024 * 1024;
auto devSize = d->isSequential() ? qint64() : d->size();
if (devSize > 0) {

View File

@@ -12,15 +12,16 @@
#include <QColorSpace>
#include <QIODevice>
#include <QImage>
#include <QImageReader>
#include <QList>
#include <QLoggingCategory>
#include <QPainter>
#include <QStack>
#include <QtEndian>
// Float images can be disabled to reduce memory usage.
// Unfortunately enabling/disabling this define results in slightly different images,
// so leave the default if possible.
#ifndef XCF_QT5_SUPPORT
// Float images are not supported by Qt 5 and can be disabled in QT 6 to reduce memory usage.
// Unfortunately enabling/disabling this define results in slightly different images, so leave the default if possible.
#define USE_FLOAT_IMAGES // default uncommented
// Let's set a "reasonable" maximum size
@@ -30,6 +31,11 @@
#ifndef XCF_MAX_IMAGE_HEIGHT
#define XCF_MAX_IMAGE_HEIGHT XCF_MAX_IMAGE_WIDTH
#endif
#else
// While it is possible to have images larger than 32767 pixels, QPainter seems unable to go beyond this threshold using Qt 5.
#define XCF_MAX_IMAGE_WIDTH 32767
#define XCF_MAX_IMAGE_HEIGHT 32767
#endif
#ifdef USE_FLOAT_IMAGES
#include <qrgbafloat.h>
@@ -1378,14 +1384,20 @@ bool XCFImageFormat::composeTiles(XCFImage &xcf_image)
}
}
// The required memory to build the image is at least doubled because tiles are loaded
#ifndef XCF_QT5_SUPPORT
// Qt 6 image allocation limit calculation: we have to check the limit here because the image is split 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
const qint64 channels = 1 + (layer.type == RGB_GIMAGE ? 2 : 0) + (layer.type == RGBA_GIMAGE ? 3 : 0);
if (!checkImageSize(layer.width, layer.height, channels * 2)) {
qCDebug(XCFPLUGIN) << "Rejecting image as it exceeds the current allocation limit.";
return false;
const int allocationLimit = QImageReader::allocationLimit();
if (allocationLimit > 0) {
if (qint64(layer.width) * qint64(layer.height) * channels * 2ll / 1024ll / 1024ll > allocationLimit) {
qCDebug(XCFPLUGIN) << "Rejecting image as it exceeds the current allocation limit of" << allocationLimit << "megabytes";
return false;
}
}
#endif
layer.image_tiles.resize(layer.nrows);