diff options
Diffstat (limited to 'src/gui/image/qimage.cpp')
-rw-r--r-- | src/gui/image/qimage.cpp | 226 |
1 files changed, 165 insertions, 61 deletions
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 5027a09a09..4f91fbb8b9 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// 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" @@ -35,8 +35,9 @@ #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> @@ -63,6 +64,17 @@ QT_WARNING_DISABLE_MSVC(4723) 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); @@ -70,7 +82,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); } @@ -94,7 +106,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) @@ -604,8 +616,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} */ /*! @@ -807,7 +819,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; } @@ -1104,6 +1116,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; @@ -1160,7 +1194,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) @@ -1385,7 +1419,7 @@ void QImage::setColorTable(const QList<QRgb> &colors) { if (!d) return; - detach(); + detachMetadata(true); // In case detach() ran out of memory if (!d) @@ -1459,18 +1493,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 { @@ -1542,7 +1578,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) @@ -1888,7 +1924,7 @@ void QImage::fill(const QColor &color) if (!hasAlphaChannel()) a = 1.0f; if (depth() == 64) { - QRgbaFloat16 c16{r, g, b, a}; + 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, @@ -1974,11 +2010,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) { @@ -2084,7 +2120,7 @@ void QImage::setColorCount(int colorCount) return; } - detach(); + detachMetadata(true); // In case detach() ran out of memory if (!d) @@ -2781,7 +2817,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; @@ -2956,7 +2992,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"); @@ -2993,7 +3029,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"); @@ -3023,7 +3059,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"); @@ -3056,7 +3092,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(); @@ -3508,7 +3544,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; @@ -3864,10 +3900,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; } /***************************************************************************** @@ -4056,9 +4097,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; @@ -4078,9 +4119,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; @@ -4110,9 +4151,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; @@ -4182,7 +4223,7 @@ void QImage::setText(const QString &key, const QString &value) { if (!d) return; - detach(); + detachMetadata(); if (d) d->text.insert(key, value); @@ -4650,6 +4691,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()); @@ -4678,6 +4721,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()); @@ -4690,6 +4735,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()); @@ -4735,12 +4782,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(); @@ -4761,13 +4811,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. @@ -4817,14 +4862,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()); } } } @@ -4866,7 +4921,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()); @@ -4936,9 +4998,9 @@ 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; + detachMetadata(false); + if (d) + d->colorSpace = colorSpace; } /*! @@ -4956,7 +5018,7 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace) return; if (!d->colorSpace.isValid()) return; - if (!colorSpace.isValid()) { + if (!colorSpace.isValidTarget()) { qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid"; return; } @@ -4977,8 +5039,16 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace) */ QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const { - if (!d || !d->colorSpace.isValid() || !colorSpace.isValid()) + if (!d) + return QImage(); + if (!d->colorSpace.isValid()) return QImage(); + if (!colorSpace.isValidTarget()) { + qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid"; + return QImage(); + } + if (d->colorSpace == colorSpace) + return *this; QImage image = copy(); image.convertToColorSpace(colorSpace); return image; @@ -5003,6 +5073,8 @@ QColorSpace QImage::colorSpace() const */ void QImage::applyColorTransform(const QColorTransform &transform) { + if (transform.isIdentity()) + return; detach(); if (!d) return; @@ -5054,21 +5126,21 @@ void QImage::applyColorTransform(const QColorTransform &transform) transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { QRgbaFloat32 *scanline = reinterpret_cast<QRgbaFloat32 *>(d->data + y * d->bytes_per_line); - transform.d->apply(scanline, scanline, width(), flags); + 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); - transform.d->apply(scanline, scanline, width(), flags); + 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 *>(d->data + y * d->bytes_per_line); - transform.d->apply(scanline, scanline, width(), flags); + QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); } }; } @@ -5076,7 +5148,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; @@ -5097,6 +5169,39 @@ void QImage::applyColorTransform(const QColorTransform &transform) *this = std::move(*this).convertToFormat(oldFormat); } +/*! + \since 6.4 + + Returns the image color transformed using \a transform on all pixels in the image. + + \sa applyColorTransform() +*/ +QImage QImage::colorTransformed(const QColorTransform &transform) const & +{ + if (!d || !d->colorSpace.isValid()) + return QImage(); + if (transform.isIdentity()) + return *this; + QImage image = copy(); + image.applyColorTransform(transform); + return image; +} + +/*! + \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 || !d->colorSpace.isValid()) + return QImage(); + applyColorTransform(transform); + return std::move(*this); +} bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags) { @@ -5682,8 +5787,7 @@ 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) { + 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()) |