diff options
Diffstat (limited to 'src/gui/image/qimagereader.cpp')
-rw-r--r-- | src/gui/image/qimagereader.cpp | 272 |
1 files changed, 128 insertions, 144 deletions
diff --git a/src/gui/image/qimagereader.cpp b/src/gui/image/qimagereader.cpp index 6139cf99c9..9366e9cbb1 100644 --- a/src/gui/image/qimagereader.cpp +++ b/src/gui/image/qimagereader.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only //#define QIMAGEREADER_DEBUG @@ -47,7 +11,6 @@ \inmodule QtGui \reentrant \ingroup painting - \ingroup io The most common way to read images is through QImage and QPixmap's constructors, or by calling QImage::load() and @@ -101,8 +64,8 @@ This can be disabled by setting the environment variable \c QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING. - \sa QImageWriter, QImageIOHandler, QImageIOPlugin, QMimeDatabase - \sa QImage::devicePixelRatio(), QPixmap::devicePixelRatio(), QIcon, QPainter::drawPixmap(), QPainter::drawImage(), Qt::AA_UseHighDpiPixmaps + \sa QImageWriter, QImageIOHandler, QImageIOPlugin, QMimeDatabase, QColorSpace + \sa QImage::devicePixelRatio(), QPixmap::devicePixelRatio(), QIcon, QPainter::drawPixmap(), QPainter::drawImage() */ /*! @@ -172,6 +135,10 @@ QT_BEGIN_NAMESPACE using namespace QImageReaderWriterHelpers; +using namespace Qt::StringLiterals; + +Q_TRACE_POINT(qtgui, QImageReader_read_before_reading, QImageReader *reader, const QString &filename); +Q_TRACE_POINT(qtgui, QImageReader_read_after_reading, QImageReader *reader, bool result); static QImageIOHandler *createReadHandlerHelper(QIODevice *device, const QByteArray &format, @@ -186,7 +153,7 @@ static QImageIOHandler *createReadHandlerHelper(QIODevice *device, QByteArray suffix; #ifndef QT_NO_IMAGEFORMATPLUGIN - static QBasicMutex mutex; + Q_CONSTINIT static QBasicMutex mutex; const auto locker = qt_scoped_lock(mutex); typedef QMultiMap<int, QString> PluginKeyMap; @@ -271,7 +238,7 @@ static QImageIOHandler *createReadHandlerHelper(QIODevice *device, } } } else { - const int testIndex = keyMap.key(QLatin1String(testFormat), -1); + const int testIndex = keyMap.key(QLatin1StringView(testFormat), -1); if (testIndex != -1) { QImageIOPlugin *plugin = qobject_cast<QImageIOPlugin *>(l->instance(testIndex)); if (plugin && plugin->capabilities(device, testFormat) & QImageIOPlugin::CanRead) { @@ -492,8 +459,12 @@ public: QString errorString; QImageReader *q; + + static int maxAlloc; }; +int QImageReaderPrivate::maxAlloc = 256; // 256 MB is enough for an 8K 64bpp image + /*! \internal */ @@ -515,9 +486,9 @@ QImageReaderPrivate::QImageReaderPrivate(QImageReader *qq) */ QImageReaderPrivate::~QImageReaderPrivate() { + delete handler; if (deleteDevice) delete device; - delete handler; } /*! @@ -525,6 +496,9 @@ QImageReaderPrivate::~QImageReaderPrivate() */ bool QImageReaderPrivate::initHandler() { + if (handler) + return true; + // check some preconditions if (!device || (!deleteDevice && !device->isOpen() && !device->open(QIODevice::ReadOnly))) { imageReaderError = QImageReader::DeviceError; @@ -534,7 +508,7 @@ bool QImageReaderPrivate::initHandler() // probe the file extension if (deleteDevice && !device->isOpen() && !device->open(QIODevice::ReadOnly) && autoDetectImageFormat) { - Q_ASSERT(qobject_cast<QFile*>(device) != 0); // future-proofing; for now this should always be the case, so... + Q_ASSERT(qobject_cast<QFile*>(device) != nullptr); // future-proofing; for now this should always be the case, so... QFile *file = static_cast<QFile *>(device); if (file->error() == QFileDevice::ResourceError) { @@ -555,14 +529,15 @@ bool QImageReaderPrivate::initHandler() int currentExtension = 0; QString fileName = file->fileName(); + bool fileIsOpen; do { - file->setFileName(fileName + QLatin1Char('.') - + QLatin1String(extensions.at(currentExtension++).constData())); - file->open(QIODevice::ReadOnly); - } while (!file->isOpen() && currentExtension < extensions.size()); + file->setFileName(fileName + u'.' + + QLatin1StringView(extensions.at(currentExtension++).constData())); + fileIsOpen = file->open(QIODevice::ReadOnly); + } while (!fileIsOpen && currentExtension < extensions.size()); - if (!device->isOpen()) { + if (!fileIsOpen) { imageReaderError = QImageReader::FileNotFoundError; errorString = QImageReader::tr("File not found"); file->setFileName(fileName); // restore the old file name @@ -571,7 +546,7 @@ bool QImageReaderPrivate::initHandler() } // assign a handler - if (!handler && (handler = createReadHandlerHelper(device, format, autoDetectImageFormat, ignoresFormatAndExtension)) == nullptr) { + if ((handler = createReadHandlerHelper(device, format, autoDetectImageFormat, ignoresFormatAndExtension)) == nullptr) { imageReaderError = QImageReader::UnsupportedFormatError; errorString = QImageReader::tr("Unsupported image format"); return false; @@ -584,7 +559,7 @@ bool QImageReaderPrivate::initHandler() */ void QImageReaderPrivate::getText() { - if (text.isEmpty() && (handler || initHandler()) && handler->supportsOption(QImageIOHandler::Description)) + if (text.isEmpty() && q->supportsOption(QImageIOHandler::Description)) text = qt_getImageTextFromDescription(handler->option(QImageIOHandler::Description).toString()); } @@ -765,7 +740,7 @@ bool QImageReader::decideFormatFromContent() const otherwise left unchanged. If the device is not already open, QImageReader will attempt to - open the device in \l QIODevice::ReadOnly mode by calling + open the device in \l {QIODeviceBase::}{ReadOnly} mode by calling open(). Note that this does not work for certain devices, such as QProcess, QTcpSocket and QUdpSocket, where more logic is required to open the device. @@ -774,12 +749,12 @@ bool QImageReader::decideFormatFromContent() const */ void QImageReader::setDevice(QIODevice *device) { + delete d->handler; + d->handler = nullptr; if (d->device && d->deleteDevice) delete d->device; d->device = device; d->deleteDevice = false; - delete d->handler; - d->handler = nullptr; d->text.clear(); } @@ -795,7 +770,7 @@ QIODevice *QImageReader::device() const /*! Sets the file name of QImageReader to \a fileName. Internally, QImageReader will create a QFile object and open it in \l - QIODevice::ReadOnly mode, and use this when reading images. + {QIODeviceBase::}{ReadOnly} mode, and use this when reading images. If \a fileName does not include a file extension (e.g., .png or .bmp), QImageReader will cycle through all supported extensions until it finds @@ -874,10 +849,7 @@ int QImageReader::quality() const */ QSize QImageReader::size() const { - if (!d->initHandler()) - return QSize(); - - if (d->handler->supportsOption(QImageIOHandler::Size)) + if (supportsOption(QImageIOHandler::Size)) return d->handler->option(QImageIOHandler::Size).toSize(); return QSize(); @@ -897,10 +869,7 @@ QSize QImageReader::size() const */ QImage::Format QImageReader::imageFormat() const { - if (!d->initHandler()) - return QImage::Format_Invalid; - - if (d->handler->supportsOption(QImageIOHandler::ImageFormat)) + if (supportsOption(QImageIOHandler::ImageFormat)) return (QImage::Format)d->handler->option(QImageIOHandler::ImageFormat).toInt(); return QImage::Format_Invalid; @@ -972,6 +941,10 @@ QRect QImageReader::clipRect() const support scaling), QImageReader will use QImage::scale() with Qt::SmoothScaling. + If only one dimension is set in \a size, the other one will be + computed from the image's \l {size()} {natural size} so as to + maintain the aspect ratio. + \sa scaledSize(), setClipRect(), setScaledClipRect() */ void QImageReader::setScaledSize(const QSize &size) @@ -1022,9 +995,7 @@ QRect QImageReader::scaledClipRect() const */ void QImageReader::setBackgroundColor(const QColor &color) { - if (!d->initHandler()) - return; - if (d->handler->supportsOption(QImageIOHandler::BackgroundColor)) + if (supportsOption(QImageIOHandler::BackgroundColor)) d->handler->setOption(QImageIOHandler::BackgroundColor, color); } @@ -1039,9 +1010,7 @@ void QImageReader::setBackgroundColor(const QColor &color) */ QColor QImageReader::backgroundColor() const { - if (!d->initHandler()) - return QColor(); - if (d->handler->supportsOption(QImageIOHandler::BackgroundColor)) + if (supportsOption(QImageIOHandler::BackgroundColor)) return qvariant_cast<QColor>(d->handler->option(QImageIOHandler::BackgroundColor)); return QColor(); } @@ -1056,9 +1025,7 @@ QColor QImageReader::backgroundColor() const */ bool QImageReader::supportsAnimation() const { - if (!d->initHandler()) - return false; - if (d->handler->supportsOption(QImageIOHandler::Animation)) + if (supportsOption(QImageIOHandler::Animation)) return d->handler->option(QImageIOHandler::Animation).toBool(); return false; } @@ -1070,10 +1037,7 @@ bool QImageReader::supportsAnimation() const */ QByteArray QImageReader::subType() const { - if (!d->initHandler()) - return QByteArray(); - - if (d->handler->supportsOption(QImageIOHandler::SubType)) + if (supportsOption(QImageIOHandler::SubType)) return d->handler->option(QImageIOHandler::SubType).toByteArray(); return QByteArray(); } @@ -1085,10 +1049,7 @@ QByteArray QImageReader::subType() const */ QList<QByteArray> QImageReader::supportedSubTypes() const { - if (!d->initHandler()) - return QList<QByteArray>(); - - if (d->handler->supportsOption(QImageIOHandler::SupportedSubTypes)) + if (supportsOption(QImageIOHandler::SupportedSubTypes)) return qvariant_cast<QList<QByteArray> >(d->handler->option(QImageIOHandler::SupportedSubTypes)); return QList<QByteArray>(); } @@ -1104,7 +1065,7 @@ QList<QByteArray> QImageReader::supportedSubTypes() const QImageIOHandler::Transformations QImageReader::transformation() const { int option = QImageIOHandler::TransformationNone; - if (d->initHandler() && d->handler->supportsOption(QImageIOHandler::ImageTransformation)) + if (supportsOption(QImageIOHandler::ImageTransformation)) option = d->handler->option(QImageIOHandler::ImageTransformation).toInt(); return QImageIOHandler::Transformations(option); } @@ -1138,10 +1099,6 @@ bool QImageReader::autoTransform() const case QImageReaderPrivate::DoNotApplyTransform: return false; case QImageReaderPrivate::UsePluginDefault: -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - if (d->initHandler()) - return d->handler->supportsOption(QImageIOHandler::TransformedByDefault); -#endif Q_FALLTHROUGH(); default: break; @@ -1150,39 +1107,6 @@ bool QImageReader::autoTransform() const } /*! - \since 5.6 - - This is an image format specific function that forces images with - gamma information to be gamma corrected to \a gamma. For image formats - that do not support gamma correction, this value is ignored. - - To gamma correct to a standard PC color-space, set gamma to \c 1/2.2. - - \sa gamma() -*/ -void QImageReader::setGamma(float gamma) -{ - if (d->initHandler() && d->handler->supportsOption(QImageIOHandler::Gamma)) - d->handler->setOption(QImageIOHandler::Gamma, gamma); -} - -/*! - \since 5.6 - - Returns the gamma level of the decoded image. If setGamma() has been - called and gamma correction is supported it will return the gamma set. - If gamma level is not supported by the image format, \c 0.0 is returned. - - \sa setGamma() -*/ -float QImageReader::gamma() const -{ - if (d->initHandler() && d->handler->supportsOption(QImageIOHandler::Gamma)) - return d->handler->option(QImageIOHandler::Gamma).toFloat(); - return 0.0; -} - -/*! Returns \c true if an image can be read for the device (i.e., the image format is supported, and the device seems to contain valid data); otherwise returns \c false. @@ -1256,31 +1180,48 @@ bool QImageReader::read(QImage *image) return false; } - if (!d->handler && !d->initHandler()) + if (!d->initHandler()) return false; + QSize scaledSize = d->scaledSize; + if ((scaledSize.width() <= 0 && scaledSize.height() > 0) || + (scaledSize.height() <= 0 && scaledSize.width() > 0)) { + // if only one dimension is given, let's try to calculate the second one + // based on the original image size and maintaining the aspect ratio + if (const QSize originalSize = size(); !originalSize.isEmpty()) { + if (scaledSize.width() <= 0) { + const auto ratio = qreal(scaledSize.height()) / originalSize.height(); + scaledSize.setWidth(qRound(originalSize.width() * ratio)); + } else { + const auto ratio = qreal(scaledSize.width()) / originalSize.width(); + scaledSize.setHeight(qRound(originalSize.height() * ratio)); + } + } + } + + const bool supportScaledSize = supportsOption(QImageIOHandler::ScaledSize) && scaledSize.isValid(); + const bool supportClipRect = supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull(); + const bool supportScaledClipRect = supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull(); + // set the handler specific options. - if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) { - if ((d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) - || d->clipRect.isNull()) { + if (supportScaledSize) { + if (supportClipRect || d->clipRect.isNull()) { // Only enable the ScaledSize option if there is no clip rect, or // if the handler also supports ClipRect. - d->handler->setOption(QImageIOHandler::ScaledSize, d->scaledSize); + d->handler->setOption(QImageIOHandler::ScaledSize, scaledSize); } } - if (d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) + if (supportClipRect) d->handler->setOption(QImageIOHandler::ClipRect, d->clipRect); - if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) + if (supportScaledClipRect) d->handler->setOption(QImageIOHandler::ScaledClipRect, d->scaledClipRect); - if (d->handler->supportsOption(QImageIOHandler::Quality)) + if (supportsOption(QImageIOHandler::Quality)) d->handler->setOption(QImageIOHandler::Quality, d->quality); // read the image + QString filename = fileName(); if (Q_TRACE_ENABLED(QImageReader_read_before_reading)) { - QString fileName = QStringLiteral("unknown"); - if (QFile *file = qobject_cast<QFile *>(d->device)) - fileName = file->fileName(); - Q_TRACE(QImageReader_read_before_reading, this, fileName); + Q_TRACE(QImageReader_read_before_reading, this, filename.isEmpty() ? u"unknown"_s : filename); } const bool result = d->handler->read(image); @@ -1295,9 +1236,9 @@ bool QImageReader::read(QImage *image) // provide default implementations for any unsupported image // options - if (d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) { - if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) { - if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + if (supportClipRect) { + if (supportScaledSize) { + if (supportScaledClipRect) { // all features are supported by the handler; nothing to do. } else { // the image is already scaled, so apply scaled clipping. @@ -1305,12 +1246,12 @@ bool QImageReader::read(QImage *image) *image = image->copy(d->scaledClipRect); } } else { - if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + if (supportScaledClipRect) { // supports scaled clipping but not scaling, most // likely a broken handler. } else { - if (d->scaledSize.isValid()) { - *image = image->scaled(d->scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + if (scaledSize.isValid()) { + *image = image->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } if (d->scaledClipRect.isValid()) { *image = image->copy(d->scaledClipRect); @@ -1318,8 +1259,8 @@ bool QImageReader::read(QImage *image) } } } else { - if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid() && d->clipRect.isNull()) { - if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + if (supportScaledSize && d->clipRect.isNull()) { + if (supportScaledClipRect) { // nothing to do (ClipRect is ignored!) } else { // provide all workarounds. @@ -1328,7 +1269,7 @@ bool QImageReader::read(QImage *image) } } } else { - if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + if (supportScaledClipRect) { // this makes no sense; a handler that supports // ScaledClipRect but not ScaledSize is broken, and we // can't work around it. @@ -1336,8 +1277,8 @@ bool QImageReader::read(QImage *image) // provide all workarounds. if (d->clipRect.isValid()) *image = image->copy(d->clipRect); - if (d->scaledSize.isValid()) - *image = image->scaled(d->scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + if (scaledSize.isValid()) + *image = image->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); if (d->scaledClipRect.isValid()) *image = image->copy(d->scaledClipRect); } @@ -1347,8 +1288,8 @@ bool QImageReader::read(QImage *image) // successful read; check for "@Nx" file name suffix and set device pixel ratio. static bool disableNxImageLoading = !qEnvironmentVariableIsEmpty("QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING"); if (!disableNxImageLoading) { - const QByteArray suffix = QFileInfo(fileName()).baseName().right(3).toLatin1(); - if (suffix.length() == 3 && suffix[0] == '@' && suffix[1] >= '2' && suffix[1] <= '9' && suffix[2] == 'x') + const QByteArray suffix = QFileInfo(filename).baseName().right(3).toLatin1(); + if (suffix.size() == 3 && suffix[0] == '@' && suffix[1] >= '2' && suffix[1] <= '9' && suffix[2] == 'x') image->setDevicePixelRatio(suffix[1] - '0'); } if (autoTransform()) @@ -1608,4 +1549,47 @@ QList<QByteArray> QImageReader::imageFormatsForMimeType(const QByteArray &mimeTy QImageReaderWriterHelpers::CanRead); } +/*! + \since 6.0 + + Returns the current allocation limit, in megabytes. + + \sa setAllocationLimit() +*/ +int QImageReader::allocationLimit() +{ + static int envLimit = []() { + bool ok = false; + int res = qEnvironmentVariableIntValue("QT_IMAGEIO_MAXALLOC", &ok); + return ok ? res : -1; + }(); + + return envLimit >= 0 ? envLimit : QImageReaderPrivate::maxAlloc; +} + +/*! + \since 6.0 + + Sets the allocation limit to \a mbLimit megabytes. Images that would + require a QImage memory allocation above this limit will be rejected. + If \a mbLimit is 0, the allocation size check will be disabled. + + This limit helps applications avoid unexpectedly large memory usage from + loading corrupt image files. It is normally not needed to change it. The + default limit is large enough for all commonly used image sizes. + + At runtime, this value may be overridden by the environment variable \c QT_IMAGEIO_MAXALLOC. + + \note The memory requirements are calculated for a minimum of 32 bits per pixel, since Qt will + typically convert an image to that depth when it is used in GUI. This means that the effective + allocation limit is significantly smaller than \a mbLimit when reading 1 bpp and 8 bpp images. + + \sa allocationLimit() +*/ +void QImageReader::setAllocationLimit(int mbLimit) +{ + if (mbLimit >= 0) + QImageReaderPrivate::maxAlloc = mbLimit; +} + QT_END_NAMESPACE |