summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2022-04-12 11:04:12 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2022-05-14 01:00:05 +0200
commite69ebf93ca49d2c3de9fc723c744fbcb21709bb3 (patch)
tree547ac5f8acb77fde58eafb09f1e2662c52b743c2
parente33a44927132d3684748ad5236d5b7fc9da334d2 (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.cpp32
-rw-r--r--src/gui/painting/qcolortransform.cpp293
-rw-r--r--src/gui/painting/qcolortransform_p.h3
-rw-r--r--tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp51
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/");