From e673e5a257569eaa816c6acd31dd754efd9f8c75 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Thu, 25 Apr 2024 11:57:58 +0200 Subject: [PATCH] Merge applyGray, applyReturnGray, and apply methods Combine all into a single flexible apply method. Also fixes a few issues with RGB colorspace on gray input. Blocking CMYK colorspace on Grayscale and ElementListProcessing RGB on grayscale out. Fixes: QTBUG-125303 Change-Id: I3987010062fbb5aa708aeb1cc239f3ce9413e34f Reviewed-by: Allan Sandfeld Jensen --- src/gui/image/qimage.cpp | 67 ++-- src/gui/image/qimage_conversions.cpp | 8 +- src/gui/image/qimage_p.h | 26 +- src/gui/painting/qcolorspace.cpp | 2 - src/gui/painting/qcolortransform.cpp | 371 +++++++++--------- src/gui/painting/qcolortransform_p.h | 6 +- src/gui/painting/qdrawhelper.cpp | 8 +- src/gui/painting/qicc.cpp | 6 + .../auto/gui/image/qimage/images/VideoHD.icc | Bin 0 -> 786 bytes tests/auto/gui/image/qimage/tst_qimage.cpp | 118 ++++++ 10 files changed, 366 insertions(+), 246 deletions(-) create mode 100644 tests/auto/gui/image/qimage/images/VideoHD.icc diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 5a2af0dbb01..175ff8cc499 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -5027,7 +5027,7 @@ void QImage::setColorSpace(const QColorSpace &colorSpace) return; if (d->colorSpace == colorSpace) return; - if (colorSpace.isValid() && !qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) + if (colorSpace.isValid() && !qt_compatibleColorModelSource(pixelFormat().colorModel(), colorSpace.colorModel())) return; detachMetadata(false); @@ -5057,7 +5057,7 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace) } if (d->colorSpace == colorSpace) return; - if (!qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) { + if (!qt_compatibleColorModelTarget(pixelFormat().colorModel(), colorSpace.colorModel(), colorSpace.transformModel())) { *this = convertedToColorSpace(colorSpace); return; } @@ -5088,7 +5088,7 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace, QImage::Format f qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid"; return; } - if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) { + if (!qt_compatibleColorModelTarget(toPixelFormat(format).colorModel(), colorSpace.colorModel(), colorSpace.transformModel())) { qWarning() << "QImage::convertToColorSpace: Color space is not compatible with format"; return; } @@ -5148,7 +5148,7 @@ QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace, QImage::Form qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid"; return QImage(); } - if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) { + if (!qt_compatibleColorModelTarget(toPixelFormat(format).colorModel(), colorSpace.colorModel(), colorSpace.transformModel())) { qWarning() << "QImage::convertedToColorSpace: Color space is not compatible with format"; return QImage(); } @@ -5181,8 +5181,9 @@ 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)) { + if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel) || + !qt_compatibleColorModelTarget(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel, + QColorTransformPrivate::get(transform)->colorSpaceOut->transformModel)) { qWarning() << "QImage::applyColorTransform can not apply format switching transform without switching format"; return; } @@ -5242,14 +5243,14 @@ void QImage::applyColorTransform(const QColorTransform &transform) transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { uint8_t *scanline = reinterpret_cast(d->data + y * d->bytes_per_line); - QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags); + QColorTransformPrivate::get(transform)->apply(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(d->data + y * d->bytes_per_line); - QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags); + QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); } }; } else if (qt_fpColorPrecision(format())) { @@ -5343,15 +5344,15 @@ QImage QImage::colorTransformed(const QColorTransform &transform) const & 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)) { + const QColorSpacePrivate *inColorSpace = QColorTransformPrivate::get(transform)->colorSpaceIn.constData(); + const QColorSpacePrivate *outColorSpace = QColorTransformPrivate::get(transform)->colorSpaceOut.constData(); + if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), inColorSpace->colorModel)) { qWarning() << "QImage::colorTransformed: Invalid input color space for transform"; return QImage(); } - if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) { + if (!qt_compatibleColorModelTarget(pixelFormat().colorModel(), outColorSpace->colorModel, outColorSpace->transformModel)) { // All model switching transforms are opaque in at least one end. - switch (outColorModel) { + switch (outColorSpace->colorModel) { case QColorSpace::ColorModel::Rgb: return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32); case QColorSpace::ColorModel::Gray: @@ -5430,13 +5431,13 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::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)) { + const QColorSpacePrivate *inColorSpace = QColorTransformPrivate::get(transform)->colorSpaceIn.constData(); + const QColorSpacePrivate *outColorSpace = QColorTransformPrivate::get(transform)->colorSpaceOut.constData(); + if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), inColorSpace->colorModel)) { qWarning() << "QImage::colorTransformed: Invalid input color space for transform"; return QImage(); } - if (!qt_compatibleColorModel(toPixelFormat(toFormat).colorModel(), outColorModel)) { + if (!qt_compatibleColorModelTarget(toPixelFormat(toFormat).colorModel(), outColorSpace->colorModel, outColorSpace->transformModel)) { qWarning() << "QImage::colorTransformed: Invalid output color space for transform"; return QImage(); } @@ -5533,7 +5534,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const quint8 *in_scanline = reinterpret_cast(d->data + y * d->bytes_per_line); QRgb *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } else { @@ -5541,7 +5542,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const quint16 *in_scanline = reinterpret_cast(d->data + y * d->bytes_per_line); QRgba64 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } @@ -5552,7 +5553,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const quint8 *in_scanline = reinterpret_cast(d->data + y * d->bytes_per_line); QCmyk32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } else { @@ -5560,7 +5561,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const quint16 *in_scanline = reinterpret_cast(d->data + y * d->bytes_per_line); QCmyk32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } @@ -5572,7 +5573,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const QRgb *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); quint8 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } else { @@ -5581,7 +5582,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const QRgba64 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); quint16 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } @@ -5592,7 +5593,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const QCmyk32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); quint8 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } else { @@ -5600,7 +5601,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const QCmyk32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); quint16 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } @@ -5704,11 +5705,11 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format const quint8 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); if (tmpFormat == Format_Grayscale8) { quint8 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } else { Q_ASSERT(tmpFormat == Format_Grayscale16); quint16 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } } }; @@ -5717,7 +5718,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format for (int y = yStart; y < yEnd; ++y) { const quint16 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); quint16 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); - QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } }; } else if (fromImage.format() == Format_CMYK8888) { @@ -5811,15 +5812,15 @@ 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)) { + const QColorSpacePrivate *inColorSpace = QColorTransformPrivate::get(transform)->colorSpaceIn.constData(); + const QColorSpacePrivate *outColorSpace = QColorTransformPrivate::get(transform)->colorSpaceOut.constData(); + if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), inColorSpace->colorModel)) { qWarning() << "QImage::colorTransformed: Invalid input color space for transform"; return QImage(); } - if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) { + if (!qt_compatibleColorModelTarget(pixelFormat().colorModel(), outColorSpace->colorModel, outColorSpace->transformModel)) { // There is currently no inplace conversion of both colorspace and format, so just use the normal version. - switch (outColorModel) { + switch (outColorSpace->colorModel) { case QColorSpace::ColorModel::Rgb: return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32); case QColorSpace::ColorModel::Gray: diff --git a/src/gui/image/qimage_conversions.cpp b/src/gui/image/qimage_conversions.cpp index a806954df2c..8d9e0b87e29 100644 --- a/src/gui/image/qimage_conversions.cpp +++ b/src/gui/image/qimage_conversions.cpp @@ -1424,7 +1424,7 @@ static void convert_ARGB_to_gray8(QImageData *dest, const QImageData *src, Qt::I for (int i = 0; i < src->height; ++i) { const QRgb *src_line = reinterpret_cast(src_data); - tfd->applyReturnGray(dest_data, src_line, src->width, flags); + tfd->apply(dest_data, src_line, src->width, flags); src_data += sbpl; dest_data += dbpl; } @@ -1461,7 +1461,7 @@ static void convert_ARGB_to_gray16(QImageData *dest, const QImageData *src, Qt:: 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->applyReturnGray(dest_line + j, tmp_line, len, flags); + tfd->apply(dest_line + j, tmp_line, len, flags); j += len; } src_data += sbpl; @@ -1498,7 +1498,7 @@ static void convert_RGBA64_to_gray8(QImageData *dest, const QImageData *src, Qt: int j = 0; while (j < src->width) { const int len = std::min(src->width - j, BufferSize); - tfd->applyReturnGray(gray_line, src_line + j, len, flags); + 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; @@ -1533,7 +1533,7 @@ static void convert_RGBA64_to_gray16(QImageData *dest, const QImageData *src, Qt for (int i = 0; i < src->height; ++i) { const QRgba64 *src_line = reinterpret_cast(src_data); quint16 *dest_line = reinterpret_cast(dest_data); - tfd->applyReturnGray(dest_line, src_line, src->width, flags); + tfd->apply(dest_line, src_line, src->width, flags); src_data += sbpl; dest_data += dbpl; } diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h index 0d42f942533..43b03ada7c2 100644 --- a/src/gui/image/qimage_p.h +++ b/src/gui/image/qimage_p.h @@ -456,7 +456,7 @@ inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format) return QColorSpace::ColorModel::Undefined; } -inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs) +inline bool qt_compatibleColorModelBase(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs) { QColorSpace::ColorModel dataCs = qt_csColorData(data); @@ -466,11 +466,27 @@ inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace:: if (cs == QColorSpace::ColorModel::Undefined || dataCs == QColorSpace::ColorModel::Undefined) return false; - if (dataCs == cs) - return true; // Matching color models + return (dataCs == cs); // Matching color models +} - if (dataCs == QColorSpace::ColorModel::Gray) - return true; // Can apply any CS with white point to Gray data +inline bool qt_compatibleColorModelSource(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs) +{ + if (qt_compatibleColorModelBase(data, cs)) + return true; + + if (data == QPixelFormat::ColorModel::Grayscale && cs == QColorSpace::ColorModel::Rgb) + return true; // Can apply Rgb CS to Gray input data + + return false; +} + +inline bool qt_compatibleColorModelTarget(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs, QColorSpace::TransformModel tm) +{ + if (qt_compatibleColorModelBase(data, cs)) + return true; + + if (data == QPixelFormat::ColorModel::Grayscale && tm == QColorSpace::TransformModel::ThreeComponentMatrix) + return true; // Can apply three-component matrix CS to gray output return false; } diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp index fce2ae4c85f..3d763d577c9 100644 --- a/src/gui/painting/qcolorspace.cpp +++ b/src/gui/painting/qcolorspace.cpp @@ -503,8 +503,6 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const // Convert to XYZ relative to our white point, not the regular D50 white point. if (!chad.isNull()) ptr->colorMatrix = chad.inverted() * ptr->colorMatrix; - else if (!whitePoint.isNull()) - ptr->colorMatrix = QColorMatrix::chromaticAdaptation(whitePoint).inverted() * ptr->colorMatrix; return transform; } diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp index cffcc4485de..f54dac9f268 100644 --- a/src/gui/painting/qcolortransform.cpp +++ b/src/gui/painting/qcolortransform.cpp @@ -1221,17 +1221,41 @@ static void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsi static void loadGray(QColorVector *buffer, const quint8 *src, const qsizetype len, const QColorTransformPrivate *d_ptr) { - for (qsizetype i = 0; i < len; ++i) { - const float y = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]); - buffer[i] = d_ptr->colorSpaceIn->whitePoint * y; + if (d_ptr->colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray || + (d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[1] && + d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[2])) { + for (qsizetype i = 0; i < len; ++i) { + const float y = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]); + buffer[i] = d_ptr->colorSpaceIn->whitePoint * y; + } + } else { + for (qsizetype i = 0; i < len; ++i) { + QColorVector v; + v.x = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]); + v.y = d_ptr->colorSpaceIn->lut[1]->u8ToLinearF32(src[i]); + v.z = d_ptr->colorSpaceIn->lut[2]->u8ToLinearF32(src[i]); + buffer[i] = d_ptr->colorSpaceIn->toXyz.map(v); + } } } static void loadGray(QColorVector *buffer, const quint16 *src, const qsizetype len, const QColorTransformPrivate *d_ptr) { - for (qsizetype i = 0; i < len; ++i) { - const float y = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]); - buffer[i] = d_ptr->colorSpaceIn->whitePoint * y; + if (d_ptr->colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray || + (d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[1] && + d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[2])) { + for (qsizetype i = 0; i < len; ++i) { + const float y = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]); + buffer[i] = d_ptr->colorSpaceIn->whitePoint * y; + } + } else { + for (qsizetype i = 0; i < len; ++i) { + QColorVector v; + v.x = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]); + v.y = d_ptr->colorSpaceIn->lut[1]->u16ToLinearF32(src[i]); + v.z = d_ptr->colorSpaceIn->lut[2]->u16ToLinearF32(src[i]); + buffer[i] = d_ptr->colorSpaceIn->toXyz.map(v); + } } } @@ -1260,6 +1284,28 @@ private: alignas(T) char data[sizeof(T) * Count]; }; +void loadUnpremultipliedLUT(QColorVector *buffer, const uchar *src, const qsizetype len) +{ + const float f = 1.0f / 255.f; + for (qsizetype i = 0; i < len; ++i) { + const float p = src[i] * f; + buffer[i].x = p; + buffer[i].y = p; + buffer[i].z = p; + } +} + +void loadUnpremultipliedLUT(QColorVector *buffer, const quint16 *src, const qsizetype len) +{ + const float f = 1.0f / 65535.f; + for (qsizetype i = 0; i < len; ++i) { + const float p = src[i] * f; + buffer[i].x = p; + buffer[i].y = p; + buffer[i].z = p; + } +} + void loadUnpremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len) { const float f = 1.0f / 255.f; @@ -1302,6 +1348,16 @@ void loadUnpremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const } } +void loadPremultipliedLUT(QColorVector *, const uchar *, const qsizetype) +{ + Q_UNREACHABLE(); +} + +void loadPremultipliedLUT(QColorVector *, const quint16 *, const qsizetype) +{ + Q_UNREACHABLE(); +} + void loadPremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len) { for (qsizetype i = 0; i < len; ++i) { @@ -1422,7 +1478,15 @@ static void storeUnpremultipliedLUT(QRgbaFloat32 *dst, const T *src, } template -static void storePremultipliedLUT(QRgb *, const T *, const QColorVector *, const qsizetype); +static void storePremultipliedLUT(QRgb *dst, const T *, const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const int r = buffer[i].x * 255.f; + const int g = buffer[i].y * 255.f; + const int b = buffer[i].z * 255.f; + dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0); + } +} template<> void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len) @@ -1436,18 +1500,6 @@ void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffe } } -template<> -void storePremultipliedLUT(QRgb *dst, const QCmyk32 *, const QColorVector *buffer, const qsizetype len) -{ - for (qsizetype i = 0; i < len; ++i) { - const int r = buffer[i].x * 255.f; - const int g = buffer[i].y * 255.f; - const int b = buffer[i].z * 255.f; - dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0); - } -} - - template static void storePremultipliedLUT(QCmyk32 *dst, const T *src, const QColorVector *buffer, const qsizetype len) { @@ -1455,7 +1507,15 @@ static void storePremultipliedLUT(QCmyk32 *dst, const T *src, const QColorVector } template -static void storePremultipliedLUT(QRgba64 *, const T *, const QColorVector *, const qsizetype); +static void storePremultipliedLUT(QRgba64 *dst, const T *, const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const int r = buffer[i].x * 65535.f; + const int g = buffer[i].y * 65535.f; + const int b = buffer[i].z * 65535.f; + dst[i] = qRgba64(r, g, b, 65535); + } +} template<> void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len) @@ -1469,17 +1529,6 @@ void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *bu } } -template<> -void storePremultipliedLUT(QRgba64 *dst, const QCmyk32 *, const QColorVector *buffer, const qsizetype len) -{ - for (qsizetype i = 0; i < len; ++i) { - const int r = buffer[i].x * 65535.f; - const int g = buffer[i].y * 65535.f; - const int b = buffer[i].z * 65535.f; - dst[i] = qRgba64(r, g, b, 65535); - } -} - template<> void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len) { @@ -1630,11 +1679,43 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const return c; } +template +constexpr bool IsGrayscale = std::is_same_v || std::is_same_v; +template +constexpr bool IsAlwaysOpaque = std::is_same_v || IsGrayscale; +template +constexpr bool CanUseThreeComponent = !std::is_same_v; +template +constexpr bool UnclampedValues = std::is_same_v || std::is_same_v; + +// Possible combos for data and color spaces: +// DataCM ColorSpaceCM ColorSpacePM Notes +// Gray Gray ThreeMatrix +// Gray Rgb ThreeMatrix Invalid colorMatrix +// Rgb Rgb ThreeMatrix +// Rgb Rgb ElementProc +// Gray Rgb ElementProc Only possible for input data +// Cmyk Cmyk ElementProc +// +// Gray data can be uchar, quint16, and is always Opaque +// Rgb data can be QRgb, QRgba64, or QRgbaFloat32, and is Unpremultiplied, Premultiplied, or Opaque +// Cmyk data can be Cmyk32, and is always Opaque +// +// colorMatrix as setup for Gray on Gray or Rgb on Rgb, but not Gray data on Rgb colorspace. + template void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const { - // Avoid compiling this part for S=QCmyk32: - if constexpr (!std::is_same_v) { + if constexpr (IsGrayscale) { + if (colorSpaceIn->isThreeComponentMatrix()) { + loadGray(buffer, src, len, this); + if (!colorSpaceOut->isThreeComponentMatrix() || colorSpaceIn->colorModel != QColorSpace::ColorModel::Gray) { + if (!colorSpaceIn->chad.isNull()) + applyMatrix(buffer, len, colorSpaceIn->chad); + } + return; + } + } else if constexpr (CanUseThreeComponent) { if (colorSpaceIn->isThreeComponentMatrix()) { if (flags & InputPremultiplied) loadPremultiplied(buffer, src, len, this); @@ -1642,7 +1723,7 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, loadUnpremultiplied(buffer, src, len, this); if (!colorSpaceOut->isThreeComponentMatrix()) - applyMatrix(buffer, len, colorSpaceIn->toXyz); + applyMatrix(buffer, len, colorMatrix); return; } } @@ -1653,9 +1734,6 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, else loadUnpremultipliedLUT(buffer, src, len); - if constexpr (std::is_same_v || std::is_same_v) - clampIfNeeded(buffer, len); - // Do element based conversion for (auto &&element : colorSpaceIn->mAB) std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); @@ -1664,13 +1742,44 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, template void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const { - constexpr ApplyMatrixForm doClamp = (std::is_same_v || std::is_same_v) ? DoNotClamp : DoClamp; - // Avoid compiling this part for D=QCmyk32: - if constexpr (!std::is_same_v) { - if (colorSpaceOut->isThreeComponentMatrix()) { - applyMatrix(buffer, len, colorMatrix); + constexpr ApplyMatrixForm doClamp = UnclampedValues ? DoNotClamp : DoClamp; + if constexpr (IsGrayscale) { + Q_UNUSED(src); // dealing with buggy warnings in gcc 9 + Q_UNUSED(flags); + // Calculate the matrix for grayscale conversion + QColorMatrix grayMatrix; + if (colorSpaceIn == colorSpaceOut || + (colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray && + colorSpaceOut->colorModel == QColorSpace::ColorModel::Gray)) { + // colorMatrix already has the right form + grayMatrix = colorMatrix; + } else { + if constexpr (IsGrayscale) { + if (colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray) + grayMatrix = colorSpaceIn->chad; + else + grayMatrix = QColorMatrix::identity(); // Otherwise already handled in applyConvertIn + } else { + if (colorSpaceIn->isThreeComponentMatrix()) + grayMatrix = colorSpaceIn->toXyz; + else + grayMatrix = QColorMatrix::identity(); + } + if (!colorSpaceOut->chad.isNull()) + grayMatrix = colorSpaceOut->chad.inverted() * grayMatrix; + } - if constexpr (std::is_same_v) { + applyMatrix(buffer, len, grayMatrix); + storeOpaque(dst, buffer, len, this); + return; + } else if constexpr (CanUseThreeComponent) { + if (colorSpaceOut->isThreeComponentMatrix()) { + if (IsGrayscale && colorSpaceIn->colorModel != QColorSpace::ColorModel::Gray) + applyMatrix(buffer, len, colorSpaceOut->toXyz.inverted()); // colorMatrix wasnt prepared for gray input + else + applyMatrix(buffer, len, colorMatrix); + + if constexpr (IsAlwaysOpaque) { storeOpaque(dst, buffer, len, this); } else { if (flags & InputOpaque) @@ -1683,23 +1792,27 @@ void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector return; } } - Q_ASSERT(!colorSpaceOut->isThreeComponentMatrix()); + if constexpr (!IsGrayscale) { + Q_ASSERT(!colorSpaceOut->isThreeComponentMatrix()); - // Do element based conversion - for (auto &&element : colorSpaceOut->mBA) - std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); + // Do element based conversion + for (auto &&element : colorSpaceOut->mBA) + std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); - clampIfNeeded(buffer, len); + clampIfNeeded(buffer, len); - if (flags & OutputPremultiplied) - storePremultipliedLUT(dst, src, buffer, len); - else - storeUnpremultipliedLUT(dst, src, buffer, len); + if (flags & OutputPremultiplied) + storePremultipliedLUT(dst, src, buffer, len); + else + storeUnpremultipliedLUT(dst, src, buffer, len); + } else { + Q_UNREACHABLE(); + } } /*! \internal - Adapt Profile Connecting Color spaces. + Adapt Profile Connection Spaces. */ void QColorTransformPrivate::pcsAdapt(QColorVector *buffer, qsizetype count) const { @@ -1745,133 +1858,6 @@ void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, Transf } } -/*! - \internal - Is to be called on a color-transform to XYZ, returns only luminance values. - - */ -template -void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const -{ - Q_ASSERT(colorSpaceOut->isThreeComponentMatrix()); - updateLutsOut(); - if (!colorSpaceIn->isThreeComponentMatrix()) { - QUninitialized buffer; - - qsizetype i = 0; - while (i < count) { - const qsizetype len = qMin(count - i, WorkBlockSize); - - applyConvertIn(src, buffer, len, flags); - - // Match Profile Connection Spaces (PCS): - if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) { - for (qsizetype j = 0; j < len; ++j) - buffer[j] = buffer[j].xyzToLab(); - } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) { - for (qsizetype j = 0; j < len; ++j) - buffer[j] = buffer[j].labToXyz(); - } - - applyMatrix(buffer, len, colorMatrix); - storeOpaque(dst + i, buffer, len, this); - - i += len; - } - return; - } - if constexpr (!std::is_same_v) { - if (!colorMatrix.isValid()) - return; - - updateLutsIn(); - - 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); - - storeOpaque(dst + i, buffer, len, this); - - i += len; - } - } else { - Q_UNREACHABLE(); - } -} - -/*! - \internal -*/ -template -void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const -{ - Q_ASSERT(colorSpaceIn->isThreeComponentMatrix()); - updateLutsIn(); - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { - if (!colorSpaceOut->isThreeComponentMatrix()) { - QUninitialized buffer; - - qsizetype i = 0; - while (i < count) { - const qsizetype len = qMin(count - i, WorkBlockSize); - loadGray(buffer, src + i, len, this); - - applyMatrix(buffer, len, colorMatrix); - - // Match Profile Connection Spaces (PCS): - if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) { - for (qsizetype j = 0; j < len; ++j) - buffer[j] = buffer[j].xyzToLab(); - } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) { - for (qsizetype j = 0; j < len; ++j) - buffer[j] = buffer[j].labToXyz(); - } - - // Do element based conversion - for (auto &&element : colorSpaceOut->mBA) - std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); - - clampIfNeeded(buffer, len); - - storeUnpremultipliedLUT(dst, src, buffer, len); // input is always opaque - - i += len; - } - return; - } - } - Q_ASSERT(colorSpaceOut->isThreeComponentMatrix()); - if constexpr (!std::is_same_v) { - if (!colorMatrix.isValid()) - return; - - updateLutsOut(); - - QUninitialized buffer; - - qsizetype i = 0; - while (i < count) { - const qsizetype len = qMin(count - i, WorkBlockSize); - loadGray(buffer, src + i, len, this); - - applyMatrix(buffer, len, colorMatrix); - - storeOpaque(dst + i, buffer, len, this); - i += len; - } - } else { - Q_UNREACHABLE(); - } -} - /*! \internal \enum QColorTransformPrivate::TransformFlag @@ -1900,25 +1886,24 @@ void QColorTransformPrivate::prepare() updateLutsOut(); } -// Only allow versions increasing precision -template void QColorTransformPrivate::applyReturnGray(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyReturnGray(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyReturnGray(quint16 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyReturnGray(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyGray(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyGray(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyGray(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyGray(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyGray(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyGray(QCmyk32 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; -template void QColorTransformPrivate::applyGray(QRgba64 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; - +// Only some versions increasing precision 14/36 combos +template void QColorTransformPrivate::apply(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(quint16 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QRgb *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(QCmyk32 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QCmyk32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QCmyk32 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QCmyk32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QCmyk32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(QRgba64 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QRgba64 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QRgba64 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h index c74fe100eb1..3f116034200 100644 --- a/src/gui/painting/qcolortransform_p.h +++ b/src/gui/painting/qcolortransform_p.h @@ -27,7 +27,7 @@ class QCmyk32; class QColorTransformPrivate : public QSharedData { public: - QColorMatrix colorMatrix; + QColorMatrix colorMatrix; // Combined colorSpaceIn->toXyz and colorSpaceOut->toXyz.inverted() QExplicitlySharedDataPointer colorSpaceIn; QExplicitlySharedDataPointer colorSpaceOut; @@ -53,10 +53,6 @@ public: template void apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const; - template - void applyGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const; - template - void applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const; private: void pcsAdapt(QColorVector *buffer, qsizetype len) const; diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp index b7a943be38e..aaf57e19d1f 100644 --- a/src/gui/painting/qdrawhelper.cpp +++ b/src/gui/painting/qdrawhelper.cpp @@ -644,7 +644,7 @@ static void QT_FASTCALL destStoreGray8(QRasterBuffer *rasterBuffer, int x, int y QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); - tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied); + tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied); } } @@ -668,7 +668,7 @@ static void QT_FASTCALL destStoreGray16(QRasterBuffer *rasterBuffer, int x, int QRgba64 tmp_line[BufferSize]; for (int k = 0; k < length; ++k) tmp_line[k] = QRgba64::fromArgb32(buffer[k]); - tfd->applyReturnGray(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied); + tfd->apply(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied); } } @@ -749,7 +749,7 @@ static void QT_FASTCALL destStore64Gray8(QRasterBuffer *rasterBuffer, int x, int QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); quint16 gray_line[BufferSize]; - tfd->applyReturnGray(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied); + tfd->apply(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied); for (int k = 0; k < length; ++k) data[k] = qt_div_257(gray_line[k]); } @@ -771,7 +771,7 @@ static void QT_FASTCALL destStore64Gray16(QRasterBuffer *rasterBuffer, int x, in QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb; QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ(); QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf); - tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied); + tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied); } } diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp index 574911788da..0323dd11959 100644 --- a/src/gui/painting/qicc.cpp +++ b/src/gui/painting/qicc.cpp @@ -1993,6 +1993,12 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace) if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint)) return false; } + if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) { + if (!parseChad(data, it.value(), colorspaceDPtr)) + return false; + } else if (!colorspaceDPtr->whitePoint.isNull()) { + colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint); + } } if (auto it = tagIndex.constFind(Tag::desc); it != tagIndex.constEnd()) { diff --git a/tests/auto/gui/image/qimage/images/VideoHD.icc b/tests/auto/gui/image/qimage/images/VideoHD.icc new file mode 100644 index 0000000000000000000000000000000000000000..b96eb68136e3d63fa77e0bd2eeb39d7ed23cc674 GIT binary patch literal 786 zcmZQzU>0Hk0wxCrhTOc8q9AuCg^0)~1qLLLm|R?d#6@Q^GBA9*#sDN47%uAumauW4 zD$)!bQFb*y@1B3ZR1_ncK z1_q%31_qgG28QS(3=HgtfNnYgbZRb;p9nJpL>D9$LHHnhnJk_#FueG{z#uNbzyM;4 z?4eXX*U3=("fromFormat"); + QTest::addColumn("fromCS"); + QTest::addColumn("toCS"); + + QImage::Format formats[] = { + QImage::Format_Grayscale8, + QImage::Format_Grayscale16, + }; + + QList colorSpaces = { + QColorSpace::SRgbLinear, + QColorSpace::DisplayP3, + QColorSpace(QPointF(0.31271, 0.32902), QColorSpace::TransferFunction::SRgb), + QColorSpace(QPointF(0.30, 0.33), QColorSpace::TransferFunction::Linear) + }; + std::string names[] = { + "sRgbLinear", + "displayP3", + "graySRgb", + "grayOther", + "videoHD(A2B)" + }; + + QFile iccProfile(m_prefix + "VideoHD.icc"); + iccProfile.open(QIODevice::ReadOnly); + colorSpaces.append(QColorSpace::fromIccProfile(iccProfile.readAll())); + + for (auto fromFormat : formats) { + for (int from = 0; from < 5; ++from) { + for (int to = 0; to < 4; ++to) { + QTest::addRow("%s: %s -> %s", formatToString(fromFormat).data(), names[from].c_str(), names[to].c_str()) + << fromFormat << colorSpaces[from] << colorSpaces[to]; + } + } + } +} + +void tst_QImage::colorSpaceFromGrayConversion() +{ + QFETCH(QImage::Format, fromFormat); + QFETCH(QColorSpace, fromCS); + QFETCH(QColorSpace, toCS); + + QImage image(16, 16, fromFormat); + image.setColorSpace(fromCS); + QVERIFY(image.colorSpace().isValid()); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + } + } + QImage imageConverted = image.convertedToColorSpace(toCS); + QCOMPARE(imageConverted.format(), fromFormat); + QCOMPARE(imageConverted.size(), image.size()); + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 3)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } +} + +void tst_QImage::colorSpaceToGrayConversion_data() +{ + QTest::addColumn("fromFormat"); + + 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) + QTest::addRow("%s -> Gray", formatToString(fromFormat).data()) << fromFormat; +} + +void tst_QImage::colorSpaceToGrayConversion() +{ + QFETCH(QImage::Format, fromFormat); + + QImage image(16, 16, fromFormat); + image.setColorSpace(QColorSpace::DisplayP3); + QVERIFY(image.colorSpace().isValid()); + + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8)); + } + } + + QColorSpace grayColorSpace(QPointF(0.31271, 0.32902), QColorSpace::TransferFunction::SRgb); + + QImage imageConverted = image.convertedToColorSpace(grayColorSpace); + QVERIFY(imageConverted.format() == QImage::Format_Grayscale8 || imageConverted.format() == QImage::Format_Grayscale16); + QCOMPARE(imageConverted.size(), image.size()); + int gray = 0; + for (int x = 0; x < image.width(); ++x) { + int newGray = qGray(imageConverted.pixel(x, 11)); + QCOMPARE_GE(newGray, gray); + gray = newGray; + } +} + void tst_QImage::deepCopyWhenPaintingActive() { QImage image(64, 64, QImage::Format_ARGB32_Premultiplied);