diff options
Diffstat (limited to 'src/gui/painting/qcolortransform.cpp')
-rw-r--r-- | src/gui/painting/qcolortransform.cpp | 1162 |
1 files changed, 912 insertions, 250 deletions
diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp index c89909cff4..aac07bdc09 100644 --- a/src/gui/painting/qcolortransform.cpp +++ b/src/gui/painting/qcolortransform.cpp @@ -1,9 +1,11 @@ -// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qcolortransform.h" #include "qcolortransform_p.h" +#include "qcmyk_p.h" +#include "qcolorclut_p.h" #include "qcolormatrix_p.h" #include "qcolorspace_p.h" #include "qcolortrc_p.h" @@ -139,11 +141,31 @@ bool QColorTransform::compare(const QColorTransform &other) const return false; if (bool(d->colorSpaceOut) != bool(other.d->colorSpaceOut)) return false; - for (int i = 0; i < 3; ++i) { - if (d->colorSpaceIn && d->colorSpaceIn->trc[i] != other.d->colorSpaceIn->trc[i]) + if (d->colorSpaceIn) { + if (d->colorSpaceIn->transformModel != other.d->colorSpaceIn->transformModel) return false; - if (d->colorSpaceOut && d->colorSpaceOut->trc[i] != other.d->colorSpaceOut->trc[i]) + if (d->colorSpaceIn->isThreeComponentMatrix()) { + for (int i = 0; i < 3; ++i) { + if (d->colorSpaceIn && d->colorSpaceIn->trc[i] != other.d->colorSpaceIn->trc[i]) + return false; + } + } else { + if (!d->colorSpaceIn->equals(other.d->colorSpaceIn.constData())) + return false; + } + } + if (d->colorSpaceOut) { + if (d->colorSpaceOut->transformModel != other.d->colorSpaceOut->transformModel) return false; + if (d->colorSpaceOut->isThreeComponentMatrix()) { + for (int i = 0; i < 3; ++i) { + if (d->colorSpaceOut && d->colorSpaceOut->trc[i] != other.d->colorSpaceOut->trc[i]) + return false; + } + } else { + if (!d->colorSpaceOut->equals(other.d->colorSpaceOut.constData())) + return false; + } } return true; } @@ -159,23 +181,7 @@ QRgb QColorTransform::map(QRgb argb) const return argb; constexpr float f = 1.0f / 255.0f; QColorVector c = { qRed(argb) * f, qGreen(argb) * f, qBlue(argb) * f }; - 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); - c.x = std::max(0.0f, std::min(1.0f, c.x)); - c.y = std::max(0.0f, std::min(1.0f, c.y)); - c.z = std::max(0.0f, std::min(1.0f, c.z)); - 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); - } - + c = d->map(c); return qRgba(c.x * 255 + 0.5f, c.y * 255 + 0.5f, c.z * 255 + 0.5f, qAlpha(argb)); } @@ -190,27 +196,47 @@ QRgba64 QColorTransform::map(QRgba64 rgba64) const return rgba64; constexpr float f = 1.0f / 65535.0f; QColorVector c = { rgba64.red() * f, rgba64.green() * f, rgba64.blue() * f }; - 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); - c.x = std::max(0.0f, std::min(1.0f, c.x)); - c.y = std::max(0.0f, std::min(1.0f, c.y)); - c.z = std::max(0.0f, std::min(1.0f, c.z)); - 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); - } - + c = d->map(c); return QRgba64::fromRgba64(c.x * 65535.f + 0.5f, c.y * 65535.f + 0.5f, c.z * 65535.f + 0.5f, rgba64.alpha()); } /*! + Applies the color transformation on the QRgbaFloat16 value \a rgbafp16. + + The input should be opaque or unpremultiplied. + \since 6.4 +*/ +QRgbaFloat16 QColorTransform::map(QRgbaFloat16 rgbafp16) const +{ + if (!d) + return rgbafp16; + QColorVector c(rgbafp16.r, rgbafp16.g, rgbafp16.b); + c = d->mapExtended(c); + rgbafp16.r = qfloat16(c.x); + rgbafp16.g = qfloat16(c.y); + rgbafp16.b = qfloat16(c.z); + return rgbafp16; +} + +/*! + Applies the color transformation on the QRgbaFloat32 value \a rgbafp32. + + The input should be opaque or unpremultiplied. + \since 6.4 +*/ +QRgbaFloat32 QColorTransform::map(QRgbaFloat32 rgbafp32) const +{ + if (!d) + return rgbafp32; + QColorVector c(rgbafp32.r, rgbafp32.g, rgbafp32.b); + c = d->mapExtended(c); + rgbafp32.r = c.x; + rgbafp32.g = c.y; + rgbafp32.b = c.z; + return rgbafp32; +} + +/*! Applies the color transformation on the QColor value \a color. */ @@ -219,44 +245,42 @@ 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(); - - 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); + 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); + 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; } // Optimized sub-routines for fast block based conversion: -template<bool DoClamp = true> +enum ApplyMatrixForm { + DoNotClamp = 0, + DoClamp = 1 +}; + +template<ApplyMatrixForm doClamp = DoClamp> static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorMatrix &colorMatrix) { #if defined(__SSE2__) @@ -276,7 +300,7 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM cx = _mm_add_ps(cx, cy); cx = _mm_add_ps(cx, cz); // Clamp: - if (DoClamp) { + if (doClamp) { cx = _mm_min_ps(cx, maxV); cx = _mm_max_ps(cx, minV); } @@ -296,19 +320,19 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM cx = vaddq_f32(cx, cy); cx = vaddq_f32(cx, cz); // Clamp: - if (DoClamp) { + if (doClamp) { cx = vminq_f32(cx, maxV); cx = vmaxq_f32(cx, minV); } vst1q_f32(&buffer[j].x, cx); } #else - for (int j = 0; j < len; ++j) { + for (qsizetype j = 0; j < len; ++j) { const QColorVector cv = colorMatrix.map(buffer[j]); - if (DoClamp) { - buffer[j].x = std::max(0.0f, std::min(1.0f, cv.x)); - buffer[j].y = std::max(0.0f, std::min(1.0f, cv.y)); - buffer[j].z = std::max(0.0f, std::min(1.0f, cv.z)); + if (doClamp) { + buffer[j].x = std::clamp(cv.x, 0.f, 1.f); + buffer[j].y = std::clamp(cv.y, 0.f, 1.f); + buffer[j].z = std::clamp(cv.z, 0.f, 1.f); } else { buffer[j] = cv; } @@ -316,6 +340,39 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM #endif } +template<ApplyMatrixForm doClamp = DoClamp> +static void clampIfNeeded(QColorVector *buffer, const qsizetype len) +{ + if constexpr (doClamp != DoClamp) + return; +#if defined(__SSE2__) + const __m128 minV = _mm_set1_ps(0.0f); + const __m128 maxV = _mm_set1_ps(1.0f); + for (qsizetype j = 0; j < len; ++j) { + __m128 c = _mm_loadu_ps(&buffer[j].x); + c = _mm_min_ps(c, maxV); + c = _mm_max_ps(c, minV); + _mm_storeu_ps(&buffer[j].x, c); + } +#elif defined(__ARM_NEON__) + const float32x4_t minV = vdupq_n_f32(0.0f); + const float32x4_t maxV = vdupq_n_f32(1.0f); + for (qsizetype j = 0; j < len; ++j) { + float32x4_t c = vld1q_f32(&buffer[j].x); + c = vminq_f32(c, maxV); + c = vmaxq_f32(c, minV); + vst1q_f32(&buffer[j].x, c); + } +#else + for (qsizetype j = 0; j < len; ++j) { + const QColorVector cv = buffer[j]; + buffer[j].x = std::clamp(cv.x, 0.f, 1.f); + buffer[j].y = std::clamp(cv.y, 0.f, 1.f); + buffer[j].z = std::clamp(cv.z, 0.f, 1.f); + } +#endif +} + #if defined(__SSE2__) || defined(__ARM_NEON__) template<typename T> static constexpr inline bool isArgb(); @@ -332,9 +389,29 @@ inline int getAlpha<QRgb>(const QRgb &p) template<> inline int getAlpha<QRgba64>(const QRgba64 &p) { return p.alpha(); } + #endif template<typename T> +static float getAlphaF(const T &); +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); +} +template<> float getAlphaF(const QRgbaFloat32 &r) +{ + return r.a; +} + +template<typename T> static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr); template<typename T> static void loadUnpremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr); @@ -370,7 +447,7 @@ inline void loadP<QRgba64>(const QRgba64 &p, __m128i &v) template<typename T> static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr) { - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256)); constexpr bool isARGB = isArgb<T>(); for (qsizetype i = 0; i < len; ++i) { @@ -389,7 +466,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp vf = _mm_andnot_ps(vAlphaMask, vf); // LUT - v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = isARGB ? _mm_extract_epi16(v, 4) : _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = isARGB ? _mm_extract_epi16(v, 0) : _mm_extract_epi16(v, 4); @@ -405,7 +482,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp template<> void loadPremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len, const QColorTransformPrivate *d_ptr) { - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256)); const __m128 vZero = _mm_set1_ps(0.0f); const __m128 vOne = _mm_set1_ps(1.0f); @@ -427,7 +504,7 @@ void loadPremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *s const __m128 over = _mm_cmpgt_ps(vf, vOne); if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) { // Within gamut - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = _mm_extract_epi16(v, 4); @@ -446,7 +523,7 @@ void loadPremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *s } } -// Load to [0-4080] in 4x32 SIMD +// Load to [0->TrcResolution] in 4x32 SIMD template<typename T> static inline void loadPU(const T &p, __m128i &v); @@ -460,7 +537,7 @@ inline void loadPU<QRgb>(const QRgb &p, __m128i &v) v = _mm_unpacklo_epi8(v, _mm_setzero_si128()); v = _mm_unpacklo_epi16(v, _mm_setzero_si128()); #endif - v = _mm_slli_epi32(v, 4); + v = _mm_slli_epi32(v, QColorTrcLut::ShiftUp); } template<> @@ -473,7 +550,7 @@ inline void loadPU<QRgba64>(const QRgba64 &p, __m128i &v) #else v = _mm_unpacklo_epi16(v, _mm_setzero_si128()); #endif - v = _mm_srli_epi32(v, 4); + v = _mm_srli_epi32(v, QColorTrcLut::ShiftDown); } template<typename T> @@ -498,7 +575,7 @@ void loadUnpremultiplied(QColorVector *buffer, const T *src, const qsizetype len template<> void loadUnpremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len, const QColorTransformPrivate *d_ptr) { - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256)); const __m128 vZero = _mm_set1_ps(0.0f); const __m128 vOne = _mm_set1_ps(1.0f); @@ -508,7 +585,7 @@ void loadUnpremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 const __m128 over = _mm_cmpgt_ps(vf, vOne); if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) { // Within gamut - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = _mm_extract_epi16(v, 4); @@ -569,7 +646,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp vf = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(vf), vAlphaMask)); // LUT - v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f))); + v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f))); const int ridx = isARGB ? vgetq_lane_u32(v, 2) : vgetq_lane_u32(v, 0); const int gidx = vgetq_lane_u32(v, 1); const int bidx = isARGB ? vgetq_lane_u32(v, 0) : vgetq_lane_u32(v, 2); @@ -582,7 +659,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp } } -// Load to [0-4080] in 4x32 SIMD +// Load to [0->TrcResultion] in 4x32 SIMD template<typename T> static inline void loadPU(const T &p, uint32x4_t &v); @@ -590,7 +667,7 @@ template<> inline void loadPU<QRgb>(const QRgb &p, uint32x4_t &v) { v = vmovl_u16(vget_low_u16(vmovl_u8(vreinterpret_u8_u32(vmov_n_u32(p))))); - v = vshlq_n_u32(v, 4); + v = vshlq_n_u32(v, QColorTrcLut::ShiftUp); } template<> @@ -599,7 +676,7 @@ inline void loadPU<QRgba64>(const QRgba64 &p, uint32x4_t &v) uint16x4_t v16 = vreinterpret_u16_u64(vld1_u64(reinterpret_cast<const uint64_t *>(&p))); v16 = vsub_u16(v16, vshr_n_u16(v16, 8)); v = vmovl_u16(v16); - v = vshrq_n_u32(v, 4); + v = vshrq_n_u32(v, QColorTrcLut::ShiftDown); } template<typename T> @@ -628,7 +705,7 @@ void loadPremultiplied<QRgb>(QColorVector *buffer, const QRgb *src, const qsizet const uint p = src[i]; const int a = qAlpha(p); if (a) { - const float ia = 4080.0f / a; + const float ia = float(QColorTrcLut::Resolution) / a; const int ridx = int(qRed(p) * ia + 0.5f); const int gidx = int(qGreen(p) * ia + 0.5f); const int bidx = int(qBlue(p) * ia + 0.5f); @@ -648,7 +725,7 @@ void loadPremultiplied<QRgba64>(QColorVector *buffer, const QRgba64 *src, const const QRgba64 &p = src[i]; const int a = p.alpha(); if (a) { - const float ia = 4080.0f / a; + const float ia = float(QColorTrcLut::Resolution) / a; const int ridx = int(p.red() * ia + 0.5f); const int gidx = int(p.green() * ia + 0.5f); const int bidx = int(p.blue() * ia + 0.5f); @@ -738,17 +815,18 @@ inline void storeP<QRgba64>(QRgba64 &p, __m128i &v, int a) #endif } -template<typename T> -static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len, +template<typename D, typename S, + typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>> +static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256)); - constexpr bool isARGB = isArgb<T>(); + constexpr bool isARGB = isArgb<D>(); for (qsizetype i = 0; i < len; ++i) { - const int a = getAlpha<T>(src[i]); + const int a = getAlpha<S>(src[i]); __m128 vf = _mm_loadu_ps(&buffer[i].x); - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); __m128 va = _mm_mul_ps(_mm_set1_ps(a), iFF00); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); @@ -759,21 +837,21 @@ static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, vf = _mm_cvtepi32_ps(v); vf = _mm_mul_ps(vf, va); v = _mm_cvtps_epi32(vf); - storeP<T>(dst[i], v, a); + storeP<D>(dst[i], v, a); } } -template<> -void storePremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, - const QColorVector *buffer, const qsizetype len, - const QColorTransformPrivate *d_ptr) +template<typename S> +static void storePremultiplied(QRgbaFloat32 *dst, const S *src, + const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) { - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 vZero = _mm_set1_ps(0.0f); const __m128 vOne = _mm_set1_ps(1.0f); const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256)); for (qsizetype i = 0; i < len; ++i) { - const float a = src[i].a; + const float a = getAlphaF<S>(src[i]); __m128 va = _mm_set1_ps(a); __m128 vf = _mm_loadu_ps(&buffer[i].x); const __m128 under = _mm_cmplt_ps(vf, vZero); @@ -781,7 +859,7 @@ void storePremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) { // Within gamut va = _mm_mul_ps(va, viFF00); - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = _mm_extract_epi16(v, 4); @@ -820,16 +898,17 @@ inline void storePU<QRgba64>(QRgba64 &p, __m128i &v, int a) _mm_storel_epi64((__m128i *)&p, v); } -template<typename T> -static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len, +template<typename D, typename S, + typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>> +static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - const __m128 v4080 = _mm_set1_ps(4080.f); - constexpr bool isARGB = isArgb<T>(); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); + constexpr bool isARGB = isArgb<D>(); for (qsizetype i = 0; i < len; ++i) { - const int a = getAlpha<T>(src[i]); + const int a = getAlpha<S>(src[i]); __m128 vf = _mm_loadu_ps(&buffer[i].x); - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = _mm_extract_epi16(v, 4); @@ -837,27 +916,27 @@ static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffe v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], isARGB ? 2 : 0); v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], 1); v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], isARGB ? 0 : 2); - storePU<T>(dst[i], v, a); + storePU<D>(dst[i], v, a); } } -template<> -void storeUnpremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, - const QColorVector *buffer, const qsizetype len, - const QColorTransformPrivate *d_ptr) +template<typename S> +void storeUnpremultiplied(QRgbaFloat32 *dst, const S *src, + const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) { - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 vZero = _mm_set1_ps(0.0f); const __m128 vOne = _mm_set1_ps(1.0f); const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256)); for (qsizetype i = 0; i < len; ++i) { - const float a = src[i].a; + const float a = getAlphaF<S>(src[i]); __m128 vf = _mm_loadu_ps(&buffer[i].x); const __m128 under = _mm_cmplt_ps(vf, vZero); const __m128 over = _mm_cmpgt_ps(vf, vOne); if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) { // Within gamut - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = _mm_extract_epi16(v, 4); @@ -877,15 +956,14 @@ void storeUnpremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *s } template<typename T> -static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const qsizetype len, +static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); constexpr bool isARGB = isArgb<T>(); for (qsizetype i = 0; i < len; ++i) { __m128 vf = _mm_loadu_ps(&buffer[i].x); - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = _mm_extract_epi16(v, 4); @@ -898,12 +976,10 @@ static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const } template<> -void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, - const QColorVector *buffer, const qsizetype len, - const QColorTransformPrivate *d_ptr) +void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); - const __m128 v4080 = _mm_set1_ps(4080.f); + const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); const __m128 vZero = _mm_set1_ps(0.0f); const __m128 vOne = _mm_set1_ps(1.0f); const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256)); @@ -913,7 +989,7 @@ void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const __m128 over = _mm_cmpgt_ps(vf, vOne); if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) { // Within gamut - __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes)); const int ridx = _mm_extract_epi16(v, 0); const int gidx = _mm_extract_epi16(v, 2); const int bidx = _mm_extract_epi16(v, 4); @@ -946,16 +1022,17 @@ inline void storeP<QRgba64>(QRgba64 &p, const uint16x4_t &v) vst1_u16((uint16_t *)&p, v); } -template<typename T> -static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len, +template<typename D, typename S, + typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>> +static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { const float iFF00 = 1.0f / (255 * 256); - constexpr bool isARGB = isArgb<T>(); + constexpr bool isARGB = isArgb<D>(); for (qsizetype i = 0; i < len; ++i) { - const int a = getAlpha<T>(src[i]); + const int a = getAlpha<S>(src[i]); float32x4_t vf = vld1q_f32(&buffer[i].x); - uint32x4_t v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f))); + uint32x4_t v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f))); const int ridx = vgetq_lane_u32(v, 0); const int gidx = vgetq_lane_u32(v, 1); const int bidx = vgetq_lane_u32(v, 2); @@ -968,7 +1045,7 @@ static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, v = vcvtq_u32_f32(vf); uint16x4_t v16 = vmovn_u32(v); v16 = vset_lane_u16(a, v16, 3); - storeP<T>(dst[i], v16); + storeP<D>(dst[i], v16); } } @@ -990,34 +1067,34 @@ inline void storePU<QRgba64>(QRgba64 &p, uint16x4_t &v, int a) vst1_u16((uint16_t *)&p, v); } -template<typename T> -static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len, +template<typename D, typename S, + typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>> +static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - constexpr bool isARGB = isArgb<T>(); + constexpr bool isARGB = isArgb<D>(); for (qsizetype i = 0; i < len; ++i) { - const int a = getAlpha<T>(src[i]); + const int a = getAlpha<S>(src[i]); float32x4_t vf = vld1q_f32(&buffer[i].x); - uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f)))); + uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f)))); const int ridx = vget_lane_u16(v, 0); const int gidx = vget_lane_u16(v, 1); const int bidx = vget_lane_u16(v, 2); v = vset_lane_u16(d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], v, isARGB ? 2 : 0); v = vset_lane_u16(d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], v, 1); v = vset_lane_u16(d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], v, isARGB ? 0 : 2); - storePU<T>(dst[i], v, a); + storePU<D>(dst[i], v, a); } } template<typename T> -static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const qsizetype len, +static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); constexpr bool isARGB = isArgb<T>(); for (qsizetype i = 0; i < len; ++i) { float32x4_t vf = vld1q_f32(&buffer[i].x); - uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f)))); + uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f)))); const int ridx = vget_lane_u16(v, 0); const int gidx = vget_lane_u16(v, 1); const int bidx = vget_lane_u16(v, 2); @@ -1034,9 +1111,9 @@ static void storePremultiplied(QRgb *dst, const QRgb *src, const QColorVector *b for (qsizetype i = 0; i < len; ++i) { const int a = qAlpha(src[i]); const float fa = a / (255.0f * 256.0f); - const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * 4080.0f + 0.5f)]; - const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * 4080.0f + 0.5f)]; - const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * 4080.0f + 0.5f)]; + const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * float(QColorTrcLut::Resolution) + 0.5f)]; + const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * float(QColorTrcLut::Resolution) + 0.5f)]; + const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * float(QColorTrcLut::Resolution) + 0.5f)]; dst[i] = qRgba(r * fa + 0.5f, g * fa + 0.5f, b * fa + 0.5f, a); } } @@ -1052,10 +1129,9 @@ static void storeUnpremultiplied(QRgb *dst, const QRgb *src, const QColorVector } } -static void storeOpaque(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len, +static void storeOpaque(QRgb *dst, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); for (qsizetype i = 0; i < len; ++i) { const int r = d_ptr->colorSpaceOut->lut[0]->u8FromLinearF32(buffer[i].x); const int g = d_ptr->colorSpaceOut->lut[1]->u8FromLinearF32(buffer[i].y); @@ -1064,34 +1140,36 @@ static void storeOpaque(QRgb *dst, const QRgb *src, const QColorVector *buffer, } } -static void storePremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len, +template<typename S> +static void storePremultiplied(QRgba64 *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { for (qsizetype i = 0; i < len; ++i) { - const int a = src[i].alpha(); + const int a = getAlphaF(src[i]) * 65535.f; const float fa = a / (255.0f * 256.0f); - const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * 4080.0f + 0.5f)]; - const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * 4080.0f + 0.5f)]; - const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * 4080.0f + 0.5f)]; + const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * float(QColorTrcLut::Resolution) + 0.5f)]; + const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * float(QColorTrcLut::Resolution) + 0.5f)]; + const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * float(QColorTrcLut::Resolution) + 0.5f)]; dst[i] = qRgba64(r * fa + 0.5f, g * fa + 0.5f, b * fa + 0.5f, a); } } -static void storeUnpremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len, +template<typename S> +static void storeUnpremultiplied(QRgba64 *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { for (qsizetype i = 0; i < len; ++i) { + const int a = getAlphaF(src[i]) * 65535.f; const int r = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].x); const int g = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y); const int b = d_ptr->colorSpaceOut->lut[2]->u16FromLinearF32(buffer[i].z); - dst[i] = qRgba64(r, g, b, src[i].alpha()); + dst[i] = qRgba64(r, g, b, a); } } -static void storeOpaque(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len, +static void storeOpaque(QRgba64 *dst, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); for (qsizetype i = 0; i < len; ++i) { const int r = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].x); const int g = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y); @@ -1101,11 +1179,12 @@ static void storeOpaque(QRgba64 *dst, const QRgba64 *src, const QColorVector *bu } #endif #if !defined(__SSE2__) -static void storePremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer, +template<typename S> +static void storePremultiplied(QRgbaFloat32 *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { for (qsizetype i = 0; i < len; ++i) { - const float a = src[i].a; + const float a = getAlphaF(src[i]); dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x) * a; dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y) * a; dst[i].b = d_ptr->colorSpaceOut->trc[2].applyInverseExtended(buffer[i].z) * a; @@ -1113,11 +1192,12 @@ static void storePremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const } } -static void storeUnpremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer, +template<typename S> +static void storeUnpremultiplied(QRgbaFloat32 *dst, const S *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { for (qsizetype i = 0; i < len; ++i) { - const float a = src[i].a; + const float a = getAlphaF(src[i]); dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x); dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y); dst[i].b = d_ptr->colorSpaceOut->trc[2].applyInverseExtended(buffer[i].z); @@ -1125,10 +1205,9 @@ static void storeUnpremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, con } } -static void storeOpaque(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer, const qsizetype len, +static void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); for (qsizetype i = 0; i < len; ++i) { dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x); dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y); @@ -1137,20 +1216,35 @@ static void storeOpaque(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColor } } #endif -static void storeGray(quint8 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len, + +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; + } +} + +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; + } +} + +static void storeOpaque(quint8 *dst, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); for (qsizetype i = 0; i < len; ++i) - dst[i] = d_ptr->colorSpaceOut->lut[1]->u8FromLinearF32(buffer[i].y); + dst[i] = d_ptr->colorSpaceOut->lut[0]->u8FromLinearF32(buffer[i].y); } -static void storeGray(quint16 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len, +static void storeOpaque(quint16 *dst, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { - Q_UNUSED(src); for (qsizetype i = 0; i < len; ++i) - dst[i] = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y); + dst[i] = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].y); } static constexpr qsizetype WorkBlockSize = 256; @@ -1164,53 +1258,493 @@ private: alignas(T) char data[sizeof(T) * Count]; }; +void loadUnpremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len) +{ + const float f = 1.0f / 255.f; + for (qsizetype i = 0; i < len; ++i) { + const uint p = src[i]; + buffer[i].x = qRed(p) * f; + buffer[i].y = qGreen(p) * f; + buffer[i].z = qBlue(p) * f; + } +} + +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; + for (qsizetype i = 0; i < len; ++i) { + buffer[i].x = src[i].red() * f; + buffer[i].y = src[i].green() * f; + buffer[i].z = src[i].blue() * f; + } +} + +void loadUnpremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + buffer[i].x = src[i].r; + buffer[i].y = src[i].g; + buffer[i].z = src[i].b; + } +} + +void loadPremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const uint p = src[i]; + const float f = 1.0f / qAlpha(p); + buffer[i].x = (qRed(p) * f); + buffer[i].y = (qGreen(p) * f); + buffer[i].z = (qBlue(p) * f); + } +} + +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) { + const float f = 1.0f / src[i].alpha(); + buffer[i].x = (src[i].red() * f); + buffer[i].y = (src[i].green() * f); + buffer[i].z = (src[i].blue() * f); + } +} + +void loadPremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const float f = 1.0f / src[i].a; + buffer[i].x = src[i].r * f; + buffer[i].y = src[i].g * f; + buffer[i].z = src[i].b * f; + } +} template<typename T> -void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const +static void storeUnpremultipliedLUT(QRgb *dst, const T *, const QColorVector *buffer, const qsizetype len) { - if (!colorMatrix.isValid()) + 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 storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, 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] = (src[i] & 0xff000000) | (r << 16) | (g << 8) | (b << 0); + } +} + + +template<typename T> +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<typename T> +static void storeUnpremultipliedLUT(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 storeUnpremultipliedLUT(QRgba64 *dst, const QRgb *src, + const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const int a = qAlpha(src[i]) * 257; + 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, a); + } +} + +template<> +void storeUnpremultipliedLUT(QRgba64 *dst, const QRgba64 *src, + 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, src[i].alpha()); + } +} + +template<typename T> +static void storeUnpremultipliedLUT(QRgbaFloat32 *dst, const T *src, + const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const float r = buffer[i].x; + const float g = buffer[i].y; + const float b = buffer[i].z; + dst[i] = QRgbaFloat32{r, g, b, getAlphaF(src[i])}; + } +} + +template<typename T> +static void storePremultipliedLUT(QRgb *, const T *, const QColorVector *, const qsizetype); + +template<> +void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const int a = qAlpha(src[i]); + const int r = buffer[i].x * a; + const int g = buffer[i].y * a; + const int b = buffer[i].z * a; + dst[i] = (src[i] & 0xff000000) | (r << 16) | (g << 8) | (b << 0); + } +} + +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<typename T> +static void storePremultipliedLUT(QCmyk32 *dst, const T *src, const QColorVector *buffer, const qsizetype len) +{ + storeUnpremultipliedLUT(dst, src, buffer, len); +} + +template<typename T> +static void storePremultipliedLUT(QRgba64 *, const T *, const QColorVector *, const qsizetype); + +template<> +void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const int a = qAlpha(src[i]) * 257; + const int r = buffer[i].x * a; + const int g = buffer[i].y * a; + const int b = buffer[i].z * a; + dst[i] = qRgba64(r, g, b, a); + } +} + +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) +{ + for (qsizetype i = 0; i < len; ++i) { + const int a = src[i].alpha(); + const int r = buffer[i].x * a; + const int g = buffer[i].y * a; + const int b = buffer[i].z * a; + dst[i] = qRgba64(r, g, b, a); + } +} + +template<typename T> +static void storePremultipliedLUT(QRgbaFloat32 *dst, const T *src, const QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) { + const float a = getAlphaF(src[i]); + const float r = buffer[i].x * a; + const float g = buffer[i].y * a; + const float b = buffer[i].z * a; + dst[i] = QRgbaFloat32{r, g, b, a}; + } +} + +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); + } +} + +static void visitElement(const QColorMatrix &element, QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) + buffer[i] = element.map(buffer[i]); +} + +static void visitElement(const QColorVector &offset, QColorVector *buffer, const qsizetype len) +{ + for (qsizetype i = 0; i < len; ++i) + buffer[i] += offset; +} + +static void visitElement(const QColorCLUT &element, QColorVector *buffer, const qsizetype len) +{ + if (element.isEmpty()) return; + for (qsizetype i = 0; i < len; ++i) + buffer[i] = element.apply(buffer[i]); +} - updateLutsIn(); - updateLutsOut(); +/*! + \internal +*/ +QColorVector QColorTransformPrivate::map(QColorVector c) const +{ + if (colorSpaceIn->isThreeComponentMatrix()) { + if (colorSpaceIn->lut.generated.loadAcquire()) { + c.x = colorSpaceIn->lut[0]->toLinear(c.x); + c.y = colorSpaceIn->lut[1]->toLinear(c.y); + c.z = colorSpaceIn->lut[2]->toLinear(c.z); + } else { + c.x = colorSpaceIn->trc[0].apply(c.x); + c.y = colorSpaceIn->trc[1].apply(c.y); + c.z = colorSpaceIn->trc[2].apply(c.z); + } + c = colorMatrix.map(c); + } else { + // Do element based conversion + for (auto &&element : colorSpaceIn->mAB) + std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element); + } + c.x = std::clamp(c.x, 0.0f, 1.0f); + c.y = std::clamp(c.y, 0.0f, 1.0f); + c.z = std::clamp(c.z, 0.0f, 1.0f); + + // Match Profile Connection Spaces (PCS): + if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) + c = c.xyzToLab(); + else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) + c = c.labToXyz(); + + if (colorSpaceOut->isThreeComponentMatrix()) { + if (!colorSpaceIn->isThreeComponentMatrix()) { + c = colorMatrix.map(c); + c.x = std::clamp(c.x, 0.0f, 1.0f); + c.y = std::clamp(c.y, 0.0f, 1.0f); + c.z = std::clamp(c.z, 0.0f, 1.0f); + } + if (colorSpaceOut->lut.generated.loadAcquire()) { + c.x = colorSpaceOut->lut[0]->fromLinear(c.x); + c.y = colorSpaceOut->lut[1]->fromLinear(c.y); + c.z = colorSpaceOut->lut[2]->fromLinear(c.z); + } else { + c.x = colorSpaceOut->trc[0].applyInverse(c.x); + c.y = colorSpaceOut->trc[1].applyInverse(c.y); + c.z = colorSpaceOut->trc[2].applyInverse(c.z); + } + } else { + // Do element based conversion + for (auto &&element : colorSpaceOut->mBA) + std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element); + c.x = std::clamp(c.x, 0.0f, 1.0f); + c.y = std::clamp(c.y, 0.0f, 1.0f); + c.z = std::clamp(c.z, 0.0f, 1.0f); + } + return c; +} - bool doApplyMatrix = !colorMatrix.isIdentity(); - constexpr bool DoClip = !std::is_same_v<T, QRgbaFloat16> && !std::is_same_v<T, QRgbaFloat32>; +/*! + \internal +*/ +QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const +{ + if (colorSpaceIn->isThreeComponentMatrix()) { + c.x = colorSpaceIn->trc[0].applyExtended(c.x); + c.y = colorSpaceIn->trc[1].applyExtended(c.y); + c.z = colorSpaceIn->trc[2].applyExtended(c.z); + c = colorMatrix.map(c); + } else { + // Do element based conversion + for (auto &&element : colorSpaceIn->mAB) + std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element); + } - QUninitialized<QColorVector, WorkBlockSize> buffer; + // Match Profile Connection Spaces (PCS): + if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) + c = c.xyzToLab(); + else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) + c = c.labToXyz(); + + if (colorSpaceOut->isThreeComponentMatrix()) { + if (!colorSpaceIn->isThreeComponentMatrix()) + c = colorMatrix.map(c); + c.x = colorSpaceOut->trc[0].applyInverseExtended(c.x); + c.y = colorSpaceOut->trc[1].applyInverseExtended(c.y); + c.z = colorSpaceOut->trc[2].applyInverseExtended(c.z); + } else { + // Do element based conversion + for (auto &&element : colorSpaceOut->mBA) + std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element); + } + return c; +} + +template<typename S> +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<S, QCmyk32>) { + if (colorSpaceIn->isThreeComponentMatrix()) { + if (flags & InputPremultiplied) + loadPremultiplied(buffer, src, len, this); + else + loadUnpremultiplied(buffer, src, len, this); + + if (!colorSpaceOut->isThreeComponentMatrix()) + applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the first half only. + return; + } + } + Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix()); + + if (flags & InputPremultiplied) + loadPremultipliedLUT(buffer, src, len); + else + loadUnpremultipliedLUT(buffer, src, len); + + if constexpr (std::is_same_v<S, QRgbaFloat16> || std::is_same_v<S, QRgbaFloat32>) + clampIfNeeded<DoClamp>(buffer, len); + + // Do element based conversion + for (auto &&element : colorSpaceIn->mAB) + std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); +} + +template<typename D, typename S> +void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const +{ + constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp; + // Avoid compiling this part for D=QCmyk32: + if constexpr (!std::is_same_v<D, QCmyk32>) { + if (colorSpaceOut->isThreeComponentMatrix()) { + applyMatrix<doClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only. + + if constexpr (std::is_same_v<S, QCmyk32>) { + 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; + } + } + Q_ASSERT(!colorSpaceOut->isThreeComponentMatrix()); + + // Do element based conversion + for (auto &&element : colorSpaceOut->mBA) + std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); + + clampIfNeeded<doClamp>(buffer, len); + if (flags & OutputPremultiplied) + storePremultipliedLUT(dst, src, buffer, len); + else + storeUnpremultipliedLUT(dst, src, buffer, len); +} + +template<typename D, typename S> +void QColorTransformPrivate::applyElementListTransform(D *dst, const S *src, qsizetype count, TransformFlags flags) const +{ + Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix()); + + if (!colorMatrix.isValid()) + return; + + if (colorSpaceIn->isThreeComponentMatrix()) + updateLutsIn(); + if (colorSpaceOut->isThreeComponentMatrix()) + updateLutsOut(); + + QUninitialized<QColorVector, WorkBlockSize> 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); - if (doApplyMatrix) - applyMatrix<DoClip>(buffer, len, colorMatrix); + applyConvertIn(src + i, buffer, len, flags); - if (flags & InputOpaque) - storeOpaque(dst + i, src + i, buffer, len, this); - else if (flags & OutputPremultiplied) - storePremultiplied(dst + i, src + i, buffer, len, this); - else - storeUnpremultiplied(dst + i, src + i, buffer, len, this); + // 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(); + } + + applyConvertOut(dst + i, src + i, buffer, len, flags); i += len; } } template<typename D, typename S> -void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const +void QColorTransformPrivate::applyThreeComponentMatrix(D *dst, const S *src, qsizetype count, TransformFlags flags) const { + Q_ASSERT(colorSpaceIn->isThreeComponentMatrix() && colorSpaceOut->isThreeComponentMatrix()); + if (!colorMatrix.isValid()) return; updateLutsIn(); updateLutsOut(); - QUninitialized<QColorVector, WorkBlockSize> buffer; + bool doApplyMatrix = !colorMatrix.isIdentity(); + constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp; + QUninitialized<QColorVector, WorkBlockSize> buffer; qsizetype i = 0; while (i < count) { const qsizetype len = qMin(count - i, WorkBlockSize); @@ -1219,9 +1753,17 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou else loadUnpremultiplied(buffer, src + i, len, this); - applyMatrix(buffer, len, colorMatrix); + if (doApplyMatrix) + applyMatrix<doClamp>(buffer, len, colorMatrix); + else + clampIfNeeded<doClamp>(buffer, len); - storeGray(dst + i, src + i, buffer, len, this); + if (flags & InputOpaque) + storeOpaque(dst + i, buffer, len, this); + else if (flags & OutputPremultiplied) + storePremultiplied(dst + i, src + i, buffer, len, this); + else + storeUnpremultiplied(dst + i, src + i, buffer, len, this); i += len; } @@ -1229,110 +1771,228 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou /*! \internal - \enum QColorTransformPrivate::TransformFlag + Applies the color transformation on \a count S pixels starting from + \a src and stores the result in \a dst as D pixels . - Defines how the transform is to be applied. + Assumes unpremultiplied data by default. Set \a flags to change defaults. - \value Unpremultiplied The input and output should both be unpremultiplied. - \value InputOpaque The input is guaranteed to be opaque. - \value InputPremultiplied The input is premultiplied. - \value OutputPremultiplied The output should be premultiplied. - \value Premultiplied Both input and output should both be premultiplied. + \sa prepare() */ +template<typename D, typename S> +void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const +{ + if constexpr (!std::is_same_v<D, QCmyk32> && !std::is_same_v<S, QCmyk32>) { + if (isThreeComponentMatrix()) + return applyThreeComponentMatrix<D, S>(dst, src, count, flags); + } + applyElementListTransform<D, S>(dst, src, count, flags); +} /*! \internal - Prepares a color transformation for fast application. You do not need to - call this explicitly as it will be called implicitly on the first transforms, but - if you want predictable performance on the first transforms, you can perform it - in advance. + Is to be called on a color-transform to XYZ, returns only luminance values. - \sa QColorTransform::map(), apply() -*/ -void QColorTransformPrivate::prepare() + */ +template<typename D, typename S> +void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const { - updateLutsIn(); + Q_ASSERT(colorSpaceOut->isThreeComponentMatrix()); updateLutsOut(); -} + if (!colorSpaceIn->isThreeComponentMatrix()) { + QUninitialized<QColorVector, WorkBlockSize> buffer; -/*! - \internal - Applies the color transformation on \a count QRgb pixels starting from - \a src and stores the result in \a dst. + qsizetype i = 0; + while (i < count) { + const qsizetype len = qMin(count - i, WorkBlockSize); - Thread-safe if prepare() has been called first. + applyConvertIn(src, buffer, len, flags); - Assumes unpremultiplied data by default. Set \a flags to change defaults. + // 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(); + } - \sa prepare() -*/ -void QColorTransformPrivate::apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const -{ - apply<QRgb>(dst, src, count, flags); -} + applyMatrix<DoClamp>(buffer, len, colorMatrix); + storeOpaque(dst + i, buffer, len, this); -/*! - \internal - Applies the color transformation on \a count QRgba64 pixels starting from - \a src and stores the result in \a dst. + i += len; + } + return; + } + if constexpr (!std::is_same_v<S, QCmyk32>) { + if (!colorMatrix.isValid()) + return; - Thread-safe if prepare() has been called first. + updateLutsIn(); - Assumes unpremultiplied data by default. Set \a flags to change defaults. + QUninitialized<QColorVector, WorkBlockSize> buffer; - \sa prepare() -*/ -void QColorTransformPrivate::apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const -{ - apply<QRgba64>(dst, src, count, flags); + 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<DoClamp>(buffer, len, colorMatrix); + + storeOpaque(dst + i, buffer, len, this); + + i += len; + } + } else { + Q_UNREACHABLE(); + } } /*! \internal - Applies the color transformation on \a count QRgbaFloat32 pixels starting from - \a src and stores the result in \a dst. +*/ +template<typename D, typename S> +void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const +{ + Q_ASSERT(colorSpaceIn->isThreeComponentMatrix()); + updateLutsIn(); + if constexpr (std::is_same_v<D, QRgb> || std::is_same_v<D, QRgba64> || std::is_same_v<D, QRgbaFloat32> || std::is_same_v<D, QCmyk32>) { + if (!colorSpaceOut->isThreeComponentMatrix()) { + QUninitialized<QColorVector, WorkBlockSize> buffer; - Thread-safe if prepare() has been called first. + qsizetype i = 0; + while (i < count) { + const qsizetype len = qMin(count - i, WorkBlockSize); + loadGray(buffer, src + i, len, this); - Assumes unpremultiplied data by default. Set \a flags to change defaults. + applyMatrix<DoClamp>(buffer, len, colorMatrix); - \sa prepare() -*/ -void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, - TransformFlags flags) const -{ - apply<QRgbaFloat32>(dst, src, count, 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(); + } + + // Do element based conversion + for (auto &&element : colorSpaceOut->mBA) + std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); + + clampIfNeeded<DoClamp>(buffer, len); + + storeUnpremultipliedLUT(dst, src, buffer, len); // input is always opaque + + i += len; + } + return; + } + } + Q_ASSERT(colorSpaceOut->isThreeComponentMatrix()); + if constexpr (!std::is_same_v<D, QCmyk32>) { + if (!colorMatrix.isValid()) + return; + + updateLutsOut(); + + QUninitialized<QColorVector, WorkBlockSize> buffer; + + qsizetype i = 0; + while (i < count) { + const qsizetype len = qMin(count - i, WorkBlockSize); + loadGray(buffer, src + i, len, this); + + applyMatrix<DoClamp>(buffer, len, colorMatrix); + + storeOpaque(dst + i, buffer, len, this); + i += len; + } + } else { + Q_UNREACHABLE(); + } } /*! \internal - Is to be called on a color-transform to XYZ, returns only luminance values. + \enum QColorTransformPrivate::TransformFlag + Defines how the transform should handle alpha values. + + \value Unpremultiplied The input and output should both be unpremultiplied. + \value InputOpaque The input is guaranteed to be opaque. + \value InputPremultiplied The input is premultiplied. + \value OutputPremultiplied The output should be premultiplied. + \value Premultiplied Both input and output should both be premultiplied. */ -void QColorTransformPrivate::apply(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const -{ - applyReturnGray<quint8, QRgb>(dst, src, count, flags); -} /*! \internal - Is to be called on a color-transform to XYZ, returns only luminance values. + Prepares a color transformation for fast application. You do not need to + call this explicitly as it will be called implicitly on the first transforms, but + if you want predictable performance on the first transforms, you can perform it + in advance. + \sa QColorTransform::map(), apply() */ -void QColorTransformPrivate::apply(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const +void QColorTransformPrivate::prepare() { - applyReturnGray<quint16, QRgba64>(dst, src, count, flags); + updateLutsIn(); + updateLutsOut(); } +// Only allow versions increasing precision +template void QColorTransformPrivate::applyReturnGray<quint8, QRgb>(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyReturnGray<quint8, QCmyk32>(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyReturnGray<quint16, QCmyk32>(quint16 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyReturnGray<quint16, QRgba64>(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyGray<quint8, quint8>(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyGray<quint16, quint8>(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyGray<quint16, quint16>(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyGray<QRgb, quint8>(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyGray<QCmyk32, quint8>(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyGray<QCmyk32, quint16>(QCmyk32 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::applyGray<QRgba64, quint16>(QRgba64 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const; + +template void QColorTransformPrivate::apply<QRgb, QRgb>(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgb, QCmyk32>(QRgb *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QCmyk32, QRgb>(QCmyk32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QCmyk32, QCmyk32>(QCmyk32 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QCmyk32, QRgba64>(QCmyk32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QCmyk32, QRgbaFloat32>(QCmyk32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgba64, QRgb>(QRgba64 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgba64, QCmyk32>(QRgba64 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgba64, QRgba64>(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgbaFloat32, QRgb>(QRgbaFloat32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgbaFloat32, QCmyk32>(QRgbaFloat32 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgbaFloat32, QRgba64>(QRgbaFloat32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const; +template void QColorTransformPrivate::apply<QRgbaFloat32, QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const; + +bool QColorTransformPrivate::isThreeComponentMatrix() const +{ + if (colorSpaceIn && !colorSpaceIn->isThreeComponentMatrix()) + return false; + if (colorSpaceOut && !colorSpaceOut->isThreeComponentMatrix()) + return false; + return true; +} /*! \internal */ bool QColorTransformPrivate::isIdentity() const { + if (colorSpaceIn == colorSpaceOut) + return true; if (!colorMatrix.isIdentity()) return false; if (colorSpaceIn && colorSpaceOut) { + if (colorSpaceIn->equals(colorSpaceOut.constData())) + return true; + if (!isThreeComponentMatrix()) + return false; if (colorSpaceIn->transferFunction != colorSpaceOut->transferFunction) return false; if (colorSpaceIn->transferFunction == QColorSpace::TransferFunction::Custom) { @@ -1341,6 +2001,8 @@ bool QColorTransformPrivate::isIdentity() const && colorSpaceIn->trc[2] == colorSpaceOut->trc[2]; } } else { + if (!isThreeComponentMatrix()) + return false; if (colorSpaceIn && colorSpaceIn->transferFunction != QColorSpace::TransferFunction::Linear) return false; if (colorSpaceOut && colorSpaceOut->transferFunction != QColorSpace::TransferFunction::Linear) |