From f1983dcdf6c596f901694ad16dcd3c74c77f4c13 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Wed, 28 Apr 2021 12:40:11 +0200 Subject: [PATCH] Correct RGB to Grayscale conversion The existing conversions weren't handling gamma correctly and used an ad-hoc definition of gray instead of based on true luminance. [ChangeLog][QtGui] RGB conversions to grayscale formats are now gamma-corrected and produce color-space luminance values Change-Id: I88ab870c8f5e502ddb053e6a14a75102239a26f2 Reviewed-by: Lars Knoll --- src/gui/image/qimage_conversions.cpp | 177 +++++++++++++++--- src/gui/painting/qcolorspace.cpp | 11 ++ src/gui/painting/qcolorspace_p.h | 1 + src/gui/painting/qcolortransform.cpp | 63 +++++++ src/gui/painting/qcolortransform.h | 1 + src/gui/painting/qcolortransform_p.h | 9 + src/gui/painting/qdrawhelper.cpp | 98 +++++++++- src/gui/painting/qpaintengine_raster.cpp | 1 + src/gui/painting/qpaintengine_raster_p.h | 1 + tests/auto/gui/image/qimage/tst_qimage.cpp | 29 +-- .../image/qimagewriter/tst_qimagewriter.cpp | 8 +- 11 files changed, 360 insertions(+), 39 deletions(-) diff --git a/src/gui/image/qimage_conversions.cpp b/src/gui/image/qimage_conversions.cpp index 84a5a5ab83..7415c86b9b 100644 --- a/src/gui/image/qimage_conversions.cpp +++ b/src/gui/image/qimage_conversions.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include +#include #include #include #include @@ -1284,10 +1285,81 @@ static void convert_gray16_to_RGBA64(QImageData *dest, const QImageData *src, Qt } } -static void convert_RGBA64_to_gray16(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +template +static void convert_ARGB_to_gray8(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(dest->format == QImage::Format_Grayscale8); + Q_ASSERT(src->format == QImage::Format_RGB32 || + src->format == QImage::Format_ARGB32 || + src->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const qsizetype sbpl = src->bytes_per_line; + const qsizetype dbpl = dest->bytes_per_line; + const uchar *src_data = src->data; + uchar *dest_data = dest->data; + + QColorSpace fromCS = src->colorSpace.isValid() ? src->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + QColorTransformPrivate::TransformFlags flags = Premultiplied + ? QColorTransformPrivate::InputPremultiplied + : QColorTransformPrivate::Unpremultiplied; + + for (int i = 0; i < src->height; ++i) { + const QRgb *src_line = reinterpret_cast(src_data); + tfd->apply(dest_data, src_line, src->width, flags); + src_data += sbpl; + dest_data += dbpl; + } +} + +template +static void convert_ARGB_to_gray16(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) { Q_ASSERT(dest->format == QImage::Format_Grayscale16); + Q_ASSERT(src->format == QImage::Format_RGB32 || + src->format == QImage::Format_ARGB32 || + src->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const qsizetype sbpl = src->bytes_per_line; + const qsizetype dbpl = dest->bytes_per_line; + const uchar *src_data = src->data; + uchar *dest_data = dest->data; + + QColorSpace fromCS = src->colorSpace.isValid() ? src->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + QColorTransformPrivate::TransformFlags flags = Premultiplied + ? QColorTransformPrivate::InputPremultiplied + : QColorTransformPrivate::Unpremultiplied; + + QRgba64 tmp_line[BufferSize]; + for (int i = 0; i < src->height; ++i) { + const QRgb *src_line = reinterpret_cast(src_data); + quint16 *dest_line = reinterpret_cast(dest_data); + int j = 0; + while (j < src->width) { + const int len = std::min(src->width - j, BufferSize); + for (int k = 0; k < len; ++k) + tmp_line[k] = QRgba64::fromArgb32(src_line[j + k]); + tfd->apply(dest_line + j, tmp_line, len, flags); + j += len; + } + src_data += sbpl; + dest_data += dbpl; + } +} + +template +static void convert_RGBA64_to_gray8(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(dest->format == QImage::Format_Grayscale8); Q_ASSERT(src->format == QImage::Format_RGBX64 || + src->format == QImage::Format_RGBA64 || src->format == QImage::Format_RGBA64_Premultiplied); Q_ASSERT(src->width == dest->width); Q_ASSERT(src->height == dest->height); @@ -1297,13 +1369,56 @@ static void convert_RGBA64_to_gray16(QImageData *dest, const QImageData *src, Qt const uchar *src_data = src->data; uchar *dest_data = dest->data; + QColorSpace fromCS = src->colorSpace.isValid() ? src->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + QColorTransformPrivate::TransformFlags flags = Premultiplied + ? QColorTransformPrivate::InputPremultiplied + : QColorTransformPrivate::Unpremultiplied; + + quint16 gray_line[BufferSize]; + for (int i = 0; i < src->height; ++i) { + const QRgba64 *src_line = reinterpret_cast(src_data); + uchar *dest_line = dest_data; + int j = 0; + while (j < src->width) { + const int len = std::min(src->width - j, BufferSize); + tfd->apply(gray_line, src_line + j, len, flags); + for (int k = 0; k < len; ++k) + dest_line[j + k] = qt_div_257(gray_line[k]); + j += len; + } + src_data += sbpl; + dest_data += dbpl; + } +} + +template +static void convert_RGBA64_to_gray16(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(dest->format == QImage::Format_Grayscale16); + Q_ASSERT(src->format == QImage::Format_RGBX64 || + src->format == QImage::Format_RGBA64 || + src->format == QImage::Format_RGBA64_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const qsizetype sbpl = src->bytes_per_line; + const qsizetype dbpl = dest->bytes_per_line; + const uchar *src_data = src->data; + uchar *dest_data = dest->data; + + QColorSpace fromCS = src->colorSpace.isValid() ? src->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + QColorTransformPrivate::TransformFlags flags = Premultiplied + ? QColorTransformPrivate::InputPremultiplied + : QColorTransformPrivate::Unpremultiplied; + for (int i = 0; i < src->height; ++i) { const QRgba64 *src_line = reinterpret_cast(src_data); quint16 *dest_line = reinterpret_cast(dest_data); - for (int j = 0; j < src->width; ++j) { - QRgba64 s = src_line[j].unpremultiplied(); - dest_line[j] = qGray(s.red(), s.green(), s.blue()); - } + tfd->apply(dest_line, src_line, src->width, flags); src_data += sbpl; dest_data += dbpl; } @@ -2072,23 +2187,29 @@ static void convert_Indexed8_to_Grayscale8(QImageData *dest, const QImageData *s uchar translate[256]; const QList &colors = src->colortable; bool simpleCase = (colors.size() == 256); - for (int i = 0; i < colors.size(); ++i) { - uchar gray = qGray(colors[i]); - translate[i] = gray; - simpleCase = simpleCase && (gray == i); + for (int i = 0; i < colors.size() && simpleCase; ++i) { + if (colors[i] != qRgb(i, i, i)) + simpleCase = false; + } + if (simpleCase) { + copy_8bit_pixels(dest, src); + return; } - if (simpleCase) - copy_8bit_pixels(dest, src); - else { - const uchar *sdata = src->data; - uchar *ddata = dest->data; - for (int y = 0; y < src->height; ++y) { - for (int x = 0; x < src->width; ++x) - ddata[x] = translate[sdata[x]]; - sdata += src->bytes_per_line; - ddata += dest->bytes_per_line; - } + QColorSpace fromCS = src->colorSpace.isValid() ? src->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + for (int i = 0; i < colors.size(); ++i) { + QRgba64 c16 = tf.map(QRgba64::fromArgb32(colors[i])); + translate[i] = c16.green8(); // Y from XYZ ends up in the G channel + } + + const uchar *sdata = src->data; + uchar *ddata = dest->data; + for (int y = 0; y < src->height; ++y) { + for (int x = 0; x < src->width; ++x) + ddata[x] = translate[sdata[x]]; + sdata += src->bytes_per_line; + ddata += dest->bytes_per_line; } } @@ -2120,7 +2241,7 @@ static bool convert_Indexed8_to_Grayscale8_inplace(QImageData *data, Qt::ImageCo if (colors.size() != 256) return false; for (int i = 0; i < colors.size(); ++i) { - if (i != qGray(colors[i])) + if (colors[i] != qRgb(i, i, i)) return false; } @@ -2206,6 +2327,8 @@ static void qInitImageConversions() qimage_converter_map[QImage::Format_RGB32][QImage::Format_Indexed8] = convert_RGB_to_Indexed8; qimage_converter_map[QImage::Format_RGB32][QImage::Format_ARGB32] = mask_alpha_converter; qimage_converter_map[QImage::Format_RGB32][QImage::Format_ARGB32_Premultiplied] = mask_alpha_converter; + qimage_converter_map[QImage::Format_RGB32][QImage::Format_Grayscale8] = convert_ARGB_to_gray8; + qimage_converter_map[QImage::Format_RGB32][QImage::Format_Grayscale16] = convert_ARGB_to_gray16; qimage_converter_map[QImage::Format_ARGB32][QImage::Format_Mono] = convert_X_to_Mono; qimage_converter_map[QImage::Format_ARGB32][QImage::Format_MonoLSB] = convert_X_to_Mono; @@ -2217,11 +2340,15 @@ static void qInitImageConversions() qimage_converter_map[QImage::Format_ARGB32][QImage::Format_A2BGR30_Premultiplied] = convert_ARGB_to_A2RGB30; qimage_converter_map[QImage::Format_ARGB32][QImage::Format_A2RGB30_Premultiplied] = convert_ARGB_to_A2RGB30; qimage_converter_map[QImage::Format_ARGB32][QImage::Format_RGBA64] = convert_ARGB32_to_RGBA64; + qimage_converter_map[QImage::Format_ARGB32][QImage::Format_Grayscale8] = convert_ARGB_to_gray8; + qimage_converter_map[QImage::Format_ARGB32][QImage::Format_Grayscale16] = convert_ARGB_to_gray16; qimage_converter_map[QImage::Format_ARGB32_Premultiplied][QImage::Format_Mono] = convert_ARGB_PM_to_Mono; qimage_converter_map[QImage::Format_ARGB32_Premultiplied][QImage::Format_MonoLSB] = convert_ARGB_PM_to_Mono; qimage_converter_map[QImage::Format_ARGB32_Premultiplied][QImage::Format_Indexed8] = convert_ARGB_PM_to_Indexed8; qimage_converter_map[QImage::Format_ARGB32_Premultiplied][QImage::Format_RGBA8888_Premultiplied] = convert_ARGB_to_RGBA; + qimage_converter_map[QImage::Format_ARGB32_Premultiplied][QImage::Format_Grayscale8] = convert_ARGB_to_gray8; + qimage_converter_map[QImage::Format_ARGB32_Premultiplied][QImage::Format_Grayscale16] = convert_ARGB_to_gray16; qimage_converter_map[QImage::Format_RGB888][QImage::Format_RGB32] = convert_RGB888_to_RGB; qimage_converter_map[QImage::Format_RGB888][QImage::Format_ARGB32] = convert_RGB888_to_RGB; @@ -2271,13 +2398,17 @@ static void qInitImageConversions() qimage_converter_map[QImage::Format_RGBX64][QImage::Format_RGBA64] = convert_passthrough; qimage_converter_map[QImage::Format_RGBX64][QImage::Format_RGBA64_Premultiplied] = convert_passthrough; - qimage_converter_map[QImage::Format_RGBX64][QImage::Format_Grayscale16] = convert_RGBA64_to_gray16; + qimage_converter_map[QImage::Format_RGBX64][QImage::Format_Grayscale8] = convert_RGBA64_to_gray8; + qimage_converter_map[QImage::Format_RGBX64][QImage::Format_Grayscale16] = convert_RGBA64_to_gray16; qimage_converter_map[QImage::Format_RGBA64][QImage::Format_ARGB32] = convert_RGBA64_to_ARGB32; qimage_converter_map[QImage::Format_RGBA64][QImage::Format_RGBA8888] = convert_RGBA64_to_ARGB32; qimage_converter_map[QImage::Format_RGBA64][QImage::Format_RGBX64] = convert_RGBA64_to_RGBx64; + qimage_converter_map[QImage::Format_RGBA64][QImage::Format_Grayscale8] = convert_RGBA64_to_gray8; + qimage_converter_map[QImage::Format_RGBA64][QImage::Format_Grayscale16] = convert_RGBA64_to_gray16; - qimage_converter_map[QImage::Format_RGBA64_Premultiplied][QImage::Format_Grayscale16] = convert_RGBA64_to_gray16; + qimage_converter_map[QImage::Format_RGBA64_Premultiplied][QImage::Format_Grayscale8] = convert_RGBA64_to_gray8; + qimage_converter_map[QImage::Format_RGBA64_Premultiplied][QImage::Format_Grayscale16] = convert_RGBA64_to_gray16; qimage_converter_map[QImage::Format_Grayscale16][QImage::Format_RGBX64] = convert_gray16_to_RGBA64; qimage_converter_map[QImage::Format_Grayscale16][QImage::Format_RGBA64] = convert_gray16_to_RGBA64; diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp index e770d929d3..9007b66601 100644 --- a/src/gui/painting/qcolorspace.cpp +++ b/src/gui/painting/qcolorspace.cpp @@ -454,6 +454,17 @@ QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpace return combined; } +QColorTransform QColorSpacePrivate::transformationToXYZ() const +{ + QColorTransform transform; + auto ptr = new QColorTransformPrivate; + transform.d = ptr; + ptr->colorSpaceIn = this; + ptr->colorSpaceOut = this; + ptr->colorMatrix = toXyz; + return transform; +} + /*! \class QColorSpace \brief The QColorSpace class provides a color space abstraction. diff --git a/src/gui/painting/qcolorspace_p.h b/src/gui/painting/qcolorspace_p.h index b5e5263cae..79b1774d0b 100644 --- a/src/gui/painting/qcolorspace_p.h +++ b/src/gui/painting/qcolorspace_p.h @@ -120,6 +120,7 @@ public: const QList &greenTransferFunctionTable, const QList &blueTransferFunctionTable); QColorTransform transformationToColorSpace(const QColorSpacePrivate *out) const; + QColorTransform transformationToXYZ() const; static constexpr QColorSpace::NamedColorSpace Unknown = QColorSpace::NamedColorSpace(0); QColorSpace::NamedColorSpace namedColorSpace = Unknown; diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp index 013abf769a..2a1b0ede20 100644 --- a/src/gui/painting/qcolortransform.cpp +++ b/src/gui/painting/qcolortransform.cpp @@ -602,6 +602,22 @@ static void storeOpaque(QRgba64 *dst, const QRgba64 *src, const QColorVector *bu } } +static void storeGray(quint8 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) +{ + Q_UNUSED(src); + for (qsizetype i = 0; i < len; ++i) + dst[i] = d_ptr->colorSpaceOut->lut[1]->u8FromLinearF32(buffer[i].y); +} + +static void storeGray(quint16 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) +{ + Q_UNUSED(src); + for (qsizetype i = 0; i < len; ++i) + dst[i] = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y); +} + static constexpr qsizetype WorkBlockSize = 256; template @@ -648,6 +664,33 @@ void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, Transf } } +template +void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const +{ + if (!colorMatrix.isValid()) + return; + + updateLutsIn(); + updateLutsOut(); + + QUninitialized buffer; + + qsizetype i = 0; + while (i < count) { + const qsizetype len = qMin(count - i, WorkBlockSize); + if (flags & InputPremultiplied) + loadPremultiplied(buffer, src + i, len, this); + else + loadUnpremultiplied(buffer, src + i, len, this); + + applyMatrix(buffer, len, colorMatrix); + + storeGray(dst + i, src + i, buffer, len, this); + + i += len; + } +} + /*! \internal \enum QColorTransformPrivate::TransformFlag @@ -708,5 +751,25 @@ void QColorTransformPrivate::apply(QRgba64 *dst, const QRgba64 *src, qsizetype c apply(dst, src, count, flags); } +/*! + \internal + Is to be called on a color-transform to XYZ, returns only luminance values. + +*/ +void QColorTransformPrivate::apply(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const +{ + applyReturnGray(dst, src, count, flags); +} + +/*! + \internal + Is to be called on a color-transform to XYZ, returns only luminance values. + +*/ +void QColorTransformPrivate::apply(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const +{ + applyReturnGray(dst, src, count, flags); +} + QT_END_NAMESPACE diff --git a/src/gui/painting/qcolortransform.h b/src/gui/painting/qcolortransform.h index 30aceebda3..7ec58c5617 100644 --- a/src/gui/painting/qcolortransform.h +++ b/src/gui/painting/qcolortransform.h @@ -75,6 +75,7 @@ public: private: friend class QColorSpace; friend class QColorSpacePrivate; + friend class QColorTransformPrivate; friend class QImage; QExplicitlySharedDataPointer d; diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h index 5d7116248d..b9099fa399 100644 --- a/src/gui/painting/qcolortransform_p.h +++ b/src/gui/painting/qcolortransform_p.h @@ -65,6 +65,9 @@ public: QExplicitlySharedDataPointer colorSpaceIn; QExplicitlySharedDataPointer colorSpaceOut; + static QColorTransformPrivate *get(const QColorTransform &q) + { return q.d.data(); } + void updateLutsIn() const; void updateLutsOut() const; bool simpleGammaCorrection() const; @@ -81,9 +84,15 @@ public: void apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; void apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; + void apply(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; + void apply(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; template void apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const; + + template + void applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const; + }; QT_END_NAMESPACE diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp index c66268c5f3..161eebb300 100644 --- a/src/gui/painting/qdrawhelper.cpp +++ b/src/gui/painting/qdrawhelper.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -334,6 +335,51 @@ static void QT_FASTCALL destStore(QRasterBuffer *rasterBuffer, int x, int y, con store(dest, buffer, x, length, nullptr, nullptr); } +static void QT_FASTCALL destStoreGray8(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length) +{ + uchar *data = rasterBuffer->scanLine(y) + x; + + bool failed = false; + for (int k = 0; k < length; ++k) { + if (!qIsGray(buffer[k])) { + failed = true; + break; + } + data[k] = qRed(buffer[k]); + } + if (failed) { // Non-gray colors + QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + + tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied); + } +} + +static void QT_FASTCALL destStoreGray16(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length) +{ + quint16 *data = reinterpret_cast(rasterBuffer->scanLine(y)) + x; + + bool failed = false; + for (int k = 0; k < length; ++k) { + if (!qIsGray(buffer[k])) { + failed = true; + break; + } + data[k] = qRed(buffer[k]) * 257; + } + if (failed) { // Non-gray colors + QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + + QRgba64 tmp_line[BufferSize]; + for (int k = 0; k < length; ++k) + tmp_line[k] = QRgba64::fromArgb32(buffer[k]); + tfd->apply(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied); + } +} + static DestStoreProc destStoreProc[QImage::NImageFormats] = { nullptr, // Format_Invalid @@ -360,11 +406,11 @@ static DestStoreProc destStoreProc[QImage::NImageFormats] = destStore, // Format_RGB30 destStore, // Format_A2RGB30_Premultiplied destStore, // Format_Alpha8 - destStore, // Format_Grayscale8 + destStoreGray8, // Format_Grayscale8 destStore, // Format_RGBX64 destStore, // Format_RGBA64 destStore, // Format_RGBA64_Premultiplied - destStore, // Format_Grayscale16 + destStoreGray16, // Format_Grayscale16 destStore, // Format_BGR888 }; @@ -384,6 +430,50 @@ static void QT_FASTCALL destStore64RGBA64(QRasterBuffer *rasterBuffer, int x, in } } +static void QT_FASTCALL destStore64Gray8(QRasterBuffer *rasterBuffer, int x, int y, const QRgba64 *buffer, int length) +{ + uchar *data = rasterBuffer->scanLine(y) + x; + + bool failed = false; + for (int k = 0; k < length; ++k) { + if (buffer[k].red() != buffer[k].green() || buffer[k].red() != buffer[k].blue()) { + failed = true; + break; + } + data[k] = buffer[k].red8(); + } + if (failed) { // Non-gray colors + QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + + quint16 gray_line[BufferSize]; + tfd->apply(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied); + for (int k = 0; k < length; ++k) + data[k] = qt_div_257(gray_line[k]); + } +} + +static void QT_FASTCALL destStore64Gray16(QRasterBuffer *rasterBuffer, int x, int y, const QRgba64 *buffer, int length) +{ + quint16 *data = reinterpret_cast(rasterBuffer->scanLine(y)) + x; + + bool failed = false; + for (int k = 0; k < length; ++k) { + if (buffer[k].red() != buffer[k].green() || buffer[k].red() != buffer[k].blue()) { + failed = true; + break; + } + data[k] = buffer[k].red(); + } + if (failed) { // Non-gray colors + QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb; + QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); + QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); + tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied); + } +} + static DestStoreProc64 destStoreProc64[QImage::NImageFormats] = { nullptr, // Format_Invalid @@ -410,11 +500,11 @@ static DestStoreProc64 destStoreProc64[QImage::NImageFormats] = destStore64, // Format_RGB30 destStore64, // Format_A2RGB30_Premultiplied destStore64, // Format_Alpha8 - destStore64, // Format_Grayscale8 + destStore64Gray8, // Format_Grayscale8 nullptr, // Format_RGBX64 destStore64RGBA64, // Format_RGBA64 nullptr, // Format_RGBA64_Premultiplied - destStore64, // Format_Grayscale16 + destStore64Gray16, // Format_Grayscale16 destStore64, // Format_BGR888 }; #endif diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp index de17bff844..bae8695e33 100644 --- a/src/gui/painting/qpaintengine_raster.cpp +++ b/src/gui/painting/qpaintengine_raster.cpp @@ -3788,6 +3788,7 @@ QImage::Format QRasterBuffer::prepare(QImage *image) bytes_per_line = image->bytesPerLine(); format = image->format(); + colorSpace = image->colorSpace(); if (image->depth() == 1 && image->colorTable().size() == 2) { monoDestinationWithClut = true; const QList colorTable = image->colorTable(); diff --git a/src/gui/painting/qpaintengine_raster_p.h b/src/gui/painting/qpaintengine_raster_p.h index 62cef10d32..d25fe4c3b3 100644 --- a/src/gui/painting/qpaintengine_raster_p.h +++ b/src/gui/painting/qpaintengine_raster_p.h @@ -450,6 +450,7 @@ public: QPainter::CompositionMode compositionMode; QImage::Format format; + QColorSpace colorSpace; QImage colorizeBitmap(const QImage &image, const QColor &color); private: diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp index 0fb4263024..c3096bd47f 100644 --- a/tests/auto/gui/image/qimage/tst_qimage.cpp +++ b/tests/auto/gui/image/qimage/tst_qimage.cpp @@ -2951,21 +2951,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); } } } @@ -3014,8 +3022,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)) diff --git a/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp b/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp index 907719259d..49a4504d2a 100644 --- a/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp +++ b/tests/auto/gui/image/qimagewriter/tst_qimagewriter.cpp @@ -291,7 +291,13 @@ void tst_QImageWriter::writeImage2() QVERIFY(reader.read(&written)); } - written = written.convertToFormat(image.format()); + // The 8-bit input value might have turned into a fraction in 16-bit grayscale + // which can't be preserved in file formats that doesn't support 16bpc. + if (image.format() == QImage::Format_Grayscale16 && + written.format() != QImage::Format_Grayscale16 && written.depth() <= 32) + image.convertTo(QImage::Format_Grayscale8); + + written.convertTo(image.format()); if (!equalImageContents(written, image)) { qDebug() << "image" << image.format() << image.width() << image.height() << image.depth()