diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-04-12 11:04:12 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-14 01:00:05 +0200 |
commit | e69ebf93ca49d2c3de9fc723c744fbcb21709bb3 (patch) | |
tree | 547ac5f8acb77fde58eafb09f1e2662c52b743c2 | |
parent | e33a44927132d3684748ad5236d5b7fc9da334d2 (diff) |
Add floating point color space conversions
This allows color space conversions that produces values outside the
0.0->1.0 range, which is one of the intended functions of the floating
point image formats.
Change-Id: I63b37b0f6934d4382edafb4709486c785a637c67
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
-rw-r--r-- | src/gui/image/qimage.cpp | 32 | ||||
-rw-r--r-- | src/gui/painting/qcolortransform.cpp | 293 | ||||
-rw-r--r-- | src/gui/painting/qcolortransform_p.h | 3 | ||||
-rw-r--r-- | tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp | 51 |
4 files changed, 362 insertions, 17 deletions
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 3a384660ba..9335c7b034 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -5048,30 +5048,37 @@ void QImage::applyColorTransform(const QColorTransform &transform) return; } QImage::Format oldFormat = format(); - if (depth() > 32) { - if (format() != QImage::Format_RGBX64 && format() != QImage::Format_RGBA64 - && format() != QImage::Format_RGBA64_Premultiplied) - *this = std::move(*this).convertToFormat(QImage::Format_RGBA64); - } else if (format() != QImage::Format_ARGB32 && format() != QImage::Format_RGB32 - && format() != QImage::Format_ARGB32_Premultiplied) { + if (qt_fpColorPrecision(oldFormat)) { + if (oldFormat != QImage::Format_RGBX32FPx4 && oldFormat != QImage::Format_RGBA32FPx4 + && oldFormat != QImage::Format_RGBA32FPx4_Premultiplied) + convertTo(QImage::Format_RGBA32FPx4); + } else if (depth() > 32) { + if (oldFormat != QImage::Format_RGBX64 && oldFormat != QImage::Format_RGBA64 + && oldFormat != QImage::Format_RGBA64_Premultiplied) + convertTo(QImage::Format_RGBA64); + } else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32 + && oldFormat != QImage::Format_ARGB32_Premultiplied) { if (hasAlphaChannel()) - *this = std::move(*this).convertToFormat(QImage::Format_ARGB32); + convertTo(QImage::Format_ARGB32); else - *this = std::move(*this).convertToFormat(QImage::Format_RGB32); + convertTo(QImage::Format_RGB32); } QColorTransformPrivate::TransformFlags flags = QColorTransformPrivate::Unpremultiplied; switch (format()) { case Format_ARGB32_Premultiplied: case Format_RGBA64_Premultiplied: + case Format_RGBA32FPx4_Premultiplied: flags = QColorTransformPrivate::Premultiplied; break; case Format_RGB32: case Format_RGBX64: + case Format_RGBX32FPx4: flags = QColorTransformPrivate::InputOpaque; break; case Format_ARGB32: case Format_RGBA64: + case Format_RGBA32FPx4: break; default: Q_UNREACHABLE(); @@ -5079,7 +5086,14 @@ void QImage::applyColorTransform(const QColorTransform &transform) std::function<void(int,int)> transformSegment; - if (depth() > 32) { + if (qt_fpColorPrecision(format())) { + transformSegment = [&](int yStart, int yEnd) { + for (int y = yStart; y < yEnd; ++y) { + QRgbaFloat32 *scanline = reinterpret_cast<QRgbaFloat32 *>(d->data + y * d->bytes_per_line); + transform.d->apply(scanline, scanline, width(), flags); + } + }; + } else if (depth() > 32) { transformSegment = [&](int yStart, int yEnd) { for (int y = yStart; y < yEnd; ++y) { QRgba64 *scanline = reinterpret_cast<QRgba64 *>(d->data + y * d->bytes_per_line); diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp index fa7cb0e6b4..1545ddda75 100644 --- a/src/gui/painting/qcolortransform.cpp +++ b/src/gui/painting/qcolortransform.cpp @@ -248,6 +248,7 @@ QColor QColorTransform::map(const QColor &color) const // Optimized sub-routines for fast block based conversion: +template<bool DoClamp = true> static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorMatrix &colorMatrix) { #if defined(__SSE2__) @@ -267,8 +268,10 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM cx = _mm_add_ps(cx, cy); cx = _mm_add_ps(cx, cz); // Clamp: - cx = _mm_min_ps(cx, maxV); - cx = _mm_max_ps(cx, minV); + if (DoClamp) { + cx = _mm_min_ps(cx, maxV); + cx = _mm_max_ps(cx, minV); + } _mm_storeu_ps(&buffer[j].x, cx); } #elif defined(__ARM_NEON__) @@ -285,16 +288,22 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM cx = vaddq_f32(cx, cy); cx = vaddq_f32(cx, cz); // Clamp: - cx = vminq_f32(cx, maxV); - cx = vmaxq_f32(cx, minV); + 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) { const QColorVector cv = colorMatrix.map(buffer[j]); - 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::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)); + } else { + buffer[j] = cv; + } } #endif } @@ -385,6 +394,50 @@ 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 viFF00 = _mm_set1_ps(1.0f / (255 * 256)); + const __m128 vZero = _mm_set1_ps(0.0f); + const __m128 vOne = _mm_set1_ps(1.0f); + for (qsizetype i = 0; i < len; ++i) { + __m128 vf = _mm_loadu_ps(&src[i].r); + // Approximate 1/a: + __m128 va = _mm_shuffle_ps(vf, vf, _MM_SHUFFLE(3, 3, 3, 3)); + __m128 via = _mm_rcp_ps(va); + via = _mm_sub_ps(_mm_add_ps(via, via), _mm_mul_ps(via, _mm_mul_ps(via, va))); + // v * (1/a) + vf = _mm_mul_ps(vf, via); + + // Handle zero alpha + __m128 vAlphaMask = _mm_cmpeq_ps(va, vZero); + vf = _mm_andnot_ps(vAlphaMask, vf); + + // LUT + 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)); + const int ridx = _mm_extract_epi16(v, 0); + const int gidx = _mm_extract_epi16(v, 2); + const int bidx = _mm_extract_epi16(v, 4); + v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[0]->m_toLinear[ridx], 0); + v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[1]->m_toLinear[gidx], 2); + v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[2]->m_toLinear[bidx], 4); + vf = _mm_mul_ps(_mm_cvtepi32_ps(v), viFF00); + _mm_storeu_ps(&buffer[i].x, vf); + } else { + // Outside 0.0->1.0 gamut + _mm_storeu_ps(&buffer[i].x, vf); + buffer[i].x = d_ptr->colorSpaceIn->trc[0].applyExtended(buffer[i].x); + buffer[i].y = d_ptr->colorSpaceIn->trc[1].applyExtended(buffer[i].y); + buffer[i].z = d_ptr->colorSpaceIn->trc[2].applyExtended(buffer[i].z); + } + } +} + // Load to [0-4080] in 4x32 SIMD template<typename T> static inline void loadPU(const T &p, __m128i &v); @@ -434,6 +487,37 @@ 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 iFF00 = _mm_set1_ps(1.0f / (255 * 256)); + const __m128 vZero = _mm_set1_ps(0.0f); + const __m128 vOne = _mm_set1_ps(1.0f); + for (qsizetype i = 0; i < len; ++i) { + __m128 vf = _mm_loadu_ps(&src[i].r); + 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)); + const int ridx = _mm_extract_epi16(v, 0); + const int gidx = _mm_extract_epi16(v, 2); + const int bidx = _mm_extract_epi16(v, 4); + v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[0]->m_toLinear[ridx], 0); + v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[1]->m_toLinear[gidx], 2); + v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[2]->m_toLinear[bidx], 4); + vf = _mm_mul_ps(_mm_cvtepi32_ps(v), iFF00); + _mm_storeu_ps(&buffer[i].x, vf); + } else { + // Outside 0.0->1.0 gamut + buffer[i].x = d_ptr->colorSpaceIn->trc[0].applyExtended(src[i].r); + buffer[i].y = d_ptr->colorSpaceIn->trc[1].applyExtended(src[i].g); + buffer[i].z = d_ptr->colorSpaceIn->trc[2].applyExtended(src[i].b); + } + } +} + #elif defined(__ARM_NEON__) // Load to [0-alpha] in 4x32 SIMD template<typename T> @@ -591,6 +675,35 @@ void loadUnpremultiplied<QRgba64>(QColorVector *buffer, const QRgba64 *src, cons } } #endif +#if !defined(__SSE2__) +template<> +void loadPremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len, const QColorTransformPrivate *d_ptr) +{ + for (qsizetype i = 0; i < len; ++i) { + const QRgbaFloat32 &p = src[i]; + const float a = p.a; + if (a) { + const float ia = 1.0f / a; + buffer[i].x = d_ptr->colorSpaceIn->trc[0].applyExtended(p.r * ia); + buffer[i].y = d_ptr->colorSpaceIn->trc[1].applyExtended(p.g * ia); + buffer[i].z = d_ptr->colorSpaceIn->trc[2].applyExtended(p.b * ia); + } else { + buffer[i].x = buffer[i].y = buffer[i].z = 0.0f; + } + } +} + +template<> +void loadUnpremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len, const QColorTransformPrivate *d_ptr) +{ + for (qsizetype i = 0; i < len; ++i) { + const QRgbaFloat32 &p = src[i]; + buffer[i].x = d_ptr->colorSpaceIn->trc[0].applyExtended(p.r); + buffer[i].y = d_ptr->colorSpaceIn->trc[1].applyExtended(p.g); + buffer[i].z = d_ptr->colorSpaceIn->trc[2].applyExtended(p.b); + } +} +#endif #if defined(__SSE2__) template<typename T> @@ -642,6 +755,45 @@ static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, } } +template<> +void storePremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, + const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) +{ + const __m128 v4080 = _mm_set1_ps(4080.f); + 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; + __m128 va = _mm_set1_ps(a); + __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 + va = _mm_mul_ps(va, viFF00); + __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080)); + const int ridx = _mm_extract_epi16(v, 0); + const int gidx = _mm_extract_epi16(v, 2); + const int bidx = _mm_extract_epi16(v, 4); + v = _mm_setzero_si128(); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], 0); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], 2); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], 4); + vf = _mm_mul_ps(_mm_cvtepi32_ps(v), va); + _mm_store_ps(&dst[i].r, vf); + } else { + 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); + vf = _mm_mul_ps(_mm_load_ps(&dst[i].r), va); + _mm_store_ps(&dst[i].r, vf); + } + dst[i].a = a; + } +} + template<typename T> static inline void storePU(T &p, __m128i &v, int a); template<> @@ -681,6 +833,41 @@ static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffe } } +template<> +void storeUnpremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, + const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) +{ + const __m128 v4080 = _mm_set1_ps(4080.f); + 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; + __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)); + const int ridx = _mm_extract_epi16(v, 0); + const int gidx = _mm_extract_epi16(v, 2); + const int bidx = _mm_extract_epi16(v, 4); + v = _mm_setzero_si128(); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], 0); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], 2); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], 4); + vf = _mm_mul_ps(_mm_cvtepi32_ps(v), viFF00); + _mm_storeu_ps(&dst[i].r, vf); + } else { + 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); + } + dst[i].a = a; + } +} + template<typename T> static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) @@ -701,6 +888,42 @@ static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const storePU<T>(dst[i], v, isARGB ? 255 : 0xffff); } } + +template<> +void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, + const QColorVector *buffer, const qsizetype len, + const QColorTransformPrivate *d_ptr) +{ + Q_UNUSED(src); + const __m128 v4080 = _mm_set1_ps(4080.f); + 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) { + __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)); + const int ridx = _mm_extract_epi16(v, 0); + const int gidx = _mm_extract_epi16(v, 2); + const int bidx = _mm_extract_epi16(v, 4); + v = _mm_setzero_si128(); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], 0); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], 2); + v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], 4); + vf = _mm_mul_ps(_mm_cvtepi32_ps(v), viFF00); + _mm_store_ps(&dst[i].r, vf); + } else { + 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); + } + dst[i].a = 1.0f; + } +} + #elif defined(__ARM_NEON__) template<typename T> static inline void storeP(T &p, const uint16x4_t &v); @@ -869,7 +1092,43 @@ 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, + const qsizetype len, const QColorTransformPrivate *d_ptr) +{ + for (qsizetype i = 0; i < len; ++i) { + const float a = src[i].a; + 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; + dst[i].a = a; + } +} + +static void storeUnpremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer, + const qsizetype len, const QColorTransformPrivate *d_ptr) +{ + for (qsizetype i = 0; i < len; ++i) { + const float a = src[i].a; + 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); + dst[i].a = a; + } +} +static void storeOpaque(QRgbaFloat32 *dst, const QRgbaFloat32 *src, 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); + dst[i].b = d_ptr->colorSpaceOut->trc[2].applyInverseExtended(buffer[i].z); + dst[i].a = 1.0f; + } +} +#endif static void storeGray(quint8 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len, const QColorTransformPrivate *d_ptr) { @@ -907,6 +1166,7 @@ void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, Transf updateLutsOut(); bool doApplyMatrix = (colorMatrix != QColorMatrix::identity()); + constexpr bool DoClip = !std::is_same_v<T, QRgbaFloat16> && !std::is_same_v<T, QRgbaFloat32>; QUninitialized<QColorVector, WorkBlockSize> buffer; @@ -919,7 +1179,7 @@ void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, Transf loadUnpremultiplied(buffer, src + i, len, this); if (doApplyMatrix) - applyMatrix(buffer, len, colorMatrix); + applyMatrix<DoClip>(buffer, len, colorMatrix); if (flags & InputOpaque) storeOpaque(dst + i, src + i, buffer, len, this); @@ -1021,6 +1281,23 @@ void QColorTransformPrivate::apply(QRgba64 *dst, const QRgba64 *src, qsizetype c /*! \internal + Applies the color transformation on \a count QRgbaFloat32 pixels starting from + \a src and stores the result in \a dst. + + Thread-safe if prepare() has been called first. + + Assumes unpremultiplied data by default. Set \a flags to change defaults. + + \sa prepare() +*/ +void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, + TransformFlags flags) const +{ + apply<QRgbaFloat32>(dst, src, count, flags); +} + +/*! + \internal Is to be called on a color-transform to XYZ, returns only luminance values. */ diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h index b9099fa399..ab01bb3462 100644 --- a/src/gui/painting/qcolortransform_p.h +++ b/src/gui/painting/qcolortransform_p.h @@ -55,6 +55,7 @@ #include "qcolorspace_p.h" #include <QtCore/qshareddata.h> +#include <QtGui/qrgbafloat.h> QT_BEGIN_NAMESPACE @@ -84,6 +85,8 @@ public: void apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; void apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; + void apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, + TransformFlags flags = Unpremultiplied) const; void apply(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; void apply(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags = Unpremultiplied) const; diff --git a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp index 576885de34..16c2f41d6c 100644 --- a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp +++ b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp @@ -65,6 +65,8 @@ private slots: void imageConversion64PM(); void imageConversionOverLargerGamut_data(); void imageConversionOverLargerGamut(); + void imageConversionOverLargerGamut2_data(); + void imageConversionOverLargerGamut2(); void loadImage(); @@ -489,6 +491,55 @@ void tst_QColorSpace::imageConversionOverLargerGamut() } } +void tst_QColorSpace::imageConversionOverLargerGamut2_data() +{ + QTest::addColumn<QImage::Format>("format"); + + QTest::newRow("rgbx16x4") << QImage::Format_RGBX16FPx4; + QTest::newRow("rgba16x4") << QImage::Format_RGBA16FPx4; + QTest::newRow("rgba16x4PM") << QImage::Format_RGBA16FPx4_Premultiplied; + QTest::newRow("rgbx32x4") << QImage::Format_RGBX32FPx4; + QTest::newRow("rgba32x4") << QImage::Format_RGBA32FPx4; + QTest::newRow("rgba32x4PM") << QImage::Format_RGBA32FPx4_Premultiplied; +} + +void tst_QColorSpace::imageConversionOverLargerGamut2() +{ + QFETCH(QImage::Format, format); + + QColorSpace csfrom = QColorSpace::DisplayP3; + QColorSpace csto = QColorSpace::SRgb; + + QImage testImage(256, 256, format); + testImage.setColorSpace(csfrom); + for (int y = 0; y < 256; ++y) + for (int x = 0; x < 256; ++x) + testImage.setPixel(x, y, qRgba(x, y, 16, 255)); + + QImage resultImage = testImage.convertedToColorSpace(csto); + for (int y = 0; y < 256; ++y) { + float lastRed = -256.0f; + for (int x = 0; x < 256; ++x) { + float pr = resultImage.pixelColor(x, y).redF(); + QVERIFY(pr >= lastRed); + lastRed = pr; + } + } + for (int x = 0; x < 256; ++x) { + float lastGreen = -256.0f; + for (int y = 0; y < 256; ++y) { + float pg = resultImage.pixelColor(x, y).greenF(); + QVERIFY(pg >= lastGreen); + lastGreen = pg; + } + } + // Test colors outside of sRGB are converted to values outside of 0-1 range. + QVERIFY(resultImage.pixelColor(255, 0).redF() > 1.0f); + QVERIFY(resultImage.pixelColor(255, 0).greenF() < 0.0f); + QVERIFY(resultImage.pixelColor(0, 255).redF() < 0.0f); + QVERIFY(resultImage.pixelColor(0, 255).greenF() > 1.0f); +} + void tst_QColorSpace::loadImage() { QString prefix = QFINDTESTDATA("resources/"); |