diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 9d9aa316494..a23bdfcadd8 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,7 @@ #include QT_BEGIN_NAMESPACE +class QCmyk32; using namespace Qt::StringLiterals; @@ -2635,6 +2637,9 @@ void QImage::setPixel(int x, int y, uint index_or_rgb) case Format_A2RGB30_Premultiplied: ((uint *)s)[x] = qConvertArgb32ToA2rgb30(index_or_rgb); return; + case Format_RGBX64: + ((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb | 0xff000000); + return; case Format_RGBA64: case Format_RGBA64_Premultiplied: ((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb); @@ -5035,6 +5040,8 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace) return; } applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace)); + if (d->ref.loadRelaxed() != 1) + detachMetadata(false); d->colorSpace = colorSpace; } @@ -5151,6 +5158,13 @@ void QImage::applyColorTransform(const QColorTransform &transform) { if (transform.isIdentity()) return; + + if (!qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel) || + !qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel)) { + qWarning() << "QImage::applyColorTransform can not apply format switching transform without switching format"; + return; + } + detach(); if (!d) return; @@ -5169,7 +5183,7 @@ void QImage::applyColorTransform(const QColorTransform &transform) && oldFormat != QImage::Format_RGBA64_Premultiplied) convertTo(QImage::Format_RGBA64); } else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32 - && oldFormat != QImage::Format_ARGB32_Premultiplied + && oldFormat != QImage::Format_ARGB32_Premultiplied && oldFormat != QImage::Format_CMYK8888 && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) { if (hasAlphaChannel()) convertTo(QImage::Format_ARGB32); @@ -5187,6 +5201,7 @@ void QImage::applyColorTransform(const QColorTransform &transform) case Format_Grayscale8: case Format_Grayscale16: case Format_RGB32: + case Format_CMYK8888: case Format_RGBX64: case Format_RGBX32FPx4: flags = QColorTransformPrivate::InputOpaque; @@ -5229,6 +5244,13 @@ void QImage::applyColorTransform(const QColorTransform &transform) QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); } }; + } else if (oldFormat == QImage::Format_CMYK8888) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + QCmyk32 *scanline = reinterpret_cast(d->data + y * d->bytes_per_line); + QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); + } + }; } else { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { @@ -5312,6 +5334,8 @@ QImage QImage::colorTransformed(const QColorTransform &transform) const & return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32); case QColorSpace::ColorModel::Gray: return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8); + case QColorSpace::ColorModel::Cmyk: + return colorTransformed(transform, QImage::Format_CMYK8888); case QColorSpace::ColorModel::Undefined: break; } @@ -5410,6 +5434,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format case QImage::Format_RGBA64_Premultiplied: case QImage::Format_Grayscale8: case QImage::Format_Grayscale16: + case QImage::Format_CMYK8888: // can be output natively break; case QImage::Format_RGB16: @@ -5419,7 +5444,6 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format case QImage::Format_RGB888: case QImage::Format_BGR888: case QImage::Format_RGBX8888: - case QImage::Format_CMYK8888: tmpFormat = QImage::Format_RGB32; break; case QImage::Format_Mono: @@ -5486,7 +5510,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format transformSegment = [&](int yStart, int yEnd) { 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.bits() + y * toImage.bytesPerLine()); + QRgb *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; @@ -5494,19 +5518,38 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format transformSegment = [&](int yStart, int yEnd) { 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.bits() + y * toImage.bytesPerLine()); + QRgba64 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); } }; } - } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Gray) { + } else if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Cmyk) { + // Gray -> CMYK + if (format() == QImage::Format_Grayscale8) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const quint8 *in_scanline = reinterpret_cast(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); + } + }; + } else { + transformSegment = [&](int yStart, int yEnd) { + 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); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Gray) { // RGB -> Gray if (tmpFormat == QImage::Format_Grayscale8) { fromImage.convertTo(QImage::Format_RGB32); transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - const QRgb *in_scanline = reinterpret_cast(fromImage.bits() + y * fromImage.bytesPerLine()); - quint8 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + 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); } }; @@ -5514,12 +5557,91 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format fromImage.convertTo(QImage::Format_RGBX64); transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - const QRgba64 *in_scanline = reinterpret_cast(fromImage.bits() + y * fromImage.bytesPerLine()); - quint16 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + 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); } }; } + } else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Gray) { + // CMYK -> Gray + if (tmpFormat == QImage::Format_Grayscale8) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast(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); + } + }; + } else { + transformSegment = [&](int yStart, int yEnd) { + 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); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Rgb) { + // CMYK -> RGB + if (isRgb32Data(tmpFormat) ) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgb *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else if (isRgb64Data(tmpFormat)) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgba64 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } else { + Q_ASSERT(isRgb32fpx4Data(tmpFormat)); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QCmyk32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgbaFloat32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); + } + }; + } + } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Cmyk) { + // RGB -> CMYK + if (!fromImage.hasAlphaChannel()) + transFlags = QColorTransformPrivate::InputOpaque; + else if (qPixelLayouts[fromImage.format()].premultiplied) + transFlags = QColorTransformPrivate::Premultiplied; + if (isRgb32Data(fromImage.format()) ) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgb *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } else if (isRgb64Data(fromImage.format())) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgba64 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } else { + Q_ASSERT(isRgb32fpx4Data(fromImage.format())); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgbaFloat32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } } else { Q_UNREACHABLE(); } @@ -5541,7 +5663,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format && oldFormat != QImage::Format_RGBA64_Premultiplied && oldFormat != QImage::Format_Grayscale16) fromImage.convertTo(QImage::Format_RGBA64); } else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32 - && oldFormat != QImage::Format_ARGB32_Premultiplied + && oldFormat != QImage::Format_ARGB32_Premultiplied && oldFormat != QImage::Format_CMYK8888 && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) { if (hasAlphaChannel()) fromImage.convertTo(QImage::Format_ARGB32); @@ -5557,13 +5679,13 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format if (fromImage.format() == Format_Grayscale8) { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - const quint8 *in_scanline = reinterpret_cast(fromImage.bits() + y * fromImage.bytesPerLine()); + const quint8 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); if (tmpFormat == Format_Grayscale8) { - quint8 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + quint8 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); } else { Q_ASSERT(tmpFormat == Format_Grayscale16); - quint16 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + quint16 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags); } } @@ -5571,30 +5693,39 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format } else if (fromImage.format() == Format_Grayscale16) { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - const quint16 *in_scanline = reinterpret_cast(fromImage.bits() + y * fromImage.bytesPerLine()); - quint16 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + 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); } }; - } else if (isRgb32fpx4Data(fromImage.format())) { + } else if (fromImage.format() == Format_CMYK8888) { + Q_ASSERT(tmpFormat == Format_CMYK8888); transformSegment = [&](int yStart, int yEnd) { - Q_ASSERT(isRgb32fpx4Data(tmpFormat)); for (int y = yStart; y < yEnd; ++y) { - const QRgbaFloat32 *in_scanline = reinterpret_cast(fromImage.bits() + y * fromImage.bytesPerLine()); - QRgbaFloat32 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + const QCmyk32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QCmyk32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); + QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); + } + }; + } else if (isRgb32fpx4Data(fromImage.format())) { + Q_ASSERT(isRgb32fpx4Data(tmpFormat)); + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + const QRgbaFloat32 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); + QRgbaFloat32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } }; } else if (isRgb64Data(fromImage.format())) { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - const QRgba64 *in_scanline = reinterpret_cast(fromImage.bits() + y * fromImage.bytesPerLine()); + const QRgba64 *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); if (isRgb32fpx4Data(tmpFormat)) { - QRgbaFloat32 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + QRgbaFloat32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } else { Q_ASSERT(isRgb64Data(tmpFormat)); - QRgba64 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + QRgba64 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } } @@ -5602,16 +5733,16 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format } else { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { - const QRgb *in_scanline = reinterpret_cast(fromImage.bits() + y * fromImage.bytesPerLine()); + const QRgb *in_scanline = reinterpret_cast(fromImage.constBits() + y * fromImage.bytesPerLine()); if (isRgb32fpx4Data(tmpFormat)) { - QRgbaFloat32 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + QRgbaFloat32 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } else if (isRgb64Data(tmpFormat)) { - QRgba64 *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + QRgba64 *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } else { Q_ASSERT(isRgb32Data(tmpFormat)); - QRgb *out_scanline = reinterpret_cast(toImage.bits() + y * toImage.bytesPerLine()); + QRgb *out_scanline = reinterpret_cast(toImage.d->data + y * toImage.bytesPerLine()); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags); } } @@ -5671,6 +5802,8 @@ QImage QImage::colorTransformed(const QColorTransform &transform) && return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32); case QColorSpace::ColorModel::Gray: return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8); + case QColorSpace::ColorModel::Cmyk: + return colorTransformed(transform, QImage::Format_CMYK8888); case QColorSpace::ColorModel::Undefined: break; } diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h index 9957e93f312..0d42f942533 100644 --- a/src/gui/image/qimage_p.h +++ b/src/gui/image/qimage_p.h @@ -448,6 +448,8 @@ inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format) return QColorSpace::ColorModel::Undefined; // No valid colors case QPixelFormat::ColorModel::Grayscale: return QColorSpace::ColorModel::Gray; + case QPixelFormat::ColorModel::CMYK: + return QColorSpace::ColorModel::Cmyk; default: break; } @@ -467,9 +469,8 @@ inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace:: if (dataCs == cs) return true; // Matching color models - if (cs == QColorSpace::ColorModel::Rgb) - // Can apply RGB CS to Gray data - return dataCs == QColorSpace::ColorModel::Gray; + if (dataCs == QColorSpace::ColorModel::Gray) + return true; // Can apply any CS with white point to Gray data return false; } diff --git a/src/gui/painting/qcolorclut_p.h b/src/gui/painting/qcolorclut_p.h index 81e002d9135..d95e9701b7e 100644 --- a/src/gui/painting/qcolorclut_p.h +++ b/src/gui/painting/qcolorclut_p.h @@ -20,7 +20,7 @@ QT_BEGIN_NAMESPACE -// A 3-dimensional lookup table compatible with ICC lut8, lut16, mAB, and mBA formats. +// A 3/4-dimensional lookup table compatible with ICC lut8, lut16, mAB, and mBA formats. class QColorCLUT { inline static QColorVector interpolate(const QColorVector &a, const QColorVector &b, float t) @@ -32,45 +32,92 @@ class QColorCLUT a += (b - a) * t; } public: - qsizetype gridPointsX = 0; - qsizetype gridPointsY = 0; - qsizetype gridPointsZ = 0; + uint32_t gridPointsX = 0; + uint32_t gridPointsY = 0; + uint32_t gridPointsZ = 0; + uint32_t gridPointsW = 1; QList table; bool isEmpty() const { return table.isEmpty(); } QColorVector apply(const QColorVector &v) const { - Q_ASSERT(table.size() == gridPointsX * gridPointsY * gridPointsZ); + Q_ASSERT(table.size() == gridPointsX * gridPointsY * gridPointsZ * gridPointsW); + QColorVector frac; const float x = std::clamp(v.x, 0.0f, 1.0f) * (gridPointsX - 1); const float y = std::clamp(v.y, 0.0f, 1.0f) * (gridPointsY - 1); const float z = std::clamp(v.z, 0.0f, 1.0f) * (gridPointsZ - 1); - // Variables for trilinear interpolation - const qsizetype lox = static_cast(std::floor(x)); - const qsizetype hix = std::min(lox + 1, gridPointsX - 1); - const qsizetype loy = static_cast(std::floor(y)); - const qsizetype hiy = std::min(loy + 1, gridPointsY - 1); - const qsizetype loz = static_cast(std::floor(z)); - const qsizetype hiz = std::min(loz + 1, gridPointsZ - 1); - const float fracx = x - static_cast(lox); - const float fracy = y - static_cast(loy); - const float fracz = z - static_cast(loz); - QColorVector tmp[4]; - auto index = [&](qsizetype x, qsizetype y, qsizetype z) { return x * gridPointsZ * gridPointsY + y * gridPointsZ + z; }; + const float w = std::clamp(v.w, 0.0f, 1.0f) * (gridPointsW - 1); + const uint32_t lox = static_cast(std::floor(x)); + const uint32_t hix = std::min(lox + 1, gridPointsX - 1); + const uint32_t loy = static_cast(std::floor(y)); + const uint32_t hiy = std::min(loy + 1, gridPointsY - 1); + const uint32_t loz = static_cast(std::floor(z)); + const uint32_t hiz = std::min(loz + 1, gridPointsZ - 1); + const uint32_t low = static_cast(std::floor(w)); + const uint32_t hiw = std::min(low + 1, gridPointsW - 1); + frac.x = x - static_cast(lox); + frac.y = y - static_cast(loy); + frac.z = z - static_cast(loz); + frac.w = w - static_cast(low); + if (gridPointsW > 1) { + auto index = [&](qsizetype x, qsizetype y, qsizetype z, qsizetype w) -> qsizetype { + return x * gridPointsW * gridPointsZ * gridPointsY + + y * gridPointsW * gridPointsZ + + z * gridPointsW + + w; + }; + QColorVector tmp[8]; + // interpolate over w + tmp[0] = interpolate(table[index(lox, loy, loz, low)], + table[index(lox, loy, loz, hiw)], frac.w); + tmp[1] = interpolate(table[index(lox, loy, hiz, low)], + table[index(lox, loy, hiz, hiw)], frac.w); + tmp[2] = interpolate(table[index(lox, hiy, loz, low)], + table[index(lox, hiy, loz, hiw)], frac.w); + tmp[3] = interpolate(table[index(lox, hiy, hiz, low)], + table[index(lox, hiy, hiz, hiw)], frac.w); + tmp[4] = interpolate(table[index(hix, loy, loz, low)], + table[index(hix, loy, loz, hiw)], frac.w); + tmp[5] = interpolate(table[index(hix, loy, hiz, low)], + table[index(hix, loy, hiz, hiw)], frac.w); + tmp[6] = interpolate(table[index(hix, hiy, loz, low)], + table[index(hix, hiy, loz, hiw)], frac.w); + tmp[7] = interpolate(table[index(hix, hiy, hiz, low)], + table[index(hix, hiy, hiz, hiw)], frac.w); + // interpolate over z + for (int i = 0; i < 4; ++i) + interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z); + // interpolate over y + for (int i = 0; i < 2; ++i) + interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y); + // interpolate over x + interpolateIn(tmp[0], tmp[4], frac.x); + return tmp[0]; + } + auto index = [&](qsizetype x, qsizetype y, qsizetype z) -> qsizetype { + return x * gridPointsZ * gridPointsY + + y * gridPointsZ + + z; + }; + QColorVector tmp[8] = { + table[index(lox, loy, loz)], + table[index(lox, loy, hiz)], + table[index(lox, hiy, loz)], + table[index(lox, hiy, hiz)], + table[index(hix, loy, loz)], + table[index(hix, loy, hiz)], + table[index(hix, hiy, loz)], + table[index(hix, hiy, hiz)] + }; // interpolate over z - tmp[0] = interpolate(table[index(lox, loy, loz)], - table[index(lox, loy, hiz)], fracz); - tmp[1] = interpolate(table[index(lox, hiy, loz)], - table[index(lox, hiy, hiz)], fracz); - tmp[2] = interpolate(table[index(hix, loy, loz)], - table[index(hix, loy, hiz)], fracz); - tmp[3] = interpolate(table[index(hix, hiy, loz)], - table[index(hix, hiy, hiz)], fracz); + for (int i = 0; i < 4; ++i) + interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z); // interpolate over y - interpolateIn(tmp[0], tmp[1], fracy); - interpolateIn(tmp[2], tmp[3], fracy); + for (int i = 0; i < 2; ++i) + interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y); // interpolate over x - interpolateIn(tmp[0], tmp[2], fracx); + interpolateIn(tmp[0], tmp[4], frac.x); return tmp[0]; } }; diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h index 2e6e6b58827..8854b87b410 100644 --- a/src/gui/painting/qcolormatrix_p.h +++ b/src/gui/painting/qcolormatrix_p.h @@ -28,20 +28,20 @@ class QColorVector { public: QColorVector() = default; - constexpr QColorVector(float x, float y, float z) : x(x), y(y), z(z) { } + constexpr QColorVector(float x, float y, float z, float w = 0.0f) noexcept : x(x), y(y), z(z), w(w) { } explicit constexpr QColorVector(const QPointF &chr) // from XY chromaticity : x(chr.x() / chr.y()) , y(1.0f) , z((1.0f - chr.x() - chr.y()) / chr.y()) { } - float x = 0.0f; // X, x, L, or red - float y = 0.0f; // Y, y, a, or green - float z = 0.0f; // Z, Y, b, or blue - float _unused = 0.0f; + float x = 0.0f; // X, x, L, or red/cyan + float y = 0.0f; // Y, y, a, or green/magenta + float z = 0.0f; // Z, Y, b, or blue/yellow + float w = 0.0f; // unused, or black constexpr bool isNull() const noexcept { - return !x && !y && !z; + return !x && !y && !z && !w; } bool isValid() const noexcept { @@ -59,10 +59,10 @@ public: return true; } - constexpr QColorVector operator*(float f) const { return QColorVector(x * f, y * f, z * f); } - constexpr QColorVector operator+(const QColorVector &v) const { return QColorVector(x + v.x, y + v.y, z + v.z); } - constexpr QColorVector operator-(const QColorVector &v) const { return QColorVector(x - v.x, y - v.y, z - v.z); } - void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; } + constexpr QColorVector operator*(float f) const { return QColorVector(x * f, y * f, z * f, w * f); } + constexpr QColorVector operator+(const QColorVector &v) const { return QColorVector(x + v.x, y + v.y, z + v.z, w + v.w); } + constexpr QColorVector operator-(const QColorVector &v) const { return QColorVector(x - v.x, y - v.y, z - v.z, w - v.w); } + void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; w += v.w; } QPointF toChromaticity() const { @@ -198,7 +198,8 @@ inline bool comparesEqual(const QColorVector &v1, const QColorVector &v2) { return (std::abs(v1.x - v2.x) < (1.0f / 2048.0f)) && (std::abs(v1.y - v2.y) < (1.0f / 2048.0f)) - && (std::abs(v1.z - v2.z) < (1.0f / 2048.0f)); + && (std::abs(v1.z - v2.z) < (1.0f / 2048.0f)) + && (std::abs(v1.w - v2.w) < (1.0f / 2048.0f)); } // A matrix mapping 3 value colors. diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp index 3ef1ebbeb55..058a6cb5662 100644 --- a/src/gui/painting/qcolorspace.cpp +++ b/src/gui/painting/qcolorspace.cpp @@ -468,6 +468,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit() Q_ASSERT(transferFunction == QColorSpace::TransferFunction::Custom); transformModel = QColorSpace::TransformModel::ThreeComponentMatrix; + colorModel = QColorSpace::ColorModel::Rgb; isPcsLab = false; mAB.clear(); mBA.clear(); @@ -576,6 +577,8 @@ void QColorSpacePrivate::clearElementListProcessingForEdit() \value Undefined No color model \value Rgb An RGB color model with red, green, and blue colors. Can apply to RGB and grayscale data. \value Gray A gray scale color model. Can only apply to grayscale data. + \value Cmyk Can only represent color data defined with cyan, magenta, yellow, and black colors. + In effect only QImage::Format_CMYK32. Note Cmyk color spaces will be TransformModel::ElementListProcessing. */ /*! @@ -1151,7 +1154,8 @@ static bool compareElement(const QColorSpacePrivate::TransferElement &element, { return element.trc[0] == other.trc[0] && element.trc[1] == other.trc[1] - && element.trc[2] == other.trc[2]; + && element.trc[2] == other.trc[2] + && element.trc[3] == other.trc[3]; } static bool compareElement(const QColorMatrix &element, @@ -1175,6 +1179,8 @@ static bool compareElement(const QColorCLUT &element, return false; if (element.gridPointsZ != other.gridPointsZ) return false; + if (element.gridPointsW != other.gridPointsW) + return false; if (element.table.size() != other.table.size()) return false; for (qsizetype i = 0; i < element.table.size(); ++i) { @@ -1231,6 +1237,8 @@ bool QColorSpacePrivate::equals(const QColorSpacePrivate *other) const if (!isThreeComponentMatrix()) { if (isPcsLab != other->isPcsLab) return false; + if (colorModel != other->colorModel) + return false; if (mAB.count() != other->mAB.count()) return false; if (mBA.count() != other->mBA.count()) diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h index cbb1435c569..488cbd6a530 100644 --- a/src/gui/painting/qcolorspace.h +++ b/src/gui/painting/qcolorspace.h @@ -54,6 +54,7 @@ public: Undefined = 0, Rgb = 1, Gray = 2, + Cmyk = 3, }; Q_ENUM(ColorModel) diff --git a/src/gui/painting/qcolorspace_p.h b/src/gui/painting/qcolorspace_p.h index eaa557e02db..4ec801b16b3 100644 --- a/src/gui/painting/qcolorspace_p.h +++ b/src/gui/painting/qcolorspace_p.h @@ -114,7 +114,7 @@ public: // Element list processing data: struct TransferElement { - QColorTrc trc[3]; + QColorTrc trc[4]; }; using Element = std::variant; bool isPcsLab = false; diff --git a/src/gui/painting/qcolortransfertable_p.h b/src/gui/painting/qcolortransfertable_p.h index d857e3942ed..51c9cff6d6a 100644 --- a/src/gui/painting/qcolortransfertable_p.h +++ b/src/gui/painting/qcolortransfertable_p.h @@ -45,7 +45,7 @@ public: Q_ASSERT(qsizetype(size) <= table.size()); } - bool isEmpty() const + bool isEmpty() const noexcept { return m_tableSize == 0; } diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp index a5ed529a153..b1231c38208 100644 --- a/src/gui/painting/qcolortransform.cpp +++ b/src/gui/painting/qcolortransform.cpp @@ -4,6 +4,7 @@ #include "qcolortransform.h" #include "qcolortransform_p.h" +#include "qcmyk_p.h" #include "qcolorclut_p.h" #include "qcolormatrix_p.h" #include "qcolorspace_p.h" @@ -244,38 +245,31 @@ QColor QColorTransform::map(const QColor &color) const if (!d) return color; QColor clr = color; - if (color.spec() != QColor::ExtendedRgb || color.spec() != QColor::Rgb) - clr = clr.toRgb(); + if (d->colorSpaceIn->colorModel == QColorSpace::ColorModel::Rgb) { + if (color.spec() != QColor::ExtendedRgb && color.spec() != QColor::Rgb) + clr = clr.toRgb(); + } else if (d->colorSpaceIn->colorModel == QColorSpace::ColorModel::Cmyk) { + if (color.spec() != QColor::Cmyk) + clr = clr.toCmyk(); + } + + QColorVector c = + (clr.spec() == QColor::Cmyk) + ? QColorVector(clr.cyanF(), clr.magentaF(), clr.yellowF(), clr.blackF()) + : QColorVector(clr.redF(), clr.greenF(), clr.blueF()); + + c = d->mapExtended(c); - QColorVector c = { (float)clr.redF(), (float)clr.greenF(), (float)clr.blueF() }; - if (clr.spec() == QColor::ExtendedRgb) { - c.x = d->colorSpaceIn->trc[0].applyExtended(c.x); - c.y = d->colorSpaceIn->trc[1].applyExtended(c.y); - c.z = d->colorSpaceIn->trc[2].applyExtended(c.z); - } else { - c.x = d->colorSpaceIn->trc[0].apply(c.x); - c.y = d->colorSpaceIn->trc[1].apply(c.y); - c.z = d->colorSpaceIn->trc[2].apply(c.z); - } - c = d->colorMatrix.map(c); - bool inGamut = c.x >= 0.0f && c.x <= 1.0f && c.y >= 0.0f && c.y <= 1.0f && c.z >= 0.0f && c.z <= 1.0f; - if (inGamut) { - if (d->colorSpaceOut->lut.generated.loadAcquire()) { - c.x = d->colorSpaceOut->lut[0]->fromLinear(c.x); - c.y = d->colorSpaceOut->lut[1]->fromLinear(c.y); - c.z = d->colorSpaceOut->lut[2]->fromLinear(c.z); - } else { - c.x = d->colorSpaceOut->trc[0].applyInverse(c.x); - c.y = d->colorSpaceOut->trc[1].applyInverse(c.y); - c.z = d->colorSpaceOut->trc[2].applyInverse(c.z); - } - } else { - c.x = d->colorSpaceOut->trc[0].applyInverseExtended(c.x); - c.y = d->colorSpaceOut->trc[1].applyInverseExtended(c.y); - c.z = d->colorSpaceOut->trc[2].applyInverseExtended(c.z); - } QColor out; - out.setRgbF(c.x, c.y, c.z, color.alphaF()); + if (d->colorSpaceOut->colorModel == QColorSpace::ColorModel::Cmyk) { + c.x = std::clamp(c.x, 0.f, 1.f); + c.y = std::clamp(c.y, 0.f, 1.f); + c.z = std::clamp(c.z, 0.f, 1.f); + c.w = std::clamp(c.w, 0.f, 1.f); + out.setCmykF(c.x, c.y, c.z, c.w, color.alphaF()); + } else { + out.setRgbF(c.x, c.y, c.z, color.alphaF()); + } return out; } @@ -404,6 +398,10 @@ template<> float getAlphaF(const QRgb &r) { return qAlpha(r) * (1.f / 255.f); } +template<> float getAlphaF(const QCmyk32 &) +{ + return 1.f; +} template<> float getAlphaF(const QRgba64 &r) { return r.alpha() * (1.f / 65535.f); @@ -978,9 +976,8 @@ static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len, } template<> -void storeOpaque(QRgbaFloat32 *dst, - const QColorVector *buffer, const qsizetype len, - const QColorTransformPrivate *d_ptr) +void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) { const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 vZero = _mm_set1_ps(0.0f); @@ -1272,6 +1269,18 @@ void loadUnpremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizety } } +void loadUnpremultipliedLUT(QColorVector *buffer, const QCmyk32 *src, const qsizetype len) +{ + const float f = 1.0f / 255.f; + for (qsizetype i = 0; i < len; ++i) { + const QCmyk32 p = src[i]; + buffer[i].x = (p.cyan() * f); + buffer[i].y = (p.magenta() * f); + buffer[i].z = (p.yellow() * f); + buffer[i].w = (p.black() * f); + } +} + void loadUnpremultipliedLUT(QColorVector *buffer, const QRgba64 *src, const qsizetype len) { const float f = 1.0f / 65535.f; @@ -1302,6 +1311,11 @@ void loadPremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype } } +void loadPremultipliedLUT(QColorVector *, const QCmyk32 *, const qsizetype) +{ + Q_UNREACHABLE(); +} + void loadPremultipliedLUT(QColorVector *buffer, const QRgba64 *src, const qsizetype len) { for (qsizetype i = 0; i < len; ++i) { @@ -1321,7 +1335,6 @@ void loadPremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const q buffer[i].z = src[i].b * f; } } - template static void storeUnpremultipliedLUT(QRgb *dst, const T *, const QColorVector *buffer, const qsizetype len) { @@ -1344,6 +1357,19 @@ void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buf } } + +template +void storeUnpremultipliedLUT(QCmyk32 *dst, const T *, const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const int c = buffer[i].x * 255.f; + const int m = buffer[i].y * 255.f; + const int y = buffer[i].z * 255.f; + const int k = buffer[i].w * 255.f; + dst[i] = QCmyk32(c, m, y, k); + } +} + template static void storeUnpremultipliedLUT(QRgba64 *dst, const T *, const QColorVector *buffer, const qsizetype len) @@ -1408,6 +1434,24 @@ 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) +{ + storeUnpremultipliedLUT(dst, src, buffer, len); +} + template static void storePremultipliedLUT(QRgba64 *, const T *, const QColorVector *, const qsizetype); @@ -1423,6 +1467,17 @@ 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) { @@ -1449,10 +1504,13 @@ static void storePremultipliedLUT(QRgbaFloat32 *dst, const T *src, const QColorV static void visitElement(const QColorSpacePrivate::TransferElement &element, QColorVector *buffer, const qsizetype len) { + const bool doW = element.trc[3].isValid(); for (qsizetype i = 0; i < len; ++i) { buffer[i].x = element.trc[0].apply(buffer[i].x); buffer[i].y = element.trc[1].apply(buffer[i].y); buffer[i].z = element.trc[2].apply(buffer[i].z); + if (doW) + buffer[i].w = element.trc[3].apply(buffer[i].w); } } @@ -1573,15 +1631,20 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const template void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const { - if (colorSpaceIn->isThreeComponentMatrix()) { - if (flags & InputPremultiplied) - loadPremultiplied(buffer, src, len, this); - else - loadUnpremultiplied(buffer, src, len, this); + // Avoid compiling this part for S=QCmyk32: + if constexpr (!std::is_same_v) { + if (colorSpaceIn->isThreeComponentMatrix()) { + if (flags & InputPremultiplied) + loadPremultiplied(buffer, src, len, this); + else + loadUnpremultiplied(buffer, src, len, this); - if (!colorSpaceOut->isThreeComponentMatrix()) - applyMatrix(buffer, len, colorMatrix); // colorMatrix should have the first half only. - } else { + if (!colorSpaceOut->isThreeComponentMatrix()) + applyMatrix(buffer, len, colorMatrix); // colorMatrix should have the first half only. + return; + } + } + if (!colorSpaceIn->isThreeComponentMatrix()) { if (flags & InputPremultiplied) loadPremultipliedLUT(buffer, src, len); else @@ -1600,16 +1663,25 @@ 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; - if (colorSpaceOut->isThreeComponentMatrix()) { - applyMatrix(buffer, len, colorMatrix); // colorMatrix should have the latter half only. + // Avoid compiling this part for D=QCmyk32: + if constexpr (!std::is_same_v) { + if (colorSpaceOut->isThreeComponentMatrix()) { + applyMatrix(buffer, len, colorMatrix); // colorMatrix should have the latter half only. - if (flags & InputOpaque) - storeOpaque(dst, buffer, len, this); - else if (flags & OutputPremultiplied) - storePremultiplied(dst, src, buffer, len, this); - else - storeUnpremultiplied(dst, src, buffer, len, this); - } else { + if constexpr (std::is_same_v) { + storeOpaque(dst, buffer, len, this); + } else { + if (flags & InputOpaque) + storeOpaque(dst, buffer, len, this); + else if (flags & OutputPremultiplied) + storePremultiplied(dst, src, buffer, len, this); + else + storeUnpremultiplied(dst, src, buffer, len, this); + } + return; + } + } + if (!colorSpaceOut->isThreeComponentMatrix()) { // Do element based conversion for (auto &&element : colorSpaceOut->mBA) std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); @@ -1709,10 +1781,11 @@ void QColorTransformPrivate::applyThreeComponentMatrix(D *dst, const S *src, qsi template void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const { - if (isThreeComponentMatrix()) - applyThreeComponentMatrix(dst, src, count, flags); - else - applyElementListTransform(dst, src, count, flags); + if constexpr (!std::is_same_v && !std::is_same_v) { + if (isThreeComponentMatrix()) + return applyThreeComponentMatrix(dst, src, count, flags); + } + applyElementListTransform(dst, src, count, flags); } /*! @@ -1724,20 +1797,24 @@ 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); - if (flags & InputPremultiplied) - loadPremultipliedLUT(buffer, src + i, len); - else - loadUnpremultipliedLUT(buffer, src + i, len); - // Do element based conversion - for (auto &&element : colorSpaceIn->mAB) - std::visit([&](auto &&elm) { visitElement(elm, buffer, len); }, element); + 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); @@ -1746,28 +1823,30 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou } return; } + if constexpr (!std::is_same_v) { + if (!colorMatrix.isValid()) + return; - if (!colorMatrix.isValid()) - return; + updateLutsIn(); - updateLutsIn(); - updateLutsOut(); + QUninitialized buffer; - 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); - 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); - applyMatrix(buffer, len, colorMatrix); + storeOpaque(dst + i, buffer, len, this); - storeOpaque(dst + i, buffer, len, this); - - i += len; + i += len; + } + } else { + Q_UNREACHABLE(); } } @@ -1778,7 +1857,8 @@ template void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const { Q_ASSERT(colorSpaceIn->isThreeComponentMatrix()); - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { + updateLutsIn(); + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { if (!colorSpaceOut->isThreeComponentMatrix()) { QUninitialized buffer; @@ -1789,6 +1869,15 @@ void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, Tr 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); @@ -1803,23 +1892,26 @@ void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, Tr } } Q_ASSERT(colorSpaceOut->isThreeComponentMatrix()); - if (!colorMatrix.isValid()) - return; + if constexpr (!std::is_same_v) { + if (!colorMatrix.isValid()) + return; - updateLutsIn(); - updateLutsOut(); + updateLutsOut(); - QUninitialized buffer; + QUninitialized buffer; - qsizetype i = 0; - while (i < count) { - const qsizetype len = qMin(count - i, WorkBlockSize); - loadGray(buffer, src + i, len, this); + qsizetype i = 0; + while (i < count) { + const qsizetype len = qMin(count - i, WorkBlockSize); + loadGray(buffer, src + i, len, this); - applyMatrix(buffer, len, colorMatrix); + applyMatrix(buffer, len, colorMatrix); - storeOpaque(dst + i, buffer, len, this); - i += len; + storeOpaque(dst + i, buffer, len, this); + i += len; + } + } else { + Q_UNREACHABLE(); } } @@ -1853,17 +1945,28 @@ void QColorTransformPrivate::prepare() // 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; 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 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 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; template void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; template void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const; diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h index 92b8b3cc20b..59ea6a2405b 100644 --- a/src/gui/painting/qcolortransform_p.h +++ b/src/gui/painting/qcolortransform_p.h @@ -22,6 +22,7 @@ #include QT_BEGIN_NAMESPACE +class QCmyk32; class QColorTransformPrivate : public QSharedData { diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp index 3ebaf7a9ef1..9c121c920d2 100644 --- a/src/gui/painting/qicc.cpp +++ b/src/gui/painting/qicc.cpp @@ -297,12 +297,13 @@ static bool isValidIccProfile(const ICCProfileHeader &header) return false; } if (header.inputColorSpace != uint(ColorSpaceType::Rgb) - && header.inputColorSpace != uint(ColorSpaceType::Gray)) { + && header.inputColorSpace != uint(ColorSpaceType::Gray) + && header.inputColorSpace != uint(ColorSpaceType::Cmyk)) { qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace)); return false; } if (header.pcs != uint(Tag::XYZ_) && header.pcs != uint(Tag::Lab_)) { - qCInfo(lcIcc, "Unsupported ICC profile connection space 0x%x", quint32(header.pcs)); + qCInfo(lcIcc, "Invalid ICC profile connection space 0x%x", quint32(header.pcs)); return false; } @@ -679,13 +680,23 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT } template -static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut) +static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar outputChannels) { - for (qsizetype index = 0; index < clut->table.size(); ++index) { - QColorVector v(tableData[index * 3 + 0] * f, - tableData[index * 3 + 1] * f, - tableData[index * 3 + 2] * f); - clut->table[index] = v; + if (outputChannels == 4) { + for (qsizetype index = 0; index < clut->table.size(); ++index) { + QColorVector v(tableData[index * 4 + 0] * f, + tableData[index * 4 + 1] * f, + tableData[index * 4 + 2] * f, + tableData[index * 4 + 3] * f); + clut->table[index] = v; + }; + } else { + for (qsizetype index = 0; index < clut->table.size(); ++index) { + QColorVector v(tableData[index * 3 + 0] * f, + tableData[index * 3 + 1] * f, + tableData[index * 3 + 2] * f); + clut->table[index] = v; + }; } } @@ -723,6 +734,10 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo Q_ASSERT(lut.type == quint32(Tag::mft2)); inputTableEntries = lut.inputTableEntries; outputTableEntries = lut.outputTableEntries; + if (inputTableEntries < 2 || inputTableEntries > 4096) + return false; + if (outputTableEntries < 2 || outputTableEntries > 4096) + return false; precision = 2; } @@ -746,12 +761,12 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo return false; } - if (lut.inputChannels != 3) { + if (lut.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.inputChannels == 4)) { qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels; return false; } - if (lut.outputChannels != 3) { + if (lut.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.outputChannels == 4)) { qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels; return false; } @@ -782,15 +797,18 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo clutElement.table.resize(clutTableSize); clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints; + if (lut.inputChannels == 4) + clutElement.gridPointsW = lut.clutGridPoints; + if constexpr (std::is_same_v) { - parseCLUT(tableData, 1.f / 255.f, &clutElement); + parseCLUT(tableData, 1.f / 255.f, &clutElement, lut.outputChannels); } else { float f = 1.0f / 65535.f; if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab f = 1.0f / 65280.f; QList clutTable(clutTableSize * lut.outputChannels); qFromBigEndian(tableData, clutTable.size(), clutTable.data()); - parseCLUT(clutTable.constData(), f, &clutElement); + parseCLUT(clutTable.constData(), f, &clutElement, lut.outputChannels); } tableData += clutTableSize * lut.outputChannels * precision; @@ -846,12 +864,12 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo return false; } - if (mab.inputChannels != 3) { + if (mab.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.inputChannels == 4)) { qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels; return false; } - if (mab.outputChannels != 3) { + if (mab.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.outputChannels == 4)) { qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels; return false; } @@ -901,7 +919,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true; // B Curves - if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, 3)) { + if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? mab.outputChannels : mab.inputChannels)) { qCWarning(lcIcc) << "Invalid B curves"; return false; } else { @@ -910,7 +928,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo // A Curves if (mab.aCurvesOffset) { - if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, 3)) { + if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? mab.inputChannels : mab.outputChannels)) { qCWarning(lcIcc) << "Invalid A curves"; return false; } else { @@ -951,9 +969,10 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo // CLUT if (mab.clutOffset) { - clutElement.gridPointsX = data[tagEntry.offset + mab.clutOffset]; - clutElement.gridPointsY = data[tagEntry.offset + mab.clutOffset + 1]; - clutElement.gridPointsZ = data[tagEntry.offset + mab.clutOffset + 2]; + clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab.clutOffset]); + clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab.clutOffset + 1]); + clutElement.gridPointsZ = uint8_t(data[tagEntry.offset + mab.clutOffset + 2]); + clutElement.gridPointsW = std::max(uint8_t(data[tagEntry.offset + mab.clutOffset + 3]), uint8_t(1)); const uchar precision = data[tagEntry.offset + mab.clutOffset + 16]; if (precision > 2 || precision < 1) { qCWarning(lcIcc) << "Invalid mAB/mBA element CLUT precision"; @@ -963,7 +982,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo qCWarning(lcIcc) << "Empty CLUT"; return false; } - const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ; + const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW; if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) { qCWarning(lcIcc) << "CLUT oversized for tag"; return false; @@ -973,10 +992,10 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo if (precision == 2) { QList clutTable(clutTableSize * mab.outputChannels); qFromBigEndian(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data()); - parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement); + parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, mab.outputChannels); } else { const uint8_t *clutTable = reinterpret_cast(data.constData() + tagEntry.offset + mab.clutOffset + 20); - parseCLUT(clutTable, (1.f/255.f), &clutElement); + parseCLUT(clutTable, (1.f/255.f), &clutElement, mab.outputChannels); } } @@ -987,7 +1006,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo if (!clutElement.isEmpty()) colorSpacePrivate->mAB.append(std::move(clutElement)); } - if (mab.mCurvesOffset) { + if (mab.mCurvesOffset && mab.outputChannels == 3) { if (!mCurvesAreLinear) colorSpacePrivate->mAB.append(std::move(mTableElement)); if (!matrixElement.isIdentity()) @@ -1000,7 +1019,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo } else { if (!bCurvesAreLinear) colorSpacePrivate->mBA.append(std::move(bTableElement)); - if (mab.mCurvesOffset) { + if (mab.mCurvesOffset && mab.inputChannels == 3) { if (!matrixElement.isIdentity()) colorSpacePrivate->mBA.append(std::move(matrixElement)); if (!offsetElement.isNull()) @@ -1285,11 +1304,11 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace) // Check the profile is three-component matrix based: if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) || !tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) || - !tagIndex.contains(Tag::wtpt)) { + !tagIndex.contains(Tag::wtpt) || header.pcs == uint(Tag::Lab_)) { threeComponentMatrix = false; // Check if the profile is valid n-LUT based: if (!tagIndex.contains(Tag::A2B0)) { - qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor LUT"; + qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT"; return false; } } @@ -1298,6 +1317,12 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace) qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based"; return false; } + } else if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) { + threeComponentMatrix = false; + if (!tagIndex.contains(Tag::A2B0)) { + qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - CMYK, not n-LUT"; + return false; + } } else { Q_UNREACHABLE(); } @@ -1338,7 +1363,10 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace) } else { colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_)); colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing; - colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb; + if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) + colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk; + else + colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb; // Only parse the default perceptual transform for now if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true)) diff --git a/tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc b/tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc new file mode 100644 index 00000000000..b5a73495bfc Binary files /dev/null and b/tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc differ diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp index 1abca9e1883..1d0cdfcc4e5 100644 --- a/tests/auto/gui/image/qimage/tst_qimage.cpp +++ b/tests/auto/gui/image/qimage/tst_qimage.cpp @@ -171,6 +171,8 @@ private slots: void colorSpaceRgbConversion_data(); void colorSpaceRgbConversion(); + void colorSpaceCmykConversion_data(); + void colorSpaceCmykConversion(); void deepCopyWhenPaintingActive(); void scaled_QTBUG19157(); @@ -3322,6 +3324,69 @@ void tst_QImage::colorSpaceRgbConversion() } +void tst_QImage::colorSpaceCmykConversion_data() +{ + QTest::addColumn("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); diff --git a/tests/auto/gui/painting/qcolorspace/resources/CGATS001Compat-v2-micro.icc b/tests/auto/gui/painting/qcolorspace/resources/CGATS001Compat-v2-micro.icc new file mode 100644 index 00000000000..b5a73495bfc Binary files /dev/null and b/tests/auto/gui/painting/qcolorspace/resources/CGATS001Compat-v2-micro.icc differ diff --git a/tests/auto/gui/painting/qcolorspace/resources/sGrey-v4.icc b/tests/auto/gui/painting/qcolorspace/resources/sGrey-v4.icc new file mode 100644 index 00000000000..2187b6786aa Binary files /dev/null and b/tests/auto/gui/painting/qcolorspace/resources/sGrey-v4.icc differ diff --git a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp index 3f17b0ba423..4f366b02db3 100644 --- a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp +++ b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp @@ -188,21 +188,41 @@ void tst_QColorSpace::fromIccProfile_data() QTest::addColumn("testProfile"); QTest::addColumn("namedColorSpace"); QTest::addColumn("transferFunction"); + QTest::addColumn("transformModel"); + QTest::addColumn("colorModel"); QTest::addColumn("description"); QString prefix = QFINDTESTDATA("resources/"); // Read the official sRGB ICCv2 profile: QTest::newRow("sRGB2014 (ICCv2)") << prefix + "sRGB2014.icc" << QColorSpace::SRgb - << QColorSpace::TransferFunction::SRgb << QString("sRGB2014"); + << QColorSpace::TransferFunction::SRgb + << QColorSpace::TransformModel::ThreeComponentMatrix + << QColorSpace::ColorModel::Rgb << QString("sRGB2014"); // My monitor's profile: QTest::newRow("HP ZR30w (ICCv4)") << prefix + "HP_ZR30w.icc" << QColorSpace::NamedColorSpace(0) - << QColorSpace::TransferFunction::Gamma << QString("HP Z30i"); + << QColorSpace::TransferFunction::Gamma + << QColorSpace::TransformModel::ThreeComponentMatrix + << QColorSpace::ColorModel::Rgb << QString("HP Z30i"); // A profile to HD TV QTest::newRow("VideoHD") << prefix + "VideoHD.icc" << QColorSpace::NamedColorSpace(0) - << QColorSpace::TransferFunction::Custom << QString("HDTV (Rec. 709)"); + << QColorSpace::TransferFunction::Custom + << QColorSpace::TransformModel::ElementListProcessing + << QColorSpace::ColorModel::Rgb << QString("HDTV (Rec. 709)"); // sRGB on PCSLab format QTest::newRow("sRGB ICCv4 Appearance") << prefix + "sRGB_ICC_v4_Appearance.icc" << QColorSpace::NamedColorSpace(0) - << QColorSpace::TransferFunction::Custom << QString("sRGB_ICC_v4_Appearance.icc"); + << QColorSpace::TransferFunction::Custom + << QColorSpace::TransformModel::ElementListProcessing + << QColorSpace::ColorModel::Rgb << QString("sRGB_ICC_v4_Appearance.icc"); + // Grayscale profile + QTest::newRow("sGrey-v4") << prefix + "sGrey-v4.icc" << QColorSpace::NamedColorSpace(0) + << QColorSpace::TransferFunction::SRgb + << QColorSpace::TransformModel::ThreeComponentMatrix + << QColorSpace::ColorModel::Gray << QString("sGry"); + // CMYK profile + QTest::newRow("CGATS compat") << prefix + "CGATS001Compat-v2-micro.icc" << QColorSpace::NamedColorSpace(0) + << QColorSpace::TransferFunction::Custom + << QColorSpace::TransformModel::ElementListProcessing + << QColorSpace::ColorModel::Cmyk << QString("uCMY"); } void tst_QColorSpace::fromIccProfile() @@ -210,6 +230,8 @@ void tst_QColorSpace::fromIccProfile() QFETCH(QString, testProfile); QFETCH(QColorSpace::NamedColorSpace, namedColorSpace); QFETCH(QColorSpace::TransferFunction, transferFunction); + QFETCH(QColorSpace::TransformModel, transformModel); + QFETCH(QColorSpace::ColorModel, colorModel); QFETCH(QString, description); QFile file(testProfile); @@ -222,6 +244,8 @@ void tst_QColorSpace::fromIccProfile() QCOMPARE(fileColorSpace, namedColorSpace); QCOMPARE(fileColorSpace.transferFunction(), transferFunction); + QCOMPARE(fileColorSpace.transformModel(), transformModel); + QCOMPARE(fileColorSpace.colorModel(), colorModel); QCOMPARE(fileColorSpace.description(), description); QByteArray iccProfile2 = fileColorSpace.iccProfile(); diff --git a/tests/libfuzzer/gui/painting/qcolorspace/fromiccprofile/main.cpp b/tests/libfuzzer/gui/painting/qcolorspace/fromiccprofile/main.cpp index 5055b572294..3b9e8434057 100644 --- a/tests/libfuzzer/gui/painting/qcolorspace/fromiccprofile/main.cpp +++ b/tests/libfuzzer/gui/painting/qcolorspace/fromiccprofile/main.cpp @@ -4,9 +4,25 @@ #include #include +#include #include #include +static QImage::Format toFormat(QColorSpace::ColorModel model) +{ + switch (model) { + case QColorSpace::ColorModel::Rgb: + return QImage::Format_RGB32; + case QColorSpace::ColorModel::Gray: + return QImage::Format_Grayscale16; + case QColorSpace::ColorModel::Cmyk: + return QImage::Format_CMYK8888; + case QColorSpace::ColorModel::Undefined: + break; + } + return QImage::Format_Invalid; +} + extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) { // to reduce noise and increase speed static char quiet[] = "QT_LOGGING_RULES=qt.gui.icc=false"; @@ -27,9 +43,9 @@ extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) { cs2.setDescription("Hello"); bool b = (cs == cs2); Q_UNUSED(b); - QRgb color = 0xfaf8fa00; + QColor color(0xfaf8fa00); color = trans1.map(color); - QImage img(16, 2, cs.colorModel() == QColorSpace::ColorModel::Rgb ? QImage::Format_RGB32 : QImage::Format_Grayscale8); + QImage img(16, 2, toFormat(cs.colorModel())); img.setColorSpace(cs); QImage img2 = img.convertedToColorSpace(QColorSpace::SRgb); if (cs.isValidTarget()) { diff --git a/tests/manual/examples/widgets/widgets/imageviewer/imageviewer.cpp b/tests/manual/examples/widgets/widgets/imageviewer/imageviewer.cpp index fd8fbaf2b83..72a93dbf1b1 100644 --- a/tests/manual/examples/widgets/widgets/imageviewer/imageviewer.cpp +++ b/tests/manual/examples/widgets/widgets/imageviewer/imageviewer.cpp @@ -79,10 +79,11 @@ bool ImageViewer::loadFile(const QString &fileName) void ImageViewer::setImage(const QImage &newImage) { - image = newImage; - if (image.colorSpace().isValid()) - image.convertToColorSpace(QColorSpace::SRgb); - imageLabel->setPixmap(QPixmap::fromImage(image)); + if (newImage.colorSpace().isValid()) + image = newImage.convertedToColorSpace(QColorSpace::SRgb); + else + image = newImage; + imageLabel->setPixmap(QPixmap::fromImage(image, Qt::NoFormatConversion)); //! [4] scaleFactor = 1.0;