diff options
Diffstat (limited to 'tests/auto/gui/image/qimage/tst_qimage.cpp')
-rw-r--r-- | tests/auto/gui/image/qimage/tst_qimage.cpp | 690 |
1 files changed, 600 insertions, 90 deletions
diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp index 7fcbabd532..1d0cdfcc4e 100644 --- a/tests/auto/gui/image/qimage/tst_qimage.cpp +++ b/tests/auto/gui/image/qimage/tst_qimage.cpp @@ -1,37 +1,15 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** 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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + + +#include <QTest> +#include <QBuffer> +#include <QMatrix4x4> #include <qimage.h> #include <qimagereader.h> #include <qlist.h> +#include <qset.h> #include <qtransform.h> #include <qrandom.h> #include <stdio.h> @@ -90,6 +68,7 @@ private slots: void rotate_data(); void rotate(); + void rotateBigImage(); void copy(); @@ -101,6 +80,10 @@ private slots: void setPixel_data(); void setPixel(); + void setPixelWithAlpha_data(); + void setPixelWithAlpha(); + void setPixelColorWithAlpha_data(); + void setPixelColorWithAlpha(); void defaultColorTable_data(); void defaultColorTable(); @@ -123,6 +106,10 @@ private slots: void smoothScaleBig(); void smoothScaleAlpha(); + void smoothScaleFormats_data(); + void smoothScaleFormats(); + void smoothScaleNoConversion_data(); + void smoothScaleNoConversion(); void transformed_data(); void transformed(); @@ -145,6 +132,7 @@ private slots: void fillColor_data(); void fillColor(); + void fillColorWithAlpha_data(); void fillColorWithAlpha(); void fillRGB888(); @@ -181,6 +169,11 @@ private slots: void largeInplaceRgbConversion_data(); void largeInplaceRgbConversion(); + void colorSpaceRgbConversion_data(); + void colorSpaceRgbConversion(); + void colorSpaceCmykConversion_data(); + void colorSpaceCmykConversion(); + void deepCopyWhenPaintingActive(); void scaled_QTBUG19157(); @@ -208,6 +201,7 @@ private slots: void cleanupFunctions(); void devicePixelRatio(); + void deviceIndependentSize(); void rgb30Unpremul(); void rgb30Repremul_data(); void rgb30Repremul(); @@ -238,12 +232,21 @@ private slots: void wideImage(); + void largeFillScale(); + void largeRasterScale(); + + void metadataChangeWithReadOnlyPixels(); + void scaleIndexed(); + #if defined(Q_OS_WIN) void toWinHBITMAP_data(); void toWinHBITMAP(); void fromMonoHBITMAP(); #endif // Q_OS_WIN + void tofromPremultipliedFormat_data(); + void tofromPremultipliedFormat(); + private: const QString m_prefix; }; @@ -311,7 +314,21 @@ static QLatin1String formatToString(QImage::Format format) return QLatin1String("Grayscale16"); case QImage::Format_BGR888: return QLatin1String("BGR888"); - default: + case QImage::Format_RGBX16FPx4: + return QLatin1String("RGBx16FPx4"); + case QImage::Format_RGBA16FPx4: + return QLatin1String("RGBA16FPx4"); + case QImage::Format_RGBA16FPx4_Premultiplied: + return QLatin1String("RGBA16FPx4pm"); + case QImage::Format_RGBX32FPx4: + return QLatin1String("RGBx32FPx4"); + case QImage::Format_RGBA32FPx4: + return QLatin1String("RGBA32FPx4"); + case QImage::Format_RGBA32FPx4_Premultiplied: + return QLatin1String("RGBA32FPx4pm"); + case QImage::Format_CMYK8888: + return QLatin1String("CMYK8888"); + case QImage::NImageFormats: break; }; Q_UNREACHABLE(); @@ -1129,10 +1146,9 @@ void tst_QImage::rotate_data() QTest::addColumn<QImage::Format>("format"); QTest::addColumn<int>("degrees"); - QList<int> degrees; - degrees << 0 << 90 << 180 << 270; + constexpr int degrees[] = {0, 90, 180, 270}; - foreach (int d, degrees) { + for (int d : degrees) { const QString dB = QString::number(d); for (int i = QImage::Format_Indexed8; i < QImage::NImageFormats; i++) { QImage::Format format = static_cast<QImage::Format>(i); @@ -1210,12 +1226,29 @@ void tst_QImage::rotate() QCOMPARE(original, dest); } +void tst_QImage::rotateBigImage() +{ + // QTBUG-105088 + QImage big_image(3840, 2160, QImage::Format_ARGB32_Premultiplied); + QTransform t; + t.translate(big_image.width() / 2.0, big_image.height() / 2.0); + t.rotate(-89, Qt::YAxis, big_image.width()); + t.translate(-big_image.width() / 2.0, -big_image.height() / 2.0); + QVERIFY(!big_image.transformed(t).isNull()); + + QMatrix4x4 m; + m.translate(big_image.width() / 2.0, big_image.height() / 2.0); + m.projectedRotate(89, 0, 1, 0, big_image.width()); + m.translate(-big_image.width() / 2.0, -big_image.height() / 2.0); + QVERIFY(!big_image.transformed(m.toTransform()).isNull()); +} + void tst_QImage::copy() { // Task 99250 { QImage img(16,16,QImage::Format_ARGB32); - img.copy(QRect(1000,1,1,1)); + (void)img.copy(QRect(1000,1,1,1)); } } @@ -1245,15 +1278,36 @@ void tst_QImage::loadFromData() QVERIFY(original.save(&buf, "BMP")); } QVERIFY(!ba.isEmpty()); + const uchar *baPtr = reinterpret_cast<const uchar *>(ba.constData()); - QImage dest; - QVERIFY(dest.loadFromData(ba, "BMP")); - QVERIFY(!dest.isNull()); + { + QImage dest; + QVERIFY(dest.loadFromData(QByteArrayView(ba), "BMP")); + QCOMPARE(original, dest); - QCOMPARE(original, dest); + QVERIFY(!dest.loadFromData(QByteArrayView())); + QVERIFY(dest.isNull()); + } + { + QImage dest; + QVERIFY(dest.loadFromData(ba, "BMP")); + QCOMPARE(original, dest); - QVERIFY(!dest.loadFromData(QByteArray())); - QVERIFY(dest.isNull()); + QVERIFY(!dest.loadFromData(QByteArray())); + QVERIFY(dest.isNull()); + } + { + QImage dest; + QVERIFY(dest.loadFromData(baPtr, int(ba.size()), "BMP")); + QCOMPARE(original, dest); + + QVERIFY(!dest.loadFromData(nullptr, 0)); + QVERIFY(dest.isNull()); + } + + QCOMPARE(original, QImage::fromData(QByteArrayView(ba), "BMP")); + QCOMPARE(original, QImage::fromData(ba, "BMP")); + QCOMPARE(original, QImage::fromData(baPtr, int(ba.size()), "BMP")); } #if !defined(QT_NO_DATASTREAM) @@ -1449,6 +1503,64 @@ void tst_QImage::setPixel() } } +void tst_QImage::setPixelWithAlpha_data() +{ + QTest::addColumn<QImage::Format>("format"); + + for (int c = QImage::Format_RGB32; c < QImage::NImageFormats; ++c) { + if (c == QImage::Format_Grayscale8) + continue; + if (c == QImage::Format_Grayscale16) + continue; + if (c == QImage::Format_Alpha8) + continue; + if (c == QImage::Format_CMYK8888) + continue; + QTest::newRow(qPrintable(formatToString(QImage::Format(c)))) << QImage::Format(c); + } +} + +void tst_QImage::setPixelWithAlpha() +{ + QFETCH(QImage::Format, format); + QImage image(1, 1, format); + QRgb referenceColor = qRgba(0, 170, 85, 170); + image.setPixel(0, 0, referenceColor); + + if (!image.hasAlphaChannel()) + referenceColor = 0xff000000 | referenceColor; + + QRgb color = image.pixel(0, 0); + QCOMPARE(qRed(color) & 0xf0, qRed(referenceColor) & 0xf0); + QCOMPARE(qGreen(color) & 0xf0, qGreen(referenceColor) & 0xf0); + QCOMPARE(qBlue(color) & 0xf0, qBlue(referenceColor) & 0xf0); + QCOMPARE(qAlpha(color) & 0xf0, qAlpha(referenceColor) & 0xf0); +} + +void tst_QImage::setPixelColorWithAlpha_data() +{ + setPixelWithAlpha_data(); +} + +void tst_QImage::setPixelColorWithAlpha() +{ + QFETCH(QImage::Format, format); + QImage image(1, 1, format); + image.setPixelColor(0, 0, QColor(170, 85, 255, 170)); + QRgb referenceColor = qRgba(170, 85, 255, 170); + + if (!image.hasAlphaChannel()) + referenceColor = 0xff000000 | referenceColor; + else if (image.pixelFormat().premultiplied() == QPixelFormat::Premultiplied) + referenceColor = qPremultiply(referenceColor); + + QRgb color = image.pixel(0, 0); + QCOMPARE(qRed(color) & 0xf0, qRed(referenceColor) & 0xf0); + QCOMPARE(qGreen(color) & 0xf0, qGreen(referenceColor) & 0xf0); + QCOMPARE(qBlue(color) & 0xf0, qBlue(referenceColor) & 0xf0); + QCOMPARE(qAlpha(color) & 0xf0, qAlpha(referenceColor) & 0xf0); +} + void tst_QImage::convertToFormatPreserveDotsPrMeter() { QImage img(100, 100, QImage::Format_ARGB32_Premultiplied); @@ -1697,7 +1809,17 @@ void tst_QImage::smoothScale2_data() QTest::addColumn<int>("size"); int sizes[] = { 2, 3, 4, 6, 7, 8, 10, 16, 20, 32, 40, 64, 100, 101, 128, 0 }; - QImage::Format formats[] = { QImage::Format_RGB32, QImage::Format_ARGB32_Premultiplied, QImage::Format_RGBX64, QImage::Format_RGBA64_Premultiplied, QImage::Format_Invalid }; + QImage::Format formats[] = { QImage::Format_RGB32, + QImage::Format_ARGB32_Premultiplied, +#if QT_CONFIG(raster_64bit) + QImage::Format_RGBX64, + QImage::Format_RGBA64_Premultiplied, +#endif +#if QT_CONFIG(raster_fp) + QImage::Format_RGBX32FPx4, + QImage::Format_RGBA32FPx4_Premultiplied, +#endif + QImage::Format_Invalid }; for (int j = 0; formats[j] != QImage::Format_Invalid; ++j) { QString formatstr = formatToString(formats[j]); for (int i = 0; sizes[i] != 0; ++i) { @@ -1712,11 +1834,9 @@ void tst_QImage::smoothScale2() QFETCH(QImage::Format, format); QFETCH(int, size); - bool opaque = (format == QImage::Format_RGB32 || format == QImage::Format_RGBX64); - - QRgb expected = opaque ? qRgb(63, 127, 255) : qRgba(31, 63, 127, 127); - QImage img(size, size, format); + bool opaque = !img.hasAlphaChannel(); + QRgb expected = opaque ? qRgb(63, 127, 255) : qRgba(31, 63, 127, 127); img.fill(expected); // scale x down, y down @@ -1861,6 +1981,9 @@ void tst_QImage::smoothScale4_data() #if QT_CONFIG(raster_64bit) QTest::newRow("RGBx64") << QImage::Format_RGBX64; #endif +#if QT_CONFIG(raster_fp) + QTest::newRow("RGBx32FP") << QImage::Format_RGBX32FPx4; +#endif } void tst_QImage::smoothScale4() @@ -1922,6 +2045,52 @@ void tst_QImage::smoothScaleAlpha() QCOMPARE(dst, expected); } +void tst_QImage::smoothScaleFormats_data() +{ + QTest::addColumn<QImage::Format>("format"); + for (int i = QImage::Format_RGB32; i < QImage::NImageFormats; ++i) { + QTest::addRow("%s", formatToString(QImage::Format(i)).data()) << QImage::Format(i); + } +} + +void tst_QImage::smoothScaleFormats() +{ + QFETCH(QImage::Format, format); + QImage src(32, 32, format); + src.fill(0x0); + + // Upscale using painter scaling + QImage scaled = src.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + QCOMPARE(scaled.format(), src.format()); + + // > 2x down-scaling using QImage::smoothScaled() + scaled = src.scaled(8, 8, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + QCOMPARE(scaled.format(), src.format()); + + QTransform transform; + transform.rotate(45); + QImage rotated = src.transformed(transform); + QVERIFY(rotated.hasAlphaChannel()); +} + +void tst_QImage::smoothScaleNoConversion_data() +{ + QTest::addColumn<QImage::Format>("format"); + QTest::addRow("Mono") << QImage::Format_Mono; + QTest::addRow("MonoLSB") << QImage::Format_MonoLSB; + QTest::addRow("Indexed8") << QImage::Format_Indexed8; +} + +void tst_QImage::smoothScaleNoConversion() +{ + QFETCH(QImage::Format, format); + QImage img(128, 128, format); + img.fill(1); + img.setColorTable(QList<QRgb>() << qRgba(255,0,0,255) << qRgba(0,0,0,0)); + img = img.scaled(QSize(48, 48), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + QVERIFY(img.hasAlphaChannel()); +} + static int count(const QImage &img, int x, int y, int dx, int dy, QRgb pixel) { int i = 0; @@ -2260,6 +2429,8 @@ void tst_QImage::fillColor_data() QImage::Format_RGBA8888_Premultiplied, QImage::Format_BGR30, QImage::Format_A2RGB30_Premultiplied, + QImage::Format_RGBX16FPx4, + QImage::Format_RGBA32FPx4_Premultiplied, }; for (int i=0; names[i] != 0; ++i) { @@ -2314,15 +2485,28 @@ void tst_QImage::fillColor() } } -void tst_QImage::fillColorWithAlpha() +void tst_QImage::fillColorWithAlpha_data() { - QImage argb32(1, 1, QImage::Format_ARGB32); - argb32.fill(QColor(255, 0, 0, 127)); - QCOMPARE(argb32.pixel(0, 0), qRgba(255, 0, 0, 127)); + setPixelWithAlpha_data(); +} - QImage argb32pm(1, 1, QImage::Format_ARGB32_Premultiplied); - argb32pm.fill(QColor(255, 0, 0, 127)); - QCOMPARE(argb32pm.pixel(0, 0), 0x7f7f0000u); +void tst_QImage::fillColorWithAlpha() +{ + QFETCH(QImage::Format, format); + QImage image(1, 1, format); + image.fill(QColor(255, 170, 85, 170)); + QRgb referenceColor = qRgba(255, 170, 85, 170); + + if (!image.hasAlphaChannel()) + referenceColor = 0xff000000 | referenceColor; + else if (image.pixelFormat().premultiplied() == QPixelFormat::Premultiplied) + referenceColor = qPremultiply(referenceColor); + + QRgb color = image.pixel(0, 0); + QCOMPARE(qRed(color) & 0xf0, qRed(referenceColor) & 0xf0); + QCOMPARE(qGreen(color) & 0xf0, qGreen(referenceColor) & 0xf0); + QCOMPARE(qBlue(color) & 0xf0, qBlue(referenceColor) & 0xf0); + QCOMPARE(qAlpha(color) & 0xf0, qAlpha(referenceColor) & 0xf0); } void tst_QImage::fillRGB888() @@ -2348,10 +2532,13 @@ void tst_QImage::fillPixel_data() QTest::newRow("RGB16, transparent") << QImage::Format_RGB16 << 0x0u << 0xff000000u; QTest::newRow("RGB32, transparent") << QImage::Format_RGB32 << 0x0u << 0xff000000u; + QTest::newRow("RGB444, transparent") << QImage::Format_RGB444 << 0x0u << 0xff000000u; + QTest::newRow("RGB666, transparent") << QImage::Format_RGB666 << 0x0u << 0xff000000u; QTest::newRow("RGBx8888, transparent") << QImage::Format_RGBX8888 << 0x0u << 0xff000000u; QTest::newRow("ARGB32, transparent") << QImage::Format_ARGB32 << 0x0u << 0x00000000u; QTest::newRow("ARGB32pm, transparent") << QImage::Format_ARGB32_Premultiplied << 0x0u << 0x00000000u; QTest::newRow("RGBA8888pm, transparent") << QImage::Format_RGBA8888_Premultiplied << 0x0u << 0x00000000u; + QTest::newRow("Grayscale8, transparent") << QImage::Format_Grayscale8 << 0x0u << 0xff000000u; QTest::newRow("Alpha8, transparent") << QImage::Format_Alpha8 << 0x0u << 0x00000000u; QTest::newRow("RGB16, red") << QImage::Format_RGB16 << (uint)qConvertRgb32To16(0xffff0000) << 0xffff0000u; @@ -2359,13 +2546,14 @@ void tst_QImage::fillPixel_data() QTest::newRow("ARGB32, red") << QImage::Format_ARGB32 << 0xffff0000u << 0xffff0000u; QTest::newRow("RGBA8888, red") << QImage::Format_RGBA8888 << 0xff0000ffu << 0xffff0000u; - QTest::newRow("Grayscale8, grey") << QImage::Format_Grayscale8 << 0xff808080u << 0xff808080u; + QTest::newRow("Grayscale8, grey") << QImage::Format_Grayscale8 << 0x80u << 0xff808080u; QTest::newRow("RGB32, semi-red") << QImage::Format_RGB32 << 0x80ff0000u << 0xffff0000u; QTest::newRow("ARGB32, semi-red") << QImage::Format_ARGB32 << 0x80ff0000u << 0x80ff0000u; QTest::newRow("ARGB32pm, semi-red") << QImage::Format_ARGB32 << 0x80800000u << 0x80800000u; QTest::newRow("RGBA8888pm, semi-red") << QImage::Format_RGBA8888_Premultiplied << 0x80000080u << 0x80800000u; - QTest::newRow("Alpha8, semi-red") << QImage::Format_Alpha8 << 0x80000080u << 0x80000000u; + + QTest::newRow("Alpha8, semi-transparent") << QImage::Format_Alpha8 << 0x80u << 0x80000000u; } void tst_QImage::fillPixel() @@ -2378,6 +2566,8 @@ void tst_QImage::fillPixel() image.fill(color); QCOMPARE(image.pixel(0, 0), pixelValue); + if (image.depth() == 8) + QCOMPARE(*(const uchar *)image.constBits(), color); } void tst_QImage::rgbSwapped_data() @@ -2387,7 +2577,8 @@ void tst_QImage::rgbSwapped_data() for (int i = QImage::Format_Indexed8; i < QImage::NImageFormats; ++i) { if (i == QImage::Format_Alpha8 || i == QImage::Format_Grayscale8 - || i == QImage::Format_Grayscale16) { + || i == QImage::Format_Grayscale16 + || i == QImage::Format_CMYK8888) { continue; } QTest::addRow("%s", formatToString(QImage::Format(i)).data()) << QImage::Format(i); @@ -2431,11 +2622,11 @@ void tst_QImage::rgbSwapped() QCOMPARE(swappedColor.blue(), referenceColor.red()); } - QImage imageSwappedTwice = imageSwapped.rgbSwapped(); + imageSwapped.rgbSwap(); - QCOMPARE(image, imageSwappedTwice); + QCOMPARE(image, imageSwapped); - QCOMPARE(memcmp(image.constBits(), imageSwappedTwice.constBits(), image.sizeInBytes()), 0); + QCOMPARE(memcmp(image.constBits(), imageSwapped.constBits(), image.sizeInBytes()), 0); } void tst_QImage::mirrored_data() @@ -2481,20 +2672,20 @@ void tst_QImage::mirrored_data() QTest::newRow("Format_Mono, horizontal+vertical") << QImage::Format_Mono << true << true << 16 << 16; QTest::newRow("Format_MonoLSB, horizontal+vertical") << QImage::Format_MonoLSB << true << true << 16 << 16; - QTest::newRow("Format_RGB32, vertical") << QImage::Format_RGB32 << true << false << 8 << 16; - QTest::newRow("Format_ARGB32, vertical") << QImage::Format_ARGB32 << true << false << 16 << 8; + QTest::newRow("Format_RGB32, vertical, narrow") << QImage::Format_RGB32 << true << false << 8 << 16; + QTest::newRow("Format_ARGB32, vertical, short") << QImage::Format_ARGB32 << true << false << 16 << 8; QTest::newRow("Format_Mono, vertical, non-aligned") << QImage::Format_Mono << true << false << 19 << 25; QTest::newRow("Format_MonoLSB, vertical, non-aligned") << QImage::Format_MonoLSB << true << false << 19 << 25; // Non-aligned horizontal 1-bit needs special handling so test this. QTest::newRow("Format_Mono, horizontal, non-aligned") << QImage::Format_Mono << false << true << 13 << 17; - QTest::newRow("Format_Mono, horizontal, non-aligned") << QImage::Format_Mono << false << true << 19 << 25; - QTest::newRow("Format_Mono, horizontal+vertical, non-aligned") << QImage::Format_Mono << true << true << 25 << 47; + QTest::newRow("Format_Mono, horizontal, non-aligned, big") << QImage::Format_Mono << false << true << 19 << 25; + QTest::newRow("Format_Mono, horizontal+vertical, non-aligned, big") << QImage::Format_Mono << true << true << 25 << 47; QTest::newRow("Format_Mono, horizontal+vertical, non-aligned") << QImage::Format_Mono << true << true << 21 << 16; QTest::newRow("Format_MonoLSB, horizontal, non-aligned") << QImage::Format_MonoLSB << false << true << 13 << 17; - QTest::newRow("Format_MonoLSB, horizontal, non-aligned") << QImage::Format_MonoLSB << false << true << 19 << 25; - QTest::newRow("Format_MonoLSB, horizontal+vertical, non-aligned") << QImage::Format_MonoLSB << true << true << 25 << 47; + QTest::newRow("Format_MonoLSB, horizontal, non-aligned, big") << QImage::Format_MonoLSB << false << true << 19 << 25; + QTest::newRow("Format_MonoLSB, horizontal+vertical, non-aligned, big") << QImage::Format_MonoLSB << true << true << 25 << 47; QTest::newRow("Format_MonoLSB, horizontal+vertical, non-aligned") << QImage::Format_MonoLSB << true << true << 21 << 16; } @@ -2543,16 +2734,16 @@ void tst_QImage::mirrored() } } - QImage imageMirroredTwice = imageMirrored.mirrored(swap_horizontal, swap_vertical); + imageMirrored.mirror(swap_horizontal, swap_vertical); - QCOMPARE(image, imageMirroredTwice); + QCOMPARE(image, imageMirrored); if (format != QImage::Format_Mono && format != QImage::Format_MonoLSB) - QCOMPARE(memcmp(image.constBits(), imageMirroredTwice.constBits(), image.sizeInBytes()), 0); + QCOMPARE(memcmp(image.constBits(), imageMirrored.constBits(), image.sizeInBytes()), 0); else { for (int i = 0; i < image.height(); ++i) for (int j = 0; j < image.width(); ++j) - QCOMPARE(image.pixel(j,i), imageMirroredTwice.pixel(j,i)); + QCOMPARE(image.pixel(j,i), imageMirrored.pixel(j,i)); } } @@ -2563,7 +2754,6 @@ void tst_QImage::inplaceRgbSwapped_data() void tst_QImage::inplaceRgbSwapped() { -#if defined(Q_COMPILER_REF_QUALIFIERS) QFETCH(QImage::Format, format); QImage image(64, 1, format); @@ -2571,7 +2761,7 @@ void tst_QImage::inplaceRgbSwapped() QList<QRgb> testColor(image.width()); for (int i = 0; i < image.width(); ++i) - testColor[i] = qRgb(i * 2, i * 3, 255 - i * 4); + testColor[i] = qRgb(i * 2, i * 3, std::min(255 - i * 4, 0)); if (format == QImage::Format_Indexed8) { for (int i = 0; i < image.width(); ++i) { @@ -2624,7 +2814,6 @@ void tst_QImage::inplaceRgbSwapped() QCOMPARE(dataSwapped, orig.rgbSwapped()); } -#endif } @@ -2836,21 +3025,29 @@ void tst_QImage::genericRgbConversion() QImage image(16, 16, format); - for (int i = 0; i < image.height(); ++i) - for (int j = 0; j < image.width(); ++j) - image.setPixel(j, i, qRgb(j*16, i*16, 0)); + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + if (srcGrayscale || dstGrayscale) + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + else + image.setPixel(j, i, qRgb(j * 16, i * 16, (i + j) * 8)); + } + } QImage imageConverted = image.convertToFormat(dest_format); + uint mask = std::min(image.depth(), imageConverted.depth()) < 32 ? 0xFFF0F0F0 : 0xFFFFFFFF; + if (srcGrayscale || dstGrayscale) + mask = std::max(image.depth(), imageConverted.depth()) < 32 ? 0xFFF0F0F0 : 0xFFFFFFFF; + if (srcGrayscale && dstGrayscale) + mask = 0xFFFFFFFF; QCOMPARE(imageConverted.format(), dest_format); for (int i = 0; i < imageConverted.height(); ++i) { for (int j = 0; j < imageConverted.width(); ++j) { QRgb convertedColor = imageConverted.pixel(j,i); - if (srcGrayscale || dstGrayscale) { - QVERIFY(qAbs(qGray(convertedColor) - qGray(qRgb(j*16, i*16, 0))) < 15); - } else { - QCOMPARE(qRed(convertedColor) & 0xF0, j * 16); - QCOMPARE(qGreen(convertedColor) & 0xF0, i * 16); - } + if (srcGrayscale || dstGrayscale) + QCOMPARE(convertedColor & mask, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8) & mask); + else + QCOMPARE(convertedColor & mask, qRgb(j * 16, i * 16, (i + j) * 8) & mask); } } } @@ -2863,13 +3060,15 @@ void tst_QImage::inplaceRgbConversion_data() for (int i = QImage::Format_RGB32; i < QImage::NImageFormats; ++i) { if (i == QImage::Format_Alpha8 || i == QImage::Format_Grayscale8 - || i == QImage::Format_Grayscale16) { + || i == QImage::Format_Grayscale16 + || i == QImage::Format_CMYK8888) { continue; } for (int j = QImage::Format_RGB32; j < QImage::NImageFormats; ++j) { if (j == QImage::Format_Alpha8 || j == QImage::Format_Grayscale8 - || j == QImage::Format_Grayscale16) { + || j == QImage::Format_Grayscale16 + || j == QImage::Format_CMYK8888) { continue; } if (i == j) @@ -2883,6 +3082,7 @@ void tst_QImage::inplaceRgbConversion_data() void tst_QImage::inplaceRgbConversion() { + // Test that conversions between RGB formats of the same bitwidth can be done inplace. QFETCH(QImage::Format, format); QFETCH(QImage::Format, dest_format); @@ -2899,8 +3099,7 @@ void tst_QImage::inplaceRgbConversion() for (int i = 0; i < imageConverted.height(); ++i) { for (int j = 0; j < imageConverted.width(); ++j) { QRgb convertedColor = imageConverted.pixel(j,i); - QCOMPARE(qRed(convertedColor) & 0xF0, j * 16); - QCOMPARE(qGreen(convertedColor) & 0xF0, i * 16); + QCOMPARE(convertedColor & 0xFFF0F0F0, qRgb(j * 16, i * 16, 0)); } } if (qt_depthForFormat(format) == qt_depthForFormat(dest_format)) @@ -3050,6 +3249,144 @@ void tst_QImage::largeInplaceRgbConversion() } } +void tst_QImage::colorSpaceRgbConversion_data() +{ + QTest::addColumn<QImage::Format>("fromFormat"); + QTest::addColumn<QImage::Format>("toFormat"); + + // The various possible code paths for color space conversions compatible with RGB color spaces: + QImage::Format formats[] = { + QImage::Format_RGB32, + QImage::Format_ARGB32, + QImage::Format_ARGB32_Premultiplied, + QImage::Format_RGBX64, + QImage::Format_RGBA64, + QImage::Format_RGBA64_Premultiplied, + QImage::Format_RGBX32FPx4, + QImage::Format_RGBA32FPx4, + QImage::Format_RGBA32FPx4_Premultiplied, + QImage::Format_Grayscale8, + QImage::Format_Grayscale16, + }; + + for (auto fromFormat : formats) { + const QLatin1String formatI = formatToString(fromFormat); + for (auto toFormat : formats) { + QTest::addRow("%s -> %s", formatI.data(), formatToString(toFormat).data()) + << fromFormat << toFormat; + } + } +} + +void tst_QImage::colorSpaceRgbConversion() +{ + // Test that all color space conversions work + QFETCH(QImage::Format, fromFormat); + QFETCH(QImage::Format, toFormat); + + bool srcGrayscale = fromFormat == QImage::Format_Grayscale8 || fromFormat == QImage::Format_Grayscale16; + bool dstGrayscale = toFormat == QImage::Format_Grayscale8 || toFormat == QImage::Format_Grayscale16; + + QImage image(16, 16, fromFormat); + image.setColorSpace(QColorSpace::SRgb); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + if (srcGrayscale || dstGrayscale) + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + else + image.setPixel(j, i, qRgb(j * 16, i * 16, (i + j) * 8)); + } + } + + QImage imageConverted = image.convertedToColorSpace(QColorSpace::DisplayP3, toFormat); + QCOMPARE(imageConverted.format(), toFormat); + QCOMPARE(imageConverted.size(), image.size()); + if (dstGrayscale) { + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 6)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } + } else { + int red = 0; + int blue = 0; + for (int x = 0; x < image.width(); ++x) { + int newRed = qRed(imageConverted.pixel(x, 5)); + int newBlue = qBlue(imageConverted.pixel(x, 7)); + QCOMPARE_GE(newBlue, blue); + QCOMPARE_GE(newRed, red); + blue = newBlue; + red = newRed; + } + } +} + + +void tst_QImage::colorSpaceCmykConversion_data() +{ + QTest::addColumn<QImage::Format>("toFormat"); + + QImage::Format formats[] = { + QImage::Format_RGB32, + QImage::Format_ARGB32, + QImage::Format_ARGB32_Premultiplied, + QImage::Format_RGBX64, + QImage::Format_RGBA64, + QImage::Format_RGBA64_Premultiplied, + QImage::Format_RGBX32FPx4, + QImage::Format_RGBA32FPx4, + QImage::Format_RGBA32FPx4_Premultiplied, + QImage::Format_Grayscale8, + QImage::Format_Grayscale16, + }; + + for (auto toFormat : formats) + QTest::addRow("CMYK8888 -> %s", formatToString(toFormat).data()) << toFormat; +} + +void tst_QImage::colorSpaceCmykConversion() +{ + QFETCH(QImage::Format, toFormat); + + bool dstGrayscale = toFormat == QImage::Format_Grayscale8 || toFormat == QImage::Format_Grayscale16; + + QImage image(16, 16, QImage::Format_CMYK8888); + QFile iccProfile(m_prefix +"CGATS001Compat-v2-micro.icc"); + iccProfile.open(QIODevice::ReadOnly); + image.setColorSpace(QColorSpace::fromIccProfile(iccProfile.readAll())); + QVERIFY(image.colorSpace().isValid()); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + if (dstGrayscale) + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + else + image.setPixel(j, i, qRgb(j * 16, i * 16, (i + j) * 8)); + } + } + + QImage imageConverted = image.convertedToColorSpace(QColorSpace::SRgb, toFormat); + QCOMPARE(imageConverted.format(), toFormat); + QCOMPARE(imageConverted.size(), image.size()); + if (dstGrayscale) { + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 6)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } + } else { + int red = 0; + for (int x = 0; x < image.width(); ++x) { + int newRed = qRed(imageConverted.pixel(x, 5)); + QCOMPARE_GE(newRed, red); + red = newRed; + } + } +} + void tst_QImage::deepCopyWhenPaintingActive() { QImage image(64, 64, QImage::Format_ARGB32_Premultiplied); @@ -3158,7 +3495,8 @@ void tst_QImage::invertPixelsRGB_data() for (int i = QImage::Format_RGB32; i < QImage::NImageFormats; ++i) { if (i == QImage::Format_Alpha8 || i == QImage::Format_Grayscale8 - || i == QImage::Format_Grayscale16) { + || i == QImage::Format_Grayscale16 + || i == QImage::Format_CMYK8888) { continue; } QTest::addRow("%s", formatToString(QImage::Format(i)).data()) << QImage::Format(i); @@ -3290,6 +3628,9 @@ void tst_QImage::exifInvalidData() void tst_QImage::exifReadComments() { +#ifdef QT_NO_IMAGEIO_TEXT_LOADING + QSKIP("Reading text from image file is configured off"); +#endif QImage image; QVERIFY(image.load(m_prefix + "jpeg_exif_utf8_comment.jpg")); QVERIFY(!image.isNull()); @@ -3339,7 +3680,7 @@ void tst_QImage::cleanupFunctions() { called = false; - QImage *copy = 0; + QImage *copy = nullptr; { QImage image(bufferImage.bits(), bufferImage.width(), bufferImage.height(), bufferImage.format(), cleanupFunction, &called); copy = new QImage(image); @@ -3348,7 +3689,38 @@ void tst_QImage::cleanupFunctions() delete copy; QVERIFY(called); } - + { + called = false; + QImage container; + { + QImage image(bufferImage.bits(), bufferImage.width(), bufferImage.height(), bufferImage.format(), cleanupFunction, &called); + container = std::move(image); + // Test methods don't crash after move: + Q_UNUSED(image.isNull()); + Q_UNUSED(image.width()); + Q_UNUSED(image.bytesPerLine()); + Q_UNUSED(image.sizeInBytes()); + Q_UNUSED(image.constBits()); + } + // 'image' was moved and should outlive its scope + QVERIFY(!called); + container = QImage(); + QVERIFY(called); + } + { + called = false; + QImage outer(bufferImage.bits(), bufferImage.width(), bufferImage.height(), bufferImage.format(), cleanupFunction, &called); + bool called2 = false; + { + uchar internalData[256]; + QImage internal(internalData, 16, 16, QImage::Format_Grayscale8, cleanupFunction, &called2); + internal = std::move(outer); + } + // 'internal' was _not_ moved and should not outlive its original scope + QVERIFY(called2); + // 'outer' was moved into the inner scope and should now be dead. + QVERIFY(called); + } } // test image devicePixelRatio setting and detaching @@ -3378,6 +3750,15 @@ void tst_QImage::devicePixelRatio() QCOMPARE(b.devicePixelRatio(), qreal(1.0)); } +void tst_QImage::deviceIndependentSize() { + QImage a(64, 64, QImage::Format_ARGB32); + a.fill(Qt::white); + a.setDevicePixelRatio(1.0); + QCOMPARE(a.deviceIndependentSize(), QSizeF(64, 64)); + a.setDevicePixelRatio(2.0); + QCOMPARE(a.deviceIndependentSize(), QSizeF(32, 32)); +} + void tst_QImage::rgb30Unpremul() { QImage a(3, 1, QImage::Format_A2RGB30_Premultiplied); @@ -3464,6 +3845,13 @@ void tst_QImage::metadataPassthrough() QCOMPARE(converted.dotsPerMeterY(), a.dotsPerMeterY()); QCOMPARE(converted.devicePixelRatio(), a.devicePixelRatio()); + QList<QRgb> clut({ 0xFFFF0000, 0xFF00FF00, 0xFF0000FF }); + QImage convertedWithClut = a.convertToFormat(QImage::Format_Indexed8, clut); + QCOMPARE(convertedWithClut.text(QStringLiteral("Test")), a.text(QStringLiteral("Test"))); + QCOMPARE(convertedWithClut.dotsPerMeterX(), a.dotsPerMeterX()); + QCOMPARE(convertedWithClut.dotsPerMeterY(), a.dotsPerMeterY()); + QCOMPARE(convertedWithClut.devicePixelRatio(), a.devicePixelRatio()); + QImage copied = a.copy(0, 0, a.width() / 2, a.height() / 2); QCOMPARE(copied.text(QStringLiteral("Test")), a.text(QStringLiteral("Test"))); QCOMPARE(copied.dotsPerMeterX(), a.dotsPerMeterX()); @@ -3508,6 +3896,14 @@ void tst_QImage::pixelColor() // Try setting an invalid color. QTest::ignoreMessage(QtWarningMsg, "QImage::setPixelColor: color is invalid"); argb32.setPixelColor(0, 0, QColor()); + + // Test correct premultiplied handling of RGBA64 as well + QImage rgba64(1, 1, QImage::Format_RGBA64); + QImage rgba64pm(1, 1, QImage::Format_RGBA64_Premultiplied); + rgba64.setPixelColor(QPoint(0, 0), c); + rgba64pm.setPixelColor(QPoint(0, 0), c); + QCOMPARE(rgba64.pixelColor(QPoint(0, 0)), c); + QCOMPARE(rgba64pm.pixelColor(QPoint(0, 0)), c); } void tst_QImage::pixel() @@ -3628,7 +4024,7 @@ void tst_QImage::reinterpretAsFormat_data() QTest::newRow("rgb32 -> argb32") << QImage::Format_RGB32 << QImage::Format_ARGB32 << QColor(Qt::cyan) << QColor(Qt::cyan); QTest::newRow("argb32pm -> rgb32") << QImage::Format_ARGB32_Premultiplied << QImage::Format_RGB32 << QColor(Qt::transparent) << QColor(Qt::black); QTest::newRow("argb32 -> rgb32") << QImage::Format_ARGB32 << QImage::Format_RGB32 << QColor(255, 0, 0, 127) << QColor(255, 0, 0); - QTest::newRow("argb32pm -> rgb32") << QImage::Format_ARGB32_Premultiplied << QImage::Format_RGB32 << QColor(255, 0, 0, 127) << QColor(127, 0, 0); + QTest::newRow("argb32pm (red) -> rgb32") << QImage::Format_ARGB32_Premultiplied << QImage::Format_RGB32 << QColor(255, 0, 0, 127) << QColor(127, 0, 0); } void tst_QImage::reinterpretAsFormat() @@ -3723,7 +4119,10 @@ void tst_QImage::hugeQImage() #if Q_PROCESSOR_WORDSIZE < 8 QSKIP("Test only makes sense on 64-bit machines"); #else - QImage image(25000, 25000, QImage::Format_RGB32); + std::unique_ptr<char[]> enough(new (std::nothrow) char[qsizetype(25000)*25000*4]); + if (!enough) + QSKIP("Could not allocate enough memory"); + QImage image((uchar*)enough.get(), 25000, 25000, QImage::Format_RGB32); QVERIFY(!image.isNull()); QCOMPARE(image.height(), 25000); @@ -3776,6 +4175,95 @@ void tst_QImage::wideImage() // Qt6: Test that it actually works on 64bit architectures. } +void tst_QImage::largeFillScale() +{ +#if Q_PROCESSOR_WORDSIZE < 8 + QSKIP("Test fails on 32-bit builds"); +#endif + // Test from QTBUG-84428 + QImage input(QSize(std::numeric_limits<qint16>::max() + 10, 1), QImage::Format_ARGB32_Premultiplied); + input.fill(Qt::white); + + const int scaleFactor = 2; + QImage scaled = input.scaled(input.width(), input.height() * scaleFactor); + + for (int x = 0, w = input.width(); x < w; ++x) { + const auto inputPixel = input.pixel(x, 0); + auto scaledPixel = scaled.pixel(x, 0); + QCOMPARE(scaledPixel, inputPixel); + scaledPixel = scaled.pixel(x, 1); + QCOMPARE(scaledPixel, inputPixel); + } +} + +void tst_QImage::largeRasterScale() +{ +#if Q_PROCESSOR_WORDSIZE < 8 + QSKIP("Test fails on 32-bit builds"); +#endif + // Now test that qgrayraster still works at these ranges + QImage image(QSize(40000, 200), QImage::Format_RGB32); + image.fill(Qt::white); + + QPainter painter(&image); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(Qt::black); + painter.drawEllipse(QPoint(33000, 100), 6990, 99); + painter.end(); + QCOMPARE(image.pixelColor(27000, 10), Qt::white); + QCOMPARE(image.pixelColor(33000, 10), Qt::black); + QCOMPARE(image.pixelColor(39000, 10), Qt::white); + QCOMPARE(image.pixelColor(27000, 100), Qt::black); + QCOMPARE(image.pixelColor(33000, 100), Qt::black); + QCOMPARE(image.pixelColor(39000, 100), Qt::black); + QCOMPARE(image.pixelColor(27000, 190), Qt::white); + QCOMPARE(image.pixelColor(33000, 190), Qt::black); + QCOMPARE(image.pixelColor(39000, 190), Qt::white); + + // Now check grayscale antialiasing takes place in the higher coords + bool grayObserved = false; + for (int x = 33000; x < 39000; ++x) { + QRgb pixel = image.pixel(x, 20); + if (pixel == 0xff000000) + continue; // still black + if (pixel == 0xffffffff) { + QVERIFY(grayObserved); + break; + } + grayObserved = true; + } + +// image.save("largeRasterScale.png", "PNG"); +} + +void tst_QImage::metadataChangeWithReadOnlyPixels() +{ + const QRgb data[3] = { qRgb(255, 0, 0), qRgb(0, 255, 0), qRgb(0, 0, 255) }; + QImage image((const uchar *)data, 3, 1, QImage::Format_RGB32); + + QCOMPARE(image.constBits(), (const uchar *)data); + image.setDotsPerMeterX(100); + QCOMPARE(image.constBits(), (const uchar *)data); + + QImage image2 = image; + QCOMPARE(image2.constBits(), (const uchar *)data); + image2.setDotsPerMeterX(200); + // Pixels and metadata has the same sharing mechanism, so a change of a shared + // image metadata forces pixel detach (remove this sub-test if that ever changes). + QVERIFY(image2.constBits() != (const uchar *)data); + QCOMPARE(image.constBits(), (const uchar *)data); +} + +void tst_QImage::scaleIndexed() +{ + QImage image(10, 10, QImage::Format_Indexed8); + image.setColor(0, qRgb(0,0,0)); + image.setColor(1, qRgb(1,1,1)); + image.fill(1); + image.setDevicePixelRatio(2); + QImage image2 = image.scaled(20, 20, Qt::KeepAspectRatio, Qt::SmoothTransformation); // do not crash +} + #if defined(Q_OS_WIN) static inline QColor COLORREFToQColor(COLORREF cr) @@ -3888,5 +4376,27 @@ void tst_QImage::fromMonoHBITMAP() // QTBUG-72343, corruption for mono bitmaps #endif // Q_OS_WIN +void tst_QImage::tofromPremultipliedFormat_data() +{ + QTest::addColumn<QImage::Format>("unpremul"); + QTest::addColumn<QImage::Format>("premul"); + + // Test all available formats with both premultiplied and unpremultiplied versions + QTest::newRow("argb32") << QImage::Format_ARGB32 << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("rgba8888") << QImage::Format_RGBA8888 << QImage::Format_RGBA8888_Premultiplied; + QTest::newRow("rgba64") << QImage::Format_RGBA64 << QImage::Format_RGBA64_Premultiplied; + QTest::newRow("rgba16fpx4") << QImage::Format_RGBA16FPx4 << QImage::Format_RGBA16FPx4_Premultiplied; + QTest::newRow("rgba32fpx4") << QImage::Format_RGBA32FPx4 << QImage::Format_RGBA32FPx4_Premultiplied; +} + +void tst_QImage::tofromPremultipliedFormat() +{ + QFETCH(QImage::Format, unpremul); + QFETCH(QImage::Format, premul); + + QCOMPARE(qt_toPremultipliedFormat(unpremul), premul); + QCOMPARE(qt_toUnpremultipliedFormat(premul), unpremul); +} + QTEST_GUILESS_MAIN(tst_QImage) #include "tst_qimage.moc" |