diff options
Diffstat (limited to 'src/gui/image/qimage.cpp')
-rw-r--r-- | src/gui/image/qimage.cpp | 1106 |
1 files changed, 937 insertions, 169 deletions
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 9875881623..3bbf21320e 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2022 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 #include "qimage.h" @@ -57,6 +21,7 @@ #include <stdlib.h> #include <limits.h> #include <qpa/qplatformpixmap.h> +#include <private/qcolorspace_p.h> #include <private/qcolortransform_p.h> #include <private/qmemrotate_p.h> #include <private/qimagescale_p.h> @@ -71,24 +36,25 @@ #include <private/qfont_p.h> #if QT_CONFIG(thread) -#include "qsemaphore.h" -#include "qthreadpool.h" +#include <qsemaphore.h> +#include <qthreadpool.h> +#include <private/qthreadpool_p.h> #endif #include <qtgui_tracepoints_p.h> +#include <memory> + QT_BEGIN_NAMESPACE +class QCmyk32; + +using namespace Qt::StringLiterals; // MSVC 19.28 does show spurious warning "C4723: potential divide by 0" for code that divides // by height() in release builds. Anyhow, all the code paths in this file are only executed // for valid QImage's, where height() cannot be 0. Therefore disable the warning. QT_WARNING_DISABLE_MSVC(4723) -static inline bool isLocked(QImageData *data) -{ - return data != nullptr && data->is_locked; -} - #if defined(Q_CC_DEC) && defined(__alpha) && (__DECCXX_VER-0 >= 50190001) #pragma message disable narrowptr #endif @@ -100,6 +66,17 @@ static inline bool isLocked(QImageData *data) return QImage(); \ } +Q_TRACE_PREFIX(qtgui, + "#include <qimagereader.h>" +); + +Q_TRACE_METADATA(qtgui, +"ENUM { } QImage::Format;" \ +"FLAGS { } Qt::ImageConversionFlags;" +); + +Q_TRACE_PARAM_REPLACE(Qt::AspectRatioMode, int); +Q_TRACE_PARAM_REPLACE(Qt::TransformationMode, int); static QImage rotated90(const QImage &src); static QImage rotated180(const QImage &src); @@ -107,7 +84,7 @@ static QImage rotated270(const QImage &src); static int next_qimage_serial_number() { - static QBasicAtomicInt serial = Q_BASIC_ATOMIC_INITIALIZER(0); + Q_CONSTINIT static QBasicAtomicInt serial = Q_BASIC_ATOMIC_INITIALIZER(0); return 1 + serial.fetchAndAddRelaxed(1); } @@ -119,7 +96,7 @@ QImageData::QImageData() dpmx(qt_defaultDpiX() * 100 / qreal(2.54)), dpmy(qt_defaultDpiY() * 100 / qreal(2.54)), offset(0, 0), own_data(true), ro_data(false), has_alpha_clut(false), - is_cached(false), is_locked(false), cleanupFunction(nullptr), cleanupInfo(nullptr), + is_cached(false), cleanupFunction(nullptr), cleanupInfo(nullptr), paintEngine(nullptr) { } @@ -131,7 +108,7 @@ QImageData::QImageData() Creates a new image data. Returns \nullptr if invalid parameters are give or anything else failed. */ -QImageData * QImageData::create(const QSize &size, QImage::Format format) +QImageData * Q_TRACE_INSTRUMENT(qtgui) QImageData::create(const QSize &size, QImage::Format format) { if (size.isEmpty() || format <= QImage::Format_Invalid || format >= QImage::NImageFormats) return nullptr; // invalid parameter(s) @@ -145,7 +122,7 @@ QImageData * QImageData::create(const QSize &size, QImage::Format format) if (!params.isValid()) return nullptr; - QScopedPointer<QImageData> d(new QImageData); + auto d = std::make_unique<QImageData>(); switch (format) { case QImage::Format_Mono: @@ -173,7 +150,7 @@ QImageData * QImageData::create(const QSize &size, QImage::Format format) return nullptr; d->ref.ref(); - return d.take(); + return d.release(); } QImageData::~QImageData() @@ -329,6 +306,7 @@ bool QImageData::checkForAlphaPixels() const case QImage::Format_RGBX64: case QImage::Format_RGBX16FPx4: case QImage::Format_RGBX32FPx4: + case QImage::Format_CMYK8888: break; case QImage::Format_Invalid: case QImage::NImageFormats: @@ -385,7 +363,7 @@ bool QImageData::checkForAlphaPixels() const refer to the \l{How to Create Qt Plugins}{Plugin HowTo}. \warning Painting on a QImage with the format - QImage::Format_Indexed8 is not supported. + QImage::Format_Indexed8 or QImage::Format_CMYK8888 is not supported. \tableofcontents @@ -453,7 +431,7 @@ bool QImageData::checkForAlphaPixels() const The color of a pixel can be retrieved by passing its coordinates to the pixel() function. The pixel() function returns the color - as a QRgb value indepedent of the image's format. + as a QRgb value independent of the image's format. In case of monochrome and 8-bit images, the colorCount() and colorTable() functions provide information about the color @@ -641,8 +619,8 @@ bool QImageData::checkForAlphaPixels() const \endtable - \sa QImageReader, QImageWriter, QPixmap, QSvgRenderer, {Image Composition Example}, - {Image Viewer Example}, {Scribble Example}, {Pixelator Example} + \sa QImageReader, QImageWriter, QPixmap, QSvgRenderer, + {Image Composition Example}, {Scribble Example} */ /*! @@ -735,40 +713,62 @@ bool QImageData::checkForAlphaPixels() const The unused bits are always zero. \value Format_ARGB4444_Premultiplied The image is stored using a premultiplied 16-bit ARGB format (4-4-4-4). - \value Format_RGBX8888 The image is stored using a 32-bit byte-ordered RGB(x) format (8-8-8-8). - This is the same as the Format_RGBA8888 except alpha must always be 255. (added in Qt 5.2) - \value Format_RGBA8888 The image is stored using a 32-bit byte-ordered RGBA format (8-8-8-8). + \value [since 5.2] + Format_RGBX8888 The image is stored using a 32-bit byte-ordered RGB(x) format (8-8-8-8). + This is the same as the Format_RGBA8888 except alpha must always be 255. + \value [since 5.2] + Format_RGBA8888 The image is stored using a 32-bit byte-ordered RGBA format (8-8-8-8). Unlike ARGB32 this is a byte-ordered format, which means the 32bit encoding differs between big endian and little endian architectures, being respectively (0xRRGGBBAA) and (0xAABBGGRR). The order of the colors - is the same on any architecture if read as bytes 0xRR,0xGG,0xBB,0xAA. (added in Qt 5.2) - \value Format_RGBA8888_Premultiplied The image is stored using a - premultiplied 32-bit byte-ordered RGBA format (8-8-8-8). (added in Qt 5.2) - \value Format_BGR30 The image is stored using a 32-bit BGR format (x-10-10-10). (added in Qt 5.4) - \value Format_A2BGR30_Premultiplied The image is stored using a 32-bit premultiplied ABGR format (2-10-10-10). (added in Qt 5.4) - \value Format_RGB30 The image is stored using a 32-bit RGB format (x-10-10-10). (added in Qt 5.4) - \value Format_A2RGB30_Premultiplied The image is stored using a 32-bit premultiplied ARGB format (2-10-10-10). (added in Qt 5.4) - \value Format_Alpha8 The image is stored using an 8-bit alpha only format. (added in Qt 5.5) - \value Format_Grayscale8 The image is stored using an 8-bit grayscale format. (added in Qt 5.5) - \value Format_Grayscale16 The image is stored using an 16-bit grayscale format. (added in Qt 5.13) - \value Format_RGBX64 The image is stored using a 64-bit halfword-ordered RGB(x) format (16-16-16-16). - This is the same as the Format_RGBA64 except alpha must always be 65535. (added in Qt 5.12) - \value Format_RGBA64 The image is stored using a 64-bit halfword-ordered RGBA format (16-16-16-16). (added in Qt 5.12) - \value Format_RGBA64_Premultiplied The image is stored using a premultiplied 64-bit halfword-ordered - RGBA format (16-16-16-16). (added in Qt 5.12) - \value Format_BGR888 The image is stored using a 24-bit BGR format. (added in Qt 5.14) - \value Format_RGBX16FPx4 The image is stored using a 4 16-bit halfword floating point RGBx format (16FP-16FP-16FP-16FP). - This is the same as the Format_RGBA16FPx4 except alpha must always be 1.0. (added in Qt 6.2) - \value Format_RGBA16FPx4 The image is stored using a 4 16-bit halfword floating point RGBA format (16FP-16FP-16FP-16FP). (added in Qt 6.2) - \value Format_RGBA16FPx4_Premultiplied The image is stored using a premultiplied 4 16-bit halfword floating point - RGBA format (16FP-16FP-16FP-16FP). (added in Qt 6.2) - \value Format_RGBX32FPx4 The image is stored using a 4 32-bit floating point RGBx format (32FP-32FP-32FP-32FP). - This is the same as the Format_RGBA32FPx4 except alpha must always be 1.0. (added in Qt 6.2) - \value Format_RGBA32FPx4 The image is stored using a 4 32-bit floating point RGBA format (32FP-32FP-32FP-32FP). (added in Qt 6.2) - \value Format_RGBA32FPx4_Premultiplied The image is stored using a premultiplied 4 32-bit floating point - RGBA format (32FP-32FP-32FP-32FP). (added in Qt 6.2) - - \note Drawing into a QImage with QImage::Format_Indexed8 is not + is the same on any architecture if read as bytes 0xRR,0xGG,0xBB,0xAA. + \value [since 5.2] + Format_RGBA8888_Premultiplied The image is stored using a + premultiplied 32-bit byte-ordered RGBA format (8-8-8-8). + \value [since 5.4] + Format_BGR30 The image is stored using a 32-bit BGR format (x-10-10-10). + \value [since 5.4] + Format_A2BGR30_Premultiplied The image is stored using a 32-bit premultiplied ABGR format (2-10-10-10). + \value [since 5.4] + Format_RGB30 The image is stored using a 32-bit RGB format (x-10-10-10). + \value [since 5.4] + Format_A2RGB30_Premultiplied The image is stored using a 32-bit premultiplied ARGB format (2-10-10-10). + \value [since 5.5] + Format_Alpha8 The image is stored using an 8-bit alpha only format. + \value [since 5.5] + Format_Grayscale8 The image is stored using an 8-bit grayscale format. + \value [since 5.13] + Format_Grayscale16 The image is stored using an 16-bit grayscale format. + \value [since 5.12] + Format_RGBX64 The image is stored using a 64-bit halfword-ordered RGB(x) format (16-16-16-16). + This is the same as the Format_RGBA64 except alpha must always be 65535. + \value [since 5.12] + Format_RGBA64 The image is stored using a 64-bit halfword-ordered RGBA format (16-16-16-16). + \value [since 5.12] + Format_RGBA64_Premultiplied The image is stored using a premultiplied 64-bit halfword-ordered + RGBA format (16-16-16-16). + \value [since 5.14] + Format_BGR888 The image is stored using a 24-bit BGR format. + \value [since 6.2] + Format_RGBX16FPx4 The image is stored using a four 16-bit halfword floating point RGBx format (16FP-16FP-16FP-16FP). + This is the same as the Format_RGBA16FPx4 except alpha must always be 1.0. + \value [since 6.2] + Format_RGBA16FPx4 The image is stored using a four 16-bit halfword floating point RGBA format (16FP-16FP-16FP-16FP). + \value [since 6.2] + Format_RGBA16FPx4_Premultiplied The image is stored using a premultiplied four 16-bit halfword floating point + RGBA format (16FP-16FP-16FP-16FP). + \value [since 6.2] + Format_RGBX32FPx4 The image is stored using a four 32-bit floating point RGBx format (32FP-32FP-32FP-32FP). + This is the same as the Format_RGBA32FPx4 except alpha must always be 1.0. + \value [since 6.2] + Format_RGBA32FPx4 The image is stored using a four 32-bit floating point RGBA format (32FP-32FP-32FP-32FP). + \value [since 6.2] + Format_RGBA32FPx4_Premultiplied The image is stored using a premultiplied four 32-bit floating point + RGBA format (32FP-32FP-32FP-32FP). + \value [since 6.8] + Format_CMYK8888 The image is stored using a 32-bit byte-ordered CMYK format. + + \note Drawing into a QImage with format QImage::Format_Indexed8 or QImage::Format_CMYK8888 is not supported. \note Avoid most rendering directly to most of these formats using QPainter. Rendering @@ -844,7 +844,7 @@ QImageData *QImageData::create(uchar *data, int width, int height, qsizetype bp // recalculate the total with this value params.bytesPerLine = bpl; - if (mul_overflow<qsizetype>(bpl, height, ¶ms.totalSize)) + if (qMulOverflow<qsizetype>(bpl, height, ¶ms.totalSize)) return nullptr; } @@ -1030,7 +1030,7 @@ QImage::QImage(const char * const xpm[]) if (!xpm) return; if (!qt_read_xpm_image_or_array(nullptr, xpm, *this)) - // Issue: Warning because the constructor may be ambigious + // Issue: Warning because the constructor may be ambiguous qWarning("QImage::QImage(), XPM is not supported"); } #endif // QT_NO_IMAGEFORMAT_XPM @@ -1047,7 +1047,7 @@ QImage::QImage(const char * const xpm[]) QImage::QImage(const QImage &image) : QPaintDevice() { - if (image.paintingActive() || isLocked(image.d)) { + if (image.paintingActive()) { d = nullptr; image.copy().swap(*this); } else { @@ -1079,7 +1079,7 @@ QImage::~QImage() QImage &QImage::operator=(const QImage &image) { - if (image.paintingActive() || isLocked(image.d)) { + if (image.paintingActive()) { operator=(image.copy()); } else { if (image.d) @@ -1141,6 +1141,28 @@ void QImage::detach() } +/*! + \internal + + A variant for metadata-only detach, which will not detach readonly image data, + and only invalidate caches of the image data if asked to. + + \sa detach(), isDetached() +*/ +void QImage::detachMetadata(bool invalidateCache) +{ + if (d) { + if (d->is_cached && d->ref.loadRelaxed() == 1) + QImagePixmapCleanupHooks::executeImageHooks(cacheKey()); + + if (d->ref.loadRelaxed() != 1) + *this = copy(); + + if (d && invalidateCache) + ++d->detach_no; + } +} + static void copyPhysicalMetadata(QImageData *dst, const QImageData *src) { dst->dpmx = src->dpmx; @@ -1150,9 +1172,10 @@ static void copyPhysicalMetadata(QImageData *dst, const QImageData *src) static void copyMetadata(QImageData *dst, const QImageData *src) { - // Doesn't copy colortable and alpha_clut, or offset. + // Doesn't copy colortable and alpha_clut. copyPhysicalMetadata(dst, src); dst->text = src->text; + dst->offset = src->offset; dst->colorSpace = src->colorSpace; } @@ -1197,7 +1220,7 @@ static void copyMetadata(QImage *dst, const QImage &src) \sa QImage() */ -QImage QImage::copy(const QRect& r) const +QImage Q_TRACE_INSTRUMENT(qtgui) QImage::copy(const QRect& r) const { Q_TRACE_SCOPE(QImage_copy, r); if (!d) @@ -1217,7 +1240,6 @@ QImage QImage::copy(const QRect& r) const } else memcpy(image.bits(), bits(), d->nbytes); image.d->colortable = d->colortable; - image.d->offset = d->offset; image.d->has_alpha_clut = d->has_alpha_clut; copyMetadata(image.d, d); return image; @@ -1306,7 +1328,6 @@ QImage QImage::copy(const QRect& r) const } copyMetadata(image.d, d); - image.d->offset = offset(); image.d->has_alpha_clut = d->has_alpha_clut; return image; } @@ -1422,7 +1443,7 @@ void QImage::setColorTable(const QList<QRgb> &colors) { if (!d) return; - detach(); + detachMetadata(true); // In case detach() ran out of memory if (!d) @@ -1496,18 +1517,20 @@ void QImage::setDevicePixelRatio(qreal scaleFactor) if (scaleFactor == d->devicePixelRatio) return; - detach(); + detachMetadata(); if (d) d->devicePixelRatio = scaleFactor; } /*! - Returns the size of the pixmap in device independent pixels. + Returns the size of the image in device independent pixels. - This value should be used when using the pixmap size in user interface + This value should be used when using the image size in user interface size calculations. - The return value is equivalent to pixmap.size() / pixmap.devicePixelRatio(), + The return value is equivalent to image.size() / image.devicePixelRatio(). + + \since 6.2 */ QSizeF QImage::deviceIndependentSize() const { @@ -1579,7 +1602,7 @@ void QImage::setColor(int i, QRgb c) qWarning("QImage::setColor: Index out of bound %d", i); return; } - detach(); + detachMetadata(true); // In case detach() run out of memory if (!d) @@ -1914,6 +1937,31 @@ void QImage::fill(const QColor &color) qt_rectfill<quint64>(reinterpret_cast<quint64 *>(d->data), color.rgba64().premultiplied(), 0, 0, d->width, d->height, d->bytes_per_line); break; + case QImage::Format_RGBX16FPx4: + case QImage::Format_RGBA16FPx4: + case QImage::Format_RGBA16FPx4_Premultiplied: + case QImage::Format_RGBX32FPx4: + case QImage::Format_RGBA32FPx4: + case QImage::Format_RGBA32FPx4_Premultiplied:{ + float r, g, b, a; + color.getRgbF(&r, &g, &b, &a); + if (!hasAlphaChannel()) + a = 1.0f; + if (depth() == 64) { + QRgbaFloat16 c16{qfloat16(r), qfloat16(g), qfloat16(b), qfloat16(a)}; + if (d->format == Format_RGBA16FPx4_Premultiplied) + c16 = c16.premultiplied(); + qt_rectfill<QRgbaFloat16>(reinterpret_cast<QRgbaFloat16 *>(d->data), c16, + 0, 0, d->width, d->height, d->bytes_per_line); + } else { + QRgbaFloat32 c32{r, g, b, a}; + if (d->format == Format_RGBA32FPx4_Premultiplied) + c32 = c32.premultiplied(); + qt_rectfill<QRgbaFloat32>(reinterpret_cast<QRgbaFloat32 *>(d->data), c32, + 0, 0, d->width, d->height, d->bytes_per_line); + } + break; + } default: { QPainter p(this); p.setCompositionMode(QPainter::CompositionMode_Source); @@ -1986,11 +2034,11 @@ void QImage::invertPixels(InvertMode mode) qfloat16 *p = reinterpret_cast<qfloat16 *>(d->data); qfloat16 *end = reinterpret_cast<qfloat16 *>(d->data + d->nbytes); while (p < end) { - p[0] = 1.0f - p[0]; - p[1] = 1.0f - p[1]; - p[2] = 1.0f - p[2]; + p[0] = qfloat16(1) - p[0]; + p[1] = qfloat16(1) - p[1]; + p[2] = qfloat16(1) - p[2]; if (mode == InvertRgba) - p[3] = 1.0f - p[3]; + p[3] = qfloat16(1) - p[3]; p += 4; } } else if (format() >= QImage::Format_RGBX32FPx4 && format() <= QImage::Format_RGBA32FPx4_Premultiplied) { @@ -2096,7 +2144,7 @@ void QImage::setColorCount(int colorCount) return; } - detach(); + detachMetadata(true); // In case detach() ran out of memory if (!d) @@ -2179,7 +2227,6 @@ QImage QImage::convertToFormat_helper(Format format, Qt::ImageConversionFlags fl QIMAGE_SANITYCHECK_MEMORY(image); - image.d->offset = offset(); copyMetadata(image.d, d); converter(image.d, d, flags); @@ -2236,7 +2283,7 @@ static QImage convertWithPalette(const QImage &src, QImage::Format format, QImage dest(src.size(), format); dest.setColorTable(clut); - QImageData::get(dest)->text = QImageData::get(src)->text; + copyMetadata(QImageData::get(dest), QImageData::get(src)); int h = src.height(); int w = src.width(); @@ -2337,6 +2384,7 @@ bool QImage::reinterpretAsFormat(Format format) // In case detach() ran out of memory if (!d) { d = oldD; + d->ref.ref(); return false; } } @@ -2608,6 +2656,9 @@ void QImage::setPixel(int x, int y, uint index_or_rgb) case Format_A2RGB30_Premultiplied: ((uint *)s)[x] = qConvertArgb32ToA2rgb30<PixelOrderRGB>(index_or_rgb); return; + case Format_RGBX64: + ((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb | 0xff000000); + return; case Format_RGBA64: case Format_RGBA64_Premultiplied: ((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb); @@ -2792,7 +2843,7 @@ void QImage::setPixelColor(int x, int y, const QColor &color) color.getRgbF(&r, &g, &b, &a); if (d->format == Format_RGBX16FPx4) a = 1.0f; - QRgbaFloat16 c16f{r, g, b, a}; + QRgbaFloat16 c16f{qfloat16(r), qfloat16(g), qfloat16(b), qfloat16(a)}; if (d->format == Format_RGBA16FPx4_Premultiplied) c16f = c16f.premultiplied(); ((QRgbaFloat16 *)s)[x] = c16f; @@ -2967,7 +3018,7 @@ bool QImage::isGrayscale() const \sa isNull(), {QImage#Image Transformations}{Image Transformations} */ -QImage QImage::scaled(const QSize& s, Qt::AspectRatioMode aspectMode, Qt::TransformationMode mode) const +QImage Q_TRACE_INSTRUMENT(qtgui) QImage::scaled(const QSize& s, Qt::AspectRatioMode aspectMode, Qt::TransformationMode mode) const { if (!d) { qWarning("QImage::scaled: Image is a null image"); @@ -3004,7 +3055,7 @@ QImage QImage::scaled(const QSize& s, Qt::AspectRatioMode aspectMode, Qt::Transf \sa {QImage#Image Transformations}{Image Transformations} */ -QImage QImage::scaledToWidth(int w, Qt::TransformationMode mode) const +QImage Q_TRACE_INSTRUMENT(qtgui) QImage::scaledToWidth(int w, Qt::TransformationMode mode) const { if (!d) { qWarning("QImage::scaleWidth: Image is a null image"); @@ -3034,7 +3085,7 @@ QImage QImage::scaledToWidth(int w, Qt::TransformationMode mode) const \sa {QImage#Image Transformations}{Image Transformations} */ -QImage QImage::scaledToHeight(int h, Qt::TransformationMode mode) const +QImage Q_TRACE_INSTRUMENT(qtgui) QImage::scaledToHeight(int h, Qt::TransformationMode mode) const { if (!d) { qWarning("QImage::scaleHeight: Image is a null image"); @@ -3067,7 +3118,7 @@ QImage QImage::scaledToHeight(int h, Qt::TransformationMode mode) const \sa createHeuristicMask(), {QImage#Image Transformations}{Image Transformations} */ -QImage QImage::createAlphaMask(Qt::ImageConversionFlags flags) const +QImage Q_TRACE_INSTRUMENT(qtgui) QImage::createAlphaMask(Qt::ImageConversionFlags flags) const { if (!d || d->format == QImage::Format_RGB32) return QImage(); @@ -3519,7 +3570,7 @@ static inline void rgbSwapped_generic(int width, int height, const QImage *src, /*! \internal */ -QImage QImage::rgbSwapped_helper() const +QImage Q_TRACE_INSTRUMENT(qtgui) QImage::rgbSwapped_helper() const { if (isNull()) return *this; @@ -3875,10 +3926,15 @@ bool QImage::save(QIODevice* device, const char* format, int quality) const bool QImageData::doImageIO(const QImage *image, QImageWriter *writer, int quality) const { if (quality > 100 || quality < -1) - qWarning("QPixmap::save: Quality out of range [-1, 100]"); + qWarning("QImage::save: Quality out of range [-1, 100]"); if (quality >= 0) writer->setQuality(qMin(quality,100)); - return writer->write(*image); + const bool result = writer->write(*image); +#ifdef QT_DEBUG + if (!result) + qWarning("QImage::save: failed to write image - %s", qPrintable(writer->errorString())); +#endif + return result; } /***************************************************************************** @@ -3963,7 +4019,7 @@ bool QImage::operator==(const QImage & i) const return false; // obviously different stuff? - if (i.d->height != d->height || i.d->width != d->width || i.d->format != d->format) + if (i.d->height != d->height || i.d->width != d->width || i.d->format != d->format || i.d->colorSpace != d->colorSpace) return false; if (d->format != Format_RGB32) { @@ -4067,9 +4123,9 @@ int QImage::dotsPerMeterY() const */ void QImage::setDotsPerMeterX(int x) { - if (!d || !x) + if (!d || !x || d->dpmx == x) return; - detach(); + detachMetadata(); if (d) d->dpmx = x; @@ -4089,9 +4145,9 @@ void QImage::setDotsPerMeterX(int x) */ void QImage::setDotsPerMeterY(int y) { - if (!d || !y) + if (!d || !y || d->dpmy == y) return; - detach(); + detachMetadata(); if (d) d->dpmy = y; @@ -4121,9 +4177,9 @@ QPoint QImage::offset() const */ void QImage::setOffset(const QPoint& p) { - if (!d) + if (!d || d->offset == p) return; - detach(); + detachMetadata(); if (d) d->offset = p; @@ -4159,7 +4215,7 @@ QString QImage::text(const QString &key) const QString tmp; for (auto it = d->text.begin(), end = d->text.end(); it != end; ++it) - tmp += it.key() + QLatin1String(": ") + it.value().simplified() + QLatin1String("\n\n"); + tmp += it.key() + ": "_L1 + it.value().simplified() + "\n\n"_L1; if (!tmp.isEmpty()) tmp.chop(2); // remove final \n\n return tmp; @@ -4193,7 +4249,7 @@ void QImage::setText(const QString &key, const QString &value) { if (!d) return; - detach(); + detachMetadata(); if (d) d->text.insert(key, value); @@ -4661,6 +4717,8 @@ QImage QImage::smoothScaled(int w, int h) const static QImage rotated90(const QImage &image) { QImage out(image.height(), image.width(), image.format()); + if (out.isNull()) + return out; copyMetadata(&out, image); if (image.colorCount() > 0) out.setColorTable(image.colorTable()); @@ -4689,6 +4747,8 @@ static QImage rotated180(const QImage &image) return image.mirrored(true, true); QImage out(image.width(), image.height(), image.format()); + if (out.isNull()) + return out; copyMetadata(&out, image); if (image.colorCount() > 0) out.setColorTable(image.colorTable()); @@ -4701,6 +4761,8 @@ static QImage rotated180(const QImage &image) static QImage rotated270(const QImage &image) { QImage out(image.height(), image.width(), image.format()); + if (out.isNull()) + return out; copyMetadata(&out, image); if (image.colorCount() > 0) out.setColorTable(image.colorTable()); @@ -4731,7 +4793,7 @@ static QImage rotated270(const QImage &image) image where not all pixels are covered by the transformed pixels of the original image. In such cases, those background pixels will be assigned a transparent color value, and the transformed image will be given a format - with an alpha channel, even if the orginal image did not have that. + with an alpha channel, even if the original image did not have that. The transformation \a matrix is internally adjusted to compensate for unwanted translation; i.e. the image produced is the smallest @@ -4746,12 +4808,15 @@ static QImage rotated270(const QImage &image) Transformations} */ -QImage QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode ) const +QImage Q_TRACE_INSTRUMENT(qtgui) QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode ) const { if (!d) return QImage(); - Q_TRACE_SCOPE(QImage_transformed, matrix, mode); + Q_TRACE_PARAM_REPLACE(const QTransform &, double[9]); + Q_TRACE_SCOPE(QImage_transformed, QList<double>({matrix.m11(), matrix.m12(), matrix.m13(), + matrix.m21(), matrix.m22(), matrix.m23(), + matrix.m31(), matrix.m32(), matrix.m33()}).data(), mode); // source image data const int ws = width(); @@ -4772,13 +4837,8 @@ QImage QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode else if (mat.m11() == -1. && mat.m22() == -1.) return rotated180(*this); - if (mode == Qt::FastTransformation) { - hd = qRound(qAbs(mat.m22()) * hs); - wd = qRound(qAbs(mat.m11()) * ws); - } else { - hd = int(qAbs(mat.m22()) * hs + 0.9999); - wd = int(qAbs(mat.m11()) * ws + 0.9999); - } + hd = qRound(qAbs(mat.m22()) * hs); + wd = qRound(qAbs(mat.m11()) * ws); scale_xform = true; // The paint-based scaling is only bilinear, and has problems // with scaling smoothly more than 2x down. @@ -4828,14 +4888,24 @@ QImage QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode || (ws * hs) >= (1<<20) #endif ) { + QImage scaledImage; if (mat.m11() < 0.0F && mat.m22() < 0.0F) { // horizontal/vertical flip - return smoothScaled(wd, hd).mirrored(true, true).convertToFormat(format()); + scaledImage = smoothScaled(wd, hd).mirrored(true, true); } else if (mat.m11() < 0.0F) { // horizontal flip - return smoothScaled(wd, hd).mirrored(true, false).convertToFormat(format()); + scaledImage = smoothScaled(wd, hd).mirrored(true, false); } else if (mat.m22() < 0.0F) { // vertical flip - return smoothScaled(wd, hd).mirrored(false, true).convertToFormat(format()); + scaledImage = smoothScaled(wd, hd).mirrored(false, true); } else { // no flipping - return smoothScaled(wd, hd).convertToFormat(format()); + scaledImage = smoothScaled(wd, hd); + } + + switch (format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + return scaledImage; + default: + return scaledImage.convertToFormat(format()); } } } @@ -4877,7 +4947,14 @@ QImage QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode if (target_format >= QImage::Format_RGB32) { // Prevent QPainter from applying devicePixelRatio corrections - const QImage sImage = (devicePixelRatio() != 1) ? QImage(constBits(), width(), height(), format()) : *this; + QImage sImage = (devicePixelRatio() != 1) ? QImage(constBits(), width(), height(), format()) : *this; + if (sImage.d != d + && (d->format == QImage::Format_MonoLSB + || d->format == QImage::Format_Mono + || d->format == QImage::Format_Indexed8)) { + sImage.d->colortable = d->colortable; + sImage.d->has_alpha_clut = d->has_alpha_clut; + } Q_ASSERT(sImage.devicePixelRatio() == 1); Q_ASSERT(sImage.devicePixelRatio() == dImage.devicePixelRatio()); @@ -4947,9 +5024,12 @@ void QImage::setColorSpace(const QColorSpace &colorSpace) return; if (d->colorSpace == colorSpace) return; - if (!isDetached()) // Detach only if shared, not for read-only data. - detach(); - d->colorSpace = colorSpace; + if (colorSpace.isValid() && !qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) + return; + + detachMetadata(false); + if (d) + d->colorSpace = colorSpace; } /*! @@ -4959,20 +5039,60 @@ void QImage::setColorSpace(const QColorSpace &colorSpace) If the image has no valid color space, the method does nothing. + \note If \a colorSpace is not compatible with the current format, the image + will be converted to one that is. + \sa convertedToColorSpace(), setColorSpace() */ void QImage::convertToColorSpace(const QColorSpace &colorSpace) { - if (!d) - return; - if (!d->colorSpace.isValid()) + if (!d || !d->colorSpace.isValid()) return; - if (!colorSpace.isValid()) { + if (!colorSpace.isValidTarget()) { qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid"; return; } - detach(); + if (d->colorSpace == colorSpace) + return; + if (!qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) { + *this = convertedToColorSpace(colorSpace); + return; + } applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace)); + if (d->ref.loadRelaxed() != 1) + detachMetadata(false); + d->colorSpace = colorSpace; +} + +/*! + \since 6.8 + + Converts the image to \a colorSpace and \a format. + + If the image has no valid color space, the method does nothing, + nor if the color space is not compatible with with the format. + + The specified image conversion \a flags control how the image data + is handled during the format conversion process. + + \sa convertedToColorSpace(), setColorSpace() +*/ +void QImage::convertToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags) +{ + if (!d || !d->colorSpace.isValid()) + return; + if (!colorSpace.isValidTarget()) { + qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid"; + return; + } + if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) { + qWarning() << "QImage::convertToColorSpace: Color space is not compatible with format"; + return; + } + + if (d->colorSpace == colorSpace) + return convertTo(format, flags); + applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace), format, flags); d->colorSpace = colorSpace; } @@ -4983,14 +5103,56 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace) If the image has no valid color space, a null QImage is returned. - \sa convertToColorSpace() + \note If \a colorSpace is not compatible with the current format, + the returned image will also be converted to a format this is. + For more control over returned image format, see the three argument + overload of this method. + + \sa convertToColorSpace(), colorTransformed() */ QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const { - if (!d || !d->colorSpace.isValid() || !colorSpace.isValid()) + if (!d || !d->colorSpace.isValid()) return QImage(); - QImage image = copy(); - image.convertToColorSpace(colorSpace); + if (!colorSpace.isValidTarget()) { + qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid"; + return QImage(); + } + if (d->colorSpace == colorSpace) + return *this; + QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace)); + image.setColorSpace(colorSpace); + return image; +} + +/*! + \since 6.8 + + Returns the image converted to \a colorSpace and \a format. + + If the image has no valid color space, a null QImage is returned. + + The specified image conversion \a flags control how the image data + is handled during the format conversion process. + + \sa colorTransformed() +*/ +QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags) const +{ + if (!d || !d->colorSpace.isValid()) + return QImage(); + if (!colorSpace.isValidTarget()) { + qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid"; + return QImage(); + } + if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) { + qWarning() << "QImage::convertedToColorSpace: Color space is not compatible with format"; + return QImage(); + } + if (d->colorSpace == colorSpace) + return convertedTo(format, flags); + QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace), format, flags); + image.setColorSpace(colorSpace); return image; } @@ -5013,6 +5175,16 @@ QColorSpace QImage::colorSpace() const */ void QImage::applyColorTransform(const QColorTransform &transform) { + if (transform.isIdentity()) + return; + + if (!qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel) || + !qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel)) { + qWarning() << "QImage::applyColorTransform can not apply format switching transform without switching format"; + return; + } + + detach(); if (!d) return; if (pixelFormat().colorModel() == QPixelFormat::Indexed) { @@ -5021,30 +5193,41 @@ void QImage::applyColorTransform(const QColorTransform &transform) return; } QImage::Format oldFormat = format(); - if (depth() > 32) { - if (format() != QImage::Format_RGBX64 && format() != QImage::Format_RGBA64 - && format() != QImage::Format_RGBA64_Premultiplied) - *this = std::move(*this).convertToFormat(QImage::Format_RGBA64); - } else if (format() != QImage::Format_ARGB32 && format() != QImage::Format_RGB32 - && format() != QImage::Format_ARGB32_Premultiplied) { + if (qt_fpColorPrecision(oldFormat)) { + if (oldFormat != QImage::Format_RGBX32FPx4 && oldFormat != QImage::Format_RGBA32FPx4 + && oldFormat != QImage::Format_RGBA32FPx4_Premultiplied) + convertTo(QImage::Format_RGBA32FPx4); + } else if (depth() > 32) { + if (oldFormat != QImage::Format_RGBX64 && oldFormat != QImage::Format_RGBA64 + && oldFormat != QImage::Format_RGBA64_Premultiplied) + convertTo(QImage::Format_RGBA64); + } else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32 + && oldFormat != QImage::Format_ARGB32_Premultiplied && oldFormat != QImage::Format_CMYK8888 + && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) { if (hasAlphaChannel()) - *this = std::move(*this).convertToFormat(QImage::Format_ARGB32); + convertTo(QImage::Format_ARGB32); else - *this = std::move(*this).convertToFormat(QImage::Format_RGB32); + convertTo(QImage::Format_RGB32); } QColorTransformPrivate::TransformFlags flags = QColorTransformPrivate::Unpremultiplied; switch (format()) { case Format_ARGB32_Premultiplied: case Format_RGBA64_Premultiplied: + case Format_RGBA32FPx4_Premultiplied: flags = QColorTransformPrivate::Premultiplied; break; + case Format_Grayscale8: + case Format_Grayscale16: case Format_RGB32: + case Format_CMYK8888: case Format_RGBX64: + case Format_RGBX32FPx4: flags = QColorTransformPrivate::InputOpaque; break; case Format_ARGB32: case Format_RGBA64: + case Format_RGBA32FPx4: break; default: Q_UNREACHABLE(); @@ -5052,18 +5235,46 @@ void QImage::applyColorTransform(const QColorTransform &transform) std::function<void(int,int)> transformSegment; - if (depth() > 32) { + if (format() == Format_Grayscale8) { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - QRgba64 *scanline = reinterpret_cast<QRgba64 *>(scanLine(y)); - transform.d->apply(scanline, scanline, width(), flags); + uint8_t *scanline = reinterpret_cast<uint8_t *>(d->data + y * d->bytes_per_line); + QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags); + } + }; + } else if (format() == Format_Grayscale16) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + uint16_t *scanline = reinterpret_cast<uint16_t *>(d->data + y * d->bytes_per_line); + QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags); + } + }; + } else if (qt_fpColorPrecision(format())) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + QRgbaFloat32 *scanline = reinterpret_cast<QRgbaFloat32 *>(d->data + y * d->bytes_per_line); + QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); + } + }; + } else if (depth() > 32) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + QRgba64 *scanline = reinterpret_cast<QRgba64 *>(d->data + y * d->bytes_per_line); + QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); + } + }; + } else if (oldFormat == QImage::Format_CMYK8888) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + QCmyk32 *scanline = reinterpret_cast<QCmyk32 *>(d->data + y * d->bytes_per_line); + QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); } }; } else { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - QRgb *scanline = reinterpret_cast<QRgb *>(scanLine(y)); - transform.d->apply(scanline, scanline, width(), flags); + QRgb *scanline = reinterpret_cast<QRgb *>(d->data + y * d->bytes_per_line); + QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); } }; } @@ -5071,7 +5282,7 @@ void QImage::applyColorTransform(const QColorTransform &transform) #if QT_CONFIG(thread) && !defined(Q_OS_WASM) int segments = (qsizetype(width()) * height()) >> 16; segments = std::min(segments, height()); - QThreadPool *threadPool = QThreadPool::globalInstance(); + QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance(); if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) { QSemaphore semaphore; int y = 0; @@ -5092,6 +5303,549 @@ void QImage::applyColorTransform(const QColorTransform &transform) *this = std::move(*this).convertToFormat(oldFormat); } +/*! + \since 6.8 + + Applies the color transformation \a transform to all pixels in the image, and converts the format of the image to \a toFormat. + + The specified image conversion \a flags control how the image data + is handled during the format conversion process. +*/ +void QImage::applyColorTransform(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags) +{ + if (!d) + return; + if (transform.isIdentity()) + return convertTo(toFormat, flags); + + *this = colorTransformed(transform, toFormat, flags); +} + +/*! + \since 6.4 + + Returns the image color transformed using \a transform on all pixels in the image. + + \note If \a transform has a source color space which is incompatible with the format of this image, + returns a null QImage. If \a transform has a target color space which is incompatible with the format + of this image, the image will also be converted to a compatible format. For more control about the + choice of the target pixel format, see the three argument overload of this method. + + \sa applyColorTransform() +*/ +QImage QImage::colorTransformed(const QColorTransform &transform) const & +{ + if (!d) + return QImage(); + if (transform.isIdentity()) + return *this; + + QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel; + QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel; + if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) { + qWarning() << "QImage::colorTransformed: Invalid input color space for transform"; + return QImage(); + } + if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) { + // All model switching transforms are opaque in at least one end. + switch (outColorModel) { + case QColorSpace::ColorModel::Rgb: + return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32); + case QColorSpace::ColorModel::Gray: + return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8); + case QColorSpace::ColorModel::Cmyk: + return colorTransformed(transform, QImage::Format_CMYK8888); + case QColorSpace::ColorModel::Undefined: + break; + } + return QImage(); + } + + QImage image = copy(); + image.applyColorTransform(transform); + return image; +} + +static bool isRgb32Data(QImage::Format f) +{ + switch (f) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + return true; + default: + break; + } + return false; +} + +static bool isRgb64Data(QImage::Format f) +{ + switch (f) { + case QImage::Format_RGBX64: + case QImage::Format_RGBA64: + case QImage::Format_RGBA64_Premultiplied: + return true; + default: + break; + } + return false; +} + +static bool isRgb32fpx4Data(QImage::Format f) +{ + switch (f) { + case QImage::Format_RGBX32FPx4: + case QImage::Format_RGBA32FPx4: + case QImage::Format_RGBA32FPx4_Premultiplied: + return true; + default: + break; + } + return false; +} + +/*! + \since 6.8 + + Returns the image color transformed using \a transform on all pixels in the image, returning an image of format \a toFormat. + + The specified image conversion \a flags control how the image data + is handled during the format conversion process. + + \note If \a transform has a source color space which is incompatible with the format of this image, + or a target color space that is incompatible with \a toFormat, returns a null QImage. + + \sa applyColorTransform() +*/ +QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags) const & +{ + if (!d) + return QImage(); + if (toFormat == QImage::Format_Invalid) + toFormat = format(); + if (transform.isIdentity()) + return convertedTo(toFormat, flags); + + QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel; + QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel; + if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) { + qWarning() << "QImage::colorTransformed: Invalid input color space for transform"; + return QImage(); + } + if (!qt_compatibleColorModel(toPixelFormat(toFormat).colorModel(), outColorModel)) { + qWarning() << "QImage::colorTransformed: Invalid output color space for transform"; + return QImage(); + } + + QImage fromImage = *this; + + QImage::Format tmpFormat = toFormat; + switch (toFormat) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + case QImage::Format_RGBX32FPx4: + case QImage::Format_RGBA32FPx4: + case QImage::Format_RGBA32FPx4_Premultiplied: + case QImage::Format_RGBX64: + case QImage::Format_RGBA64: + case QImage::Format_RGBA64_Premultiplied: + case QImage::Format_Grayscale8: + case QImage::Format_Grayscale16: + case QImage::Format_CMYK8888: + // can be output natively + break; + case QImage::Format_RGB16: + case QImage::Format_RGB444: + case QImage::Format_RGB555: + case QImage::Format_RGB666: + case QImage::Format_RGB888: + case QImage::Format_BGR888: + case QImage::Format_RGBX8888: + tmpFormat = QImage::Format_RGB32; + break; + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + case QImage::Format_RGBA8888: + case QImage::Format_RGBA8888_Premultiplied: + tmpFormat = QImage::Format_ARGB32; + break; + case QImage::Format_BGR30: + case QImage::Format_RGB30: + tmpFormat = QImage::Format_RGBX64; + break; + case QImage::Format_A2BGR30_Premultiplied: + case QImage::Format_A2RGB30_Premultiplied: + tmpFormat = QImage::Format_RGBA64; + break; + case QImage::Format_RGBX16FPx4: + case QImage::Format_RGBA16FPx4: + case QImage::Format_RGBA16FPx4_Premultiplied: + tmpFormat = QImage::Format_RGBA32FPx4; + break; + case QImage::Format_Alpha8: + return convertedTo(QImage::Format_Alpha8); + case QImage::Format_Invalid: + case QImage::NImageFormats: + Q_UNREACHABLE(); + break; + } + QColorSpace::ColorModel inColorData = qt_csColorData(pixelFormat().colorModel()); + QColorSpace::ColorModel outColorData = qt_csColorData(toPixelFormat(toFormat).colorModel()); + // Ensure only precision increasing transforms + if (inColorData != outColorData) { + if (fromImage.format() == QImage::Format_Grayscale8 && outColorData == QColorSpace::ColorModel::Rgb) + tmpFormat = QImage::Format_RGB32; + else if (tmpFormat == QImage::Format_Grayscale8 && qt_highColorPrecision(fromImage.format())) + tmpFormat = QImage::Format_Grayscale16; + else if (fromImage.format() == QImage::Format_Grayscale16 && outColorData == QColorSpace::ColorModel::Rgb) + tmpFormat = QImage::Format_RGBX64; + } else { + if (tmpFormat == QImage::Format_Grayscale8 && fromImage.format() == QImage::Format_Grayscale16) + tmpFormat = QImage::Format_Grayscale16; + else if (qt_fpColorPrecision(fromImage.format()) && !qt_fpColorPrecision(tmpFormat)) + tmpFormat = QImage::Format_RGBA32FPx4; + else if (isRgb32Data(tmpFormat) && qt_highColorPrecision(fromImage.format(), true)) + tmpFormat = QImage::Format_RGBA64; + } + + QImage toImage(size(), tmpFormat); + copyMetadata(&toImage, *this); + + std::function<void(int, int)> transformSegment; + QColorTransformPrivate::TransformFlags transFlags = QColorTransformPrivate::Unpremultiplied; + + if (inColorData != outColorData) { + // Needs color model switching transform + if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Rgb) { + // Gray -> RGB + if (format() == QImage::Format_Grayscale8) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line); + QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line); + QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Cmyk) { + // Gray -> CMYK + if (format() == QImage::Format_Grayscale8) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line); + QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line); + QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Gray) { + // RGB -> Gray + if (tmpFormat == QImage::Format_Grayscale8) { + fromImage.convertTo(QImage::Format_RGB32); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else { + fromImage.convertTo(QImage::Format_RGBX64); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Gray) { + // CMYK -> Gray + if (tmpFormat == QImage::Format_Grayscale8) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Rgb) { + // CMYK -> RGB + if (isRgb32Data(tmpFormat) ) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else if (isRgb64Data(tmpFormat)) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else { + Q_ASSERT(isRgb32fpx4Data(tmpFormat)); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Cmyk) { + // RGB -> CMYK + if (!fromImage.hasAlphaChannel()) + transFlags = QColorTransformPrivate::InputOpaque; + else if (qPixelLayouts[fromImage.format()].premultiplied) + transFlags = QColorTransformPrivate::Premultiplied; + if (isRgb32Data(fromImage.format()) ) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } else if (isRgb64Data(fromImage.format())) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } else { + Q_ASSERT(isRgb32fpx4Data(fromImage.format())); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } + } else { + Q_UNREACHABLE(); + } + } else { + // Conversion on same color model + if (pixelFormat().colorModel() == QPixelFormat::Indexed) { + for (int i = 0; i < d->colortable.size(); ++i) + fromImage.d->colortable[i] = transform.map(d->colortable[i]); + return fromImage.convertedTo(toFormat, flags); + } + + QImage::Format oldFormat = format(); + if (qt_fpColorPrecision(oldFormat)) { + if (oldFormat != QImage::Format_RGBX32FPx4 && oldFormat != QImage::Format_RGBA32FPx4 + && oldFormat != QImage::Format_RGBA32FPx4_Premultiplied) + fromImage.convertTo(QImage::Format_RGBA32FPx4); + } else if (qt_highColorPrecision(oldFormat, true)) { + if (oldFormat != QImage::Format_RGBX64 && oldFormat != QImage::Format_RGBA64 + && oldFormat != QImage::Format_RGBA64_Premultiplied && oldFormat != QImage::Format_Grayscale16) + fromImage.convertTo(QImage::Format_RGBA64); + } else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32 + && oldFormat != QImage::Format_ARGB32_Premultiplied && oldFormat != QImage::Format_CMYK8888 + && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) { + if (hasAlphaChannel()) + fromImage.convertTo(QImage::Format_ARGB32); + else + fromImage.convertTo(QImage::Format_RGB32); + } + + if (!fromImage.hasAlphaChannel()) + transFlags = QColorTransformPrivate::InputOpaque; + else if (qPixelLayouts[fromImage.format()].premultiplied) + transFlags = QColorTransformPrivate::Premultiplied; + + if (fromImage.format() == Format_Grayscale8) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const quint8 *in_scanline = reinterpret_cast<const quint8 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + if (tmpFormat == Format_Grayscale8) { + quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); + } else { + Q_ASSERT(tmpFormat == Format_Grayscale16); + quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); + } + } + }; + } else if (fromImage.format() == Format_Grayscale16) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); + } + }; + } else if (fromImage.format() == Format_CMYK8888) { + Q_ASSERT(tmpFormat == Format_CMYK8888); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } else if (isRgb32fpx4Data(fromImage.format())) { + Q_ASSERT(isRgb32fpx4Data(tmpFormat)); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } else if (isRgb64Data(fromImage.format())) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + if (isRgb32fpx4Data(tmpFormat)) { + QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } else { + Q_ASSERT(isRgb64Data(tmpFormat)); + QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + } + }; + } else { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine()); + if (isRgb32fpx4Data(tmpFormat)) { + QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } else if (isRgb64Data(tmpFormat)) { + QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } else { + Q_ASSERT(isRgb32Data(tmpFormat)); + QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + } + }; + } + } + +#if QT_CONFIG(thread) && !defined(Q_OS_WASM) + int segments = (qsizetype(width()) * height()) >> 16; + segments = std::min(segments, height()); + QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance(); + if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) { + QSemaphore semaphore; + int y = 0; + for (int i = 0; i < segments; ++i) { + int yn = (height() - y) / (segments - i); + threadPool->start([&, y, yn]() { + transformSegment(y, y + yn); + semaphore.release(1); + }); + y += yn; + } + semaphore.acquire(segments); + } else +#endif + transformSegment(0, height()); + + if (tmpFormat != toFormat) + toImage.convertTo(toFormat); + + return toImage; +} + +/*! + \since 6.4 + \overload + + Returns the image color transformed using \a transform on all pixels in the image. + + \sa applyColorTransform() +*/ +QImage QImage::colorTransformed(const QColorTransform &transform) && +{ + if (!d) + return QImage(); + + QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel; + QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel; + if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) { + qWarning() << "QImage::colorTransformed: Invalid input color space for transform"; + return QImage(); + } + if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) { + // There is currently no inplace conversion of both colorspace and format, so just use the normal version. + switch (outColorModel) { + case QColorSpace::ColorModel::Rgb: + return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32); + case QColorSpace::ColorModel::Gray: + return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8); + case QColorSpace::ColorModel::Cmyk: + return colorTransformed(transform, QImage::Format_CMYK8888); + case QColorSpace::ColorModel::Undefined: + break; + } + return QImage(); + } + + applyColorTransform(transform); + return std::move(*this); +} + +/*! + \since 6.8 + \overload + + Returns the image color transformed using \a transform on all pixels in the image. + + \sa applyColorTransform() +*/ +QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags) && +{ + // There is currently no inplace conversion of both colorspace and format, so just use the normal version. + return colorTransformed(transform, format, flags); +} bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags) { @@ -5617,6 +6371,19 @@ static constexpr QPixelFormat pixelformats[] = { /*PREMULTIPLIED*/ QPixelFormat::Premultiplied, /*INTERPRETATION*/ QPixelFormat::FloatingPoint, /*BYTE ORDER*/ QPixelFormat::CurrentSystemEndian), + //QImage::Format_CMYK8888: + QPixelFormat(QPixelFormat::CMYK, + /*RED*/ 8, + /*GREEN*/ 8, + /*BLUE*/ 8, + /*FOURTH*/ 8, + /*FIFTH*/ 0, + /*ALPHA*/ 0, + /*ALPHA USAGE*/ QPixelFormat::IgnoresAlpha, + /*ALPHA POSITION*/ QPixelFormat::AtBeginning, + /*PREMULTIPLIED*/ QPixelFormat::NotPremultiplied, + /*INTERPRETATION*/ QPixelFormat::UnsignedInteger, + /*BYTE ORDER*/ QPixelFormat::CurrentSystemEndian), }; static_assert(sizeof(pixelformats) / sizeof(*pixelformats) == QImage::NImageFormats); @@ -5677,12 +6444,11 @@ QMap<QString, QString> qt_getImageText(const QImage &image, const QString &descr QMap<QString, QString> qt_getImageTextFromDescription(const QString &description) { QMap<QString, QString> text; - const auto pairs = QStringView{description}.split(u"\n\n"); - for (const auto &pair : pairs) { - int index = pair.indexOf(QLatin1Char(':')); - if (index >= 0 && pair.indexOf(QLatin1Char(' ')) < index) { + for (const auto &pair : QStringView{description}.tokenize(u"\n\n")) { + int index = pair.indexOf(u':'); + if (index >= 0 && pair.indexOf(u' ') < index) { if (!pair.trimmed().isEmpty()) - text.insert(QLatin1String("Description"), pair.toString().simplified()); + text.insert("Description"_L1, pair.toString().simplified()); } else { const auto key = pair.left(index); if (!key.trimmed().isEmpty()) @@ -5693,3 +6459,5 @@ QMap<QString, QString> qt_getImageTextFromDescription(const QString &description } QT_END_NAMESPACE + +#include "moc_qimage.cpp" |