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>
bb10
Allan Sandfeld Jensen 2022-04-12 11:04:12 +02:00
parent e33a449271
commit e69ebf93ca
4 changed files with 362 additions and 17 deletions

View File

@ -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);

View File

@ -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);
@ -1019,6 +1279,23 @@ void QColorTransformPrivate::apply(QRgba64 *dst, const QRgba64 *src, qsizetype c
apply<QRgba64>(dst, src, count, flags);
}
/*!
\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.

View File

@ -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;

View File

@ -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/");