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
parent
e33a449271
commit
e69ebf93ca
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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/");
|
||||
|
|
|
|||
Loading…
Reference in New Issue