summaryrefslogtreecommitdiffstats
path: root/src/gui/painting
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/painting')
-rw-r--r--src/gui/painting/qbackingstore.cpp29
-rw-r--r--src/gui/painting/qbackingstorerhisupport.cpp11
-rw-r--r--src/gui/painting/qcmyk_p.h94
-rw-r--r--src/gui/painting/qcolorclut_p.h103
-rw-r--r--src/gui/painting/qcolormatrix_p.h84
-rw-r--r--src/gui/painting/qcolorspace.cpp318
-rw-r--r--src/gui/painting/qcolorspace.h25
-rw-r--r--src/gui/painting/qcolorspace_p.h6
-rw-r--r--src/gui/painting/qcolortransferfunction_p.h62
-rw-r--r--src/gui/painting/qcolortransfergeneric_p.h109
-rw-r--r--src/gui/painting/qcolortransfertable_p.h58
-rw-r--r--src/gui/painting/qcolortransform.cpp926
-rw-r--r--src/gui/painting/qcolortransform_p.h29
-rw-r--r--src/gui/painting/qcolortrc_p.h101
-rw-r--r--src/gui/painting/qcolortrclut.cpp110
-rw-r--r--src/gui/painting/qcolortrclut_p.h88
-rw-r--r--src/gui/painting/qcoregraphics.mm4
-rw-r--r--src/gui/painting/qcoregraphics_p.h12
-rw-r--r--src/gui/painting/qdatabuffer_p.h3
-rw-r--r--src/gui/painting/qdrawhelper.cpp150
-rw-r--r--src/gui/painting/qicc.cpp1055
-rw-r--r--src/gui/painting/qimagescale.cpp2
-rw-r--r--src/gui/painting/qoutlinemapper.cpp2
-rw-r--r--src/gui/painting/qpagelayout.cpp150
-rw-r--r--src/gui/painting/qpagelayout.h13
-rw-r--r--src/gui/painting/qpagesize.h2
-rw-r--r--src/gui/painting/qpaintdevice.cpp35
-rw-r--r--src/gui/painting/qpaintdevice.h16
-rw-r--r--src/gui/painting/qpaintdevice.qdoc20
-rw-r--r--src/gui/painting/qpaintengine_raster.cpp12
-rw-r--r--src/gui/painting/qpainter.cpp9
-rw-r--r--src/gui/painting/qpdf.cpp213
-rw-r--r--src/gui/painting/qpdf_p.h18
-rw-r--r--src/gui/painting/qpixellayout.cpp106
-rw-r--r--src/gui/painting/qplatformbackingstore.cpp65
-rw-r--r--src/gui/painting/qplatformbackingstore.h11
-rw-r--r--src/gui/painting/qregion.cpp64
-rw-r--r--src/gui/painting/qregion.h4
-rw-r--r--src/gui/painting/qrhibackingstore.cpp25
39 files changed, 3040 insertions, 1104 deletions
diff --git a/src/gui/painting/qbackingstore.cpp b/src/gui/painting/qbackingstore.cpp
index 2304ee2256..3b709ec77b 100644
--- a/src/gui/painting/qbackingstore.cpp
+++ b/src/gui/painting/qbackingstore.cpp
@@ -50,6 +50,7 @@ public:
QScopedPointer<QImage> highDpiBackingstore;
QRegion staticContents;
QSize size;
+ QSize nativeSize;
bool downscale = qEnvironmentVariableIntValue("QT_WIDGETS_HIGHDPI_DOWNSCALE") > 0;
};
@@ -115,14 +116,13 @@ QWindow* QBackingStore::window() const
void QBackingStore::beginPaint(const QRegion &region)
{
- const qreal dpr = d_ptr->backingStoreDevicePixelRatio();
+ const qreal toNativeFactor = d_ptr->deviceIndependentToNativeFactor();
- if (d_ptr->highDpiBackingstore &&
- d_ptr->highDpiBackingstore->devicePixelRatio() != dpr)
+ if (d_ptr->nativeSize != QHighDpi::scale(size(), toNativeFactor))
resize(size());
QPlatformBackingStore *platformBackingStore = handle();
- platformBackingStore->beginPaint(QHighDpi::scale(region, d_ptr->deviceIndependentToNativeFactor()));
+ platformBackingStore->beginPaint(QHighDpi::scale(region, toNativeFactor));
// When QtGui is applying a high-dpi scale factor the backing store
// creates a "large" backing store image. This image needs to be
@@ -131,18 +131,20 @@ void QBackingStore::beginPaint(const QRegion &region)
// the image data to avoid having the new devicePixelRatio be propagated
// back to the platform plugin.
QPaintDevice *device = platformBackingStore->paintDevice();
- if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image) {
+ if (!qFuzzyCompare(toNativeFactor, qreal(1)) && device->devType() == QInternal::Image) {
QImage *source = static_cast<QImage *>(device);
const bool needsNewImage = d_ptr->highDpiBackingstore.isNull()
- || source->data_ptr() != d_ptr->highDpiBackingstore->data_ptr()
+ || source->constBits() != d_ptr->highDpiBackingstore->constBits()
|| source->size() != d_ptr->highDpiBackingstore->size()
- || source->devicePixelRatio() != d_ptr->highDpiBackingstore->devicePixelRatio();
- if (needsNewImage) {
+ || source->bytesPerLine() != d_ptr->highDpiBackingstore->bytesPerLine()
+ || source->format() != d_ptr->highDpiBackingstore->format();
+ if (needsNewImage)
d_ptr->highDpiBackingstore.reset(
new QImage(source->bits(), source->width(), source->height(), source->bytesPerLine(), source->format()));
- d_ptr->highDpiBackingstore->setDevicePixelRatio(dpr);
- }
+ d_ptr->highDpiBackingstore->setDevicePixelRatio(d_ptr->backingStoreDevicePixelRatio());
+ } else {
+ d_ptr->highDpiBackingstore.reset();
}
}
@@ -156,7 +158,7 @@ QPaintDevice *QBackingStore::paintDevice()
{
QPaintDevice *device = handle()->paintDevice();
- if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image)
+ if (!qFuzzyCompare(d_ptr->deviceIndependentToNativeFactor(), qreal(1)) && device->devType() == QInternal::Image)
return d_ptr->highDpiBackingstore.data();
return device;
@@ -229,9 +231,10 @@ void QBackingStore::flush(const QRegion &region, QWindow *window, const QPoint &
*/
void QBackingStore::resize(const QSize &size)
{
- d_ptr->size = size;
const qreal factor = d_ptr->deviceIndependentToNativeFactor();
- handle()->resize(QHighDpi::scale(size, factor), QHighDpi::scale(d_ptr->staticContents, factor));
+ d_ptr->size = size;
+ d_ptr->nativeSize = QHighDpi::scale(size, factor);
+ handle()->resize(d_ptr->nativeSize, QHighDpi::scale(d_ptr->staticContents, factor));
}
/*!
diff --git a/src/gui/painting/qbackingstorerhisupport.cpp b/src/gui/painting/qbackingstorerhisupport.cpp
index 630134e564..37c52155eb 100644
--- a/src/gui/painting/qbackingstorerhisupport.cpp
+++ b/src/gui/painting/qbackingstorerhisupport.cpp
@@ -114,8 +114,8 @@ bool QBackingStoreRhiSupport::create()
if (QRhi::probe(QRhi::Metal, &params)) {
rhi = QRhi::create(QRhi::Metal, &params, flags);
} else {
- qCDebug(lcQpaBackingStore, "Metal does not seem to be supported. Falling back to OpenGL.");
- rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
+ qCDebug(lcQpaBackingStore, "Metal does not seem to be supported");
+ return false;
}
}
#endif
@@ -196,13 +196,14 @@ QRhiSwapChain *QBackingStoreRhiSupport::swapChainForWindow(QWindow *window)
bool QBackingStoreRhiSupportWindowWatcher::eventFilter(QObject *obj, QEvent *event)
{
- if (event->type() == QEvent::PlatformSurface
- && static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
+ if (event->type() == QEvent::WindowAboutToChangeInternal
+ || (event->type() == QEvent::PlatformSurface
+ && static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed))
{
QWindow *window = qobject_cast<QWindow *>(obj);
auto it = m_rhiSupport->m_swapchains.find(window);
if (it != m_rhiSupport->m_swapchains.end()) {
- qCDebug(lcQpaBackingStore) << "SurfaceAboutToBeDestroyed received for tracked window" << window << "cleaning up swapchain";
+ qCDebug(lcQpaBackingStore) << event << "received for" << window << "- cleaning up swapchain";
auto data = *it;
m_rhiSupport->m_swapchains.erase(it);
data.reset(); // deletes 'this'
diff --git a/src/gui/painting/qcmyk_p.h b/src/gui/painting/qcmyk_p.h
new file mode 100644
index 0000000000..1294a18244
--- /dev/null
+++ b/src/gui/painting/qcmyk_p.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 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
+
+#ifndef QCMYK_P_H
+#define QCMYK_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/private/qtguiglobal_p.h>
+#include <QtGui/qcolor.h>
+
+QT_BEGIN_NAMESPACE
+
+class QCmyk32
+{
+private:
+ uint m_cmyk = 0;
+ friend constexpr bool comparesEqual(const QCmyk32 &lhs, const QCmyk32 &rhs) noexcept
+ {
+ return lhs.m_cmyk == rhs.m_cmyk;
+ }
+
+public:
+ QCmyk32() = default;
+
+ constexpr QCmyk32(int cyan, int magenta, int yellow, int black) :
+#if QT_BYTE_ORDER == Q_BIG_ENDIAN
+ m_cmyk(cyan << 24 | magenta << 16 | yellow << 8 | black)
+#else
+ m_cmyk(cyan | magenta << 8 | yellow << 16 | black << 24)
+#endif
+ {
+ }
+
+#if QT_BYTE_ORDER == Q_BIG_ENDIAN
+ constexpr int cyan() const noexcept { return (m_cmyk >> 24) & 0xff; }
+ constexpr int magenta() const noexcept { return (m_cmyk >> 16) & 0xff; }
+ constexpr int yellow() const noexcept { return (m_cmyk >> 8) & 0xff; }
+ constexpr int black() const noexcept { return (m_cmyk ) & 0xff; }
+#else
+ constexpr int cyan() const noexcept { return (m_cmyk ) & 0xff; }
+ constexpr int magenta() const noexcept { return (m_cmyk >> 8) & 0xff; }
+ constexpr int yellow() const noexcept { return (m_cmyk >> 16) & 0xff; }
+ constexpr int black() const noexcept { return (m_cmyk >> 24) & 0xff; }
+#endif
+
+ QColor toColor() const noexcept
+ {
+ return QColor::fromCmyk(cyan(), magenta(), yellow(), black());
+ }
+
+ constexpr uint toUint() const noexcept
+ {
+ return m_cmyk;
+ }
+
+ constexpr static QCmyk32 fromCmyk32(uint cmyk) noexcept
+ {
+ QCmyk32 result;
+ result.m_cmyk = cmyk;
+ return result;
+ }
+
+ static QCmyk32 fromRgba(QRgb rgba) noexcept
+ {
+ const QColor c = QColor(rgba).toCmyk();
+ return QCmyk32(c.cyan(), c.magenta(), c.yellow(), c.black());
+ }
+
+ static QCmyk32 fromColor(const QColor &color) noexcept
+ {
+ QColor c = color.toCmyk();
+ return QCmyk32(c.cyan(), c.magenta(), c.yellow(), c.black());
+ }
+
+ Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QCmyk32)
+};
+
+static_assert(sizeof(QCmyk32) == sizeof(int));
+static_assert(alignof(QCmyk32) == alignof(int));
+static_assert(std::is_standard_layout_v<QCmyk32>);
+
+QT_END_NAMESPACE
+
+#endif // QCMYK_P_H
diff --git a/src/gui/painting/qcolorclut_p.h b/src/gui/painting/qcolorclut_p.h
index 81e002d913..9cf8b6f267 100644
--- a/src/gui/painting/qcolorclut_p.h
+++ b/src/gui/painting/qcolorclut_p.h
@@ -20,7 +20,7 @@
QT_BEGIN_NAMESPACE
-// A 3-dimensional lookup table compatible with ICC lut8, lut16, mAB, and mBA formats.
+// A 3/4-dimensional lookup table compatible with ICC lut8, lut16, mAB, and mBA formats.
class QColorCLUT
{
inline static QColorVector interpolate(const QColorVector &a, const QColorVector &b, float t)
@@ -32,45 +32,92 @@ class QColorCLUT
a += (b - a) * t;
}
public:
- qsizetype gridPointsX = 0;
- qsizetype gridPointsY = 0;
- qsizetype gridPointsZ = 0;
+ uint32_t gridPointsX = 0;
+ uint32_t gridPointsY = 0;
+ uint32_t gridPointsZ = 0;
+ uint32_t gridPointsW = 1;
QList<QColorVector> table;
bool isEmpty() const { return table.isEmpty(); }
QColorVector apply(const QColorVector &v) const
{
- Q_ASSERT(table.size() == gridPointsX * gridPointsY * gridPointsZ);
+ Q_ASSERT(table.size() == qsizetype(gridPointsX * gridPointsY * gridPointsZ * gridPointsW));
+ QColorVector frac;
const float x = std::clamp(v.x, 0.0f, 1.0f) * (gridPointsX - 1);
const float y = std::clamp(v.y, 0.0f, 1.0f) * (gridPointsY - 1);
const float z = std::clamp(v.z, 0.0f, 1.0f) * (gridPointsZ - 1);
- // Variables for trilinear interpolation
- const qsizetype lox = static_cast<qsizetype>(std::floor(x));
- const qsizetype hix = std::min(lox + 1, gridPointsX - 1);
- const qsizetype loy = static_cast<qsizetype>(std::floor(y));
- const qsizetype hiy = std::min(loy + 1, gridPointsY - 1);
- const qsizetype loz = static_cast<qsizetype>(std::floor(z));
- const qsizetype hiz = std::min(loz + 1, gridPointsZ - 1);
- const float fracx = x - static_cast<float>(lox);
- const float fracy = y - static_cast<float>(loy);
- const float fracz = z - static_cast<float>(loz);
- QColorVector tmp[4];
- auto index = [&](qsizetype x, qsizetype y, qsizetype z) { return x * gridPointsZ * gridPointsY + y * gridPointsZ + z; };
+ const float w = std::clamp(v.w, 0.0f, 1.0f) * (gridPointsW - 1);
+ const uint32_t lox = static_cast<uint32_t>(std::floor(x));
+ const uint32_t hix = std::min(lox + 1, gridPointsX - 1);
+ const uint32_t loy = static_cast<uint32_t>(std::floor(y));
+ const uint32_t hiy = std::min(loy + 1, gridPointsY - 1);
+ const uint32_t loz = static_cast<uint32_t>(std::floor(z));
+ const uint32_t hiz = std::min(loz + 1, gridPointsZ - 1);
+ const uint32_t low = static_cast<uint32_t>(std::floor(w));
+ const uint32_t hiw = std::min(low + 1, gridPointsW - 1);
+ frac.x = x - static_cast<float>(lox);
+ frac.y = y - static_cast<float>(loy);
+ frac.z = z - static_cast<float>(loz);
+ frac.w = w - static_cast<float>(low);
+ if (gridPointsW > 1) {
+ auto index = [&](qsizetype x, qsizetype y, qsizetype z, qsizetype w) -> qsizetype {
+ return x * gridPointsW * gridPointsZ * gridPointsY
+ + y * gridPointsW * gridPointsZ
+ + z * gridPointsW
+ + w;
+ };
+ QColorVector tmp[8];
+ // interpolate over w
+ tmp[0] = interpolate(table[index(lox, loy, loz, low)],
+ table[index(lox, loy, loz, hiw)], frac.w);
+ tmp[1] = interpolate(table[index(lox, loy, hiz, low)],
+ table[index(lox, loy, hiz, hiw)], frac.w);
+ tmp[2] = interpolate(table[index(lox, hiy, loz, low)],
+ table[index(lox, hiy, loz, hiw)], frac.w);
+ tmp[3] = interpolate(table[index(lox, hiy, hiz, low)],
+ table[index(lox, hiy, hiz, hiw)], frac.w);
+ tmp[4] = interpolate(table[index(hix, loy, loz, low)],
+ table[index(hix, loy, loz, hiw)], frac.w);
+ tmp[5] = interpolate(table[index(hix, loy, hiz, low)],
+ table[index(hix, loy, hiz, hiw)], frac.w);
+ tmp[6] = interpolate(table[index(hix, hiy, loz, low)],
+ table[index(hix, hiy, loz, hiw)], frac.w);
+ tmp[7] = interpolate(table[index(hix, hiy, hiz, low)],
+ table[index(hix, hiy, hiz, hiw)], frac.w);
+ // interpolate over z
+ for (int i = 0; i < 4; ++i)
+ interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z);
+ // interpolate over y
+ for (int i = 0; i < 2; ++i)
+ interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y);
+ // interpolate over x
+ interpolateIn(tmp[0], tmp[4], frac.x);
+ return tmp[0];
+ }
+ auto index = [&](qsizetype x, qsizetype y, qsizetype z) -> qsizetype {
+ return x * gridPointsZ * gridPointsY
+ + y * gridPointsZ
+ + z;
+ };
+ QColorVector tmp[8] = {
+ table[index(lox, loy, loz)],
+ table[index(lox, loy, hiz)],
+ table[index(lox, hiy, loz)],
+ table[index(lox, hiy, hiz)],
+ table[index(hix, loy, loz)],
+ table[index(hix, loy, hiz)],
+ table[index(hix, hiy, loz)],
+ table[index(hix, hiy, hiz)]
+ };
// interpolate over z
- tmp[0] = interpolate(table[index(lox, loy, loz)],
- table[index(lox, loy, hiz)], fracz);
- tmp[1] = interpolate(table[index(lox, hiy, loz)],
- table[index(lox, hiy, hiz)], fracz);
- tmp[2] = interpolate(table[index(hix, loy, loz)],
- table[index(hix, loy, hiz)], fracz);
- tmp[3] = interpolate(table[index(hix, hiy, loz)],
- table[index(hix, hiy, hiz)], fracz);
+ for (int i = 0; i < 4; ++i)
+ interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z);
// interpolate over y
- interpolateIn(tmp[0], tmp[1], fracy);
- interpolateIn(tmp[2], tmp[3], fracy);
+ for (int i = 0; i < 2; ++i)
+ interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y);
// interpolate over x
- interpolateIn(tmp[0], tmp[2], fracx);
+ interpolateIn(tmp[0], tmp[4], frac.x);
return tmp[0];
}
};
diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h
index de6a1dddef..0c85222a9a 100644
--- a/src/gui/painting/qcolormatrix_p.h
+++ b/src/gui/painting/qcolormatrix_p.h
@@ -28,20 +28,17 @@ class QColorVector
{
public:
QColorVector() = default;
- constexpr QColorVector(float x, float y, float z) : x(x), y(y), z(z) { }
- explicit constexpr QColorVector(const QPointF &chr) // from XY chromaticity
- : x(chr.x() / chr.y())
- , y(1.0f)
- , z((1.0f - chr.x() - chr.y()) / chr.y())
- { }
- float x = 0.0f; // X, x, L, or red
- float y = 0.0f; // Y, y, a, or green
- float z = 0.0f; // Z, Y, b, or blue
- float _unused = 0.0f;
+ constexpr QColorVector(float x, float y, float z, float w = 0.0f) noexcept : x(x), y(y), z(z), w(w) { }
+ static constexpr QColorVector fromXYChromaticity(QPointF chr)
+ { return {float(chr.x() / chr.y()), 1.0f, float((1.0f - chr.x() - chr.y()) / chr.y())}; }
+ float x = 0.0f; // X, x, L, or red/cyan
+ float y = 0.0f; // Y, y, a, or green/magenta
+ float z = 0.0f; // Z, Y, b, or blue/yellow
+ float w = 0.0f; // unused, or black
constexpr bool isNull() const noexcept
{
- return !x && !y && !z;
+ return !x && !y && !z && !w;
}
bool isValid() const noexcept
{
@@ -59,16 +56,24 @@ public:
return true;
}
- constexpr QColorVector operator*(float f) const { return QColorVector(x * f, y * f, z * f); }
- constexpr QColorVector operator+(const QColorVector &v) const { return QColorVector(x + v.x, y + v.y, z + v.z); }
- constexpr QColorVector operator-(const QColorVector &v) const { return QColorVector(x - v.x, y - v.y, z - v.z); }
- void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; }
+ constexpr QColorVector operator*(float f) const { return QColorVector(x * f, y * f, z * f, w * f); }
+ constexpr QColorVector operator+(const QColorVector &v) const { return QColorVector(x + v.x, y + v.y, z + v.z, w + v.w); }
+ constexpr QColorVector operator-(const QColorVector &v) const { return QColorVector(x - v.x, y - v.y, z - v.z, w - v.w); }
+ void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; w += v.w; }
+
+ QPointF toChromaticity() const
+ {
+ if (isNull())
+ return QPointF();
+ float mag = 1.0f / (x + y + z);
+ return QPointF(x * mag, y * mag);
+ }
// Common whitepoints:
static constexpr QPointF D50Chromaticity() { return QPointF(0.34567, 0.35850); }
static constexpr QPointF D65Chromaticity() { return QPointF(0.31271, 0.32902); }
- static constexpr QColorVector D50() { return QColorVector(D50Chromaticity()); }
- static constexpr QColorVector D65() { return QColorVector(D65Chromaticity()); }
+ static constexpr QColorVector D50() { return fromXYChromaticity(D50Chromaticity()); }
+ static constexpr QColorVector D65() { return fromXYChromaticity(D65Chromaticity()); }
QColorVector xyzToLab() const
{
@@ -103,11 +108,11 @@ public:
#else
v = _mm_or_ps(_mm_and_ps(cmpgt, est), _mm_andnot_ps(cmpgt, kapmul));
#endif
- QColorVector out;
- _mm_storeu_ps(&out.x, v);
- const float L = 116.f * out.y - 16.f;
- const float a = 500.f * (out.x - out.y);
- const float b = 200.f * (out.y - out.z);
+ alignas(16) float out[4];
+ _mm_store_ps(out, v);
+ const float L = 116.f * out[1] - 16.f;
+ const float a = 500.f * (out[0] - out[1]);
+ const float b = 200.f * (out[1] - out[2]);
#else
float xr = x * (1.f / ref.x);
float yr = y * (1.f / ref.y);
@@ -190,7 +195,8 @@ inline bool comparesEqual(const QColorVector &v1, const QColorVector &v2)
{
return (std::abs(v1.x - v2.x) < (1.0f / 2048.0f))
&& (std::abs(v1.y - v2.y) < (1.0f / 2048.0f))
- && (std::abs(v1.z - v2.z) < (1.0f / 2048.0f));
+ && (std::abs(v1.z - v2.z) < (1.0f / 2048.0f))
+ && (std::abs(v1.w - v2.w) < (1.0f / 2048.0f));
}
// A matrix mapping 3 value colors.
@@ -279,6 +285,32 @@ public:
{ 0.0f, v.y, 0.0f },
{ 0.0f, 0.0f, v.z } };
}
+ static QColorMatrix chromaticAdaptation(const QColorVector &whitePoint)
+ {
+ constexpr QColorVector whitePointD50 = QColorVector::D50();
+ if (whitePoint != whitePointD50) {
+ // A chromatic adaptation to map a white point to XYZ D50.
+
+ // The Bradford method chromatic adaptation matrix:
+ const QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
+ { 0.2664f, 1.7135f, -0.0685f },
+ { -0.1614f, 0.0367f, 1.0296f } };
+ const QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
+ { -0.1470543f, 0.5183603f, 0.0400428f },
+ { 0.1599627f, 0.0492912f, 0.9684867f } };
+
+ const QColorVector srcCone = abrad.map(whitePoint);
+ if (srcCone.x && srcCone.y && srcCone.z) {
+ const QColorVector dstCone = abrad.map(whitePointD50);
+ const QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
+ { 0, dstCone.y / srcCone.y, 0 },
+ { 0, 0, dstCone.z / srcCone.z } };
+ return abradinv * (wToD50 * abrad);
+ }
+ }
+ return QColorMatrix::identity();
+ }
+
// These are used to recognize matrices from ICC profiles:
static QColorMatrix toXyzFromSRgb()
{
@@ -304,6 +336,12 @@ public:
{ 0.1351922452f, 0.7118769884f, 0.0000000000f },
{ 0.0313525312f, 0.0000856627f, 0.8251883388f } };
}
+ static QColorMatrix toXyzFromBt2020()
+ {
+ return QColorMatrix { { 0.673447f, 0.279037f, -0.00192261f },
+ { 0.165665f, 0.675339f, 0.0299835f },
+ { 0.125092f, 0.0456238f, 0.797134f } };
+ }
friend inline bool comparesEqual(const QColorMatrix &lhs, const QColorMatrix &rhs);
Q_DECLARE_EQUALITY_COMPARABLE(QColorMatrix);
};
diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp
index 334587fd31..3d763d577c 100644
--- a/src/gui/painting/qcolorspace.cpp
+++ b/src/gui/painting/qcolorspace.cpp
@@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE
Q_CONSTINIT QBasicMutex QColorSpacePrivate::s_lutWriteLock;
-Q_CONSTINIT static QAtomicPointer<QColorSpacePrivate> s_predefinedColorspacePrivates[QColorSpace::ProPhotoRgb] = {};
+Q_CONSTINIT static QAtomicPointer<QColorSpacePrivate> s_predefinedColorspacePrivates[QColorSpace::Bt2100Hlg] = {};
static void cleanupPredefinedColorspaces()
{
for (QAtomicPointer<QColorSpacePrivate> &ptr : s_predefinedColorspacePrivates) {
@@ -60,6 +60,12 @@ QColorSpacePrimaries::QColorSpacePrimaries(QColorSpace::Primaries primaries)
bluePoint = QPointF(0.0366, 0.0001);
whitePoint = QColorVector::D50Chromaticity();
break;
+ case QColorSpace::Primaries::Bt2020:
+ redPoint = QPointF(0.708, 0.292);
+ greenPoint = QPointF(0.170, 0.797);
+ bluePoint = QPointF(0.131, 0.046);
+ whitePoint = QColorVector::D65Chromaticity();
+ break;
default:
Q_UNREACHABLE();
}
@@ -81,49 +87,19 @@ bool QColorSpacePrimaries::areValid() const
QColorMatrix QColorSpacePrimaries::toXyzMatrix() const
{
// This converts to XYZ in some undefined scale.
- QColorMatrix toXyz = { QColorVector(redPoint),
- QColorVector(greenPoint),
- QColorVector(bluePoint) };
+ QColorMatrix toXyz = { QColorVector::fromXYChromaticity(redPoint),
+ QColorVector::fromXYChromaticity(greenPoint),
+ QColorVector::fromXYChromaticity(bluePoint) };
// Since the white point should be (1.0, 1.0, 1.0) in the
// input, we can figure out the scale by using the
// inverse conversion on the white point.
- QColorVector wXyz(whitePoint);
+ const auto wXyz = QColorVector::fromXYChromaticity(whitePoint);
QColorVector whiteScale = toXyz.inverted().map(wXyz);
// Now we have scaled conversion to XYZ relative to the given whitepoint
toXyz = toXyz * QColorMatrix::fromScale(whiteScale);
- // But we want a conversion to XYZ relative to D50
- QColorVector wXyzD50 = QColorVector::D50();
-
- if (wXyz != wXyzD50) {
- // Do chromatic adaptation to map our white point to XYZ D50.
-
- // The Bradford method chromatic adaptation matrix:
- QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
- { 0.2664f, 1.7135f, -0.0685f },
- { -0.1614f, 0.0367f, 1.0296f } };
- QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
- { -0.1470543f, 0.5183603f, 0.0400428f },
- { 0.1599627f, 0.0492912f, 0.9684867f } };
-
- QColorVector srcCone = abrad.map(wXyz);
- QColorVector dstCone = abrad.map(wXyzD50);
-
- if (srcCone.x && srcCone.y && srcCone.z) {
- QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
- { 0, dstCone.y / srcCone.y, 0 },
- { 0, 0, dstCone.z / srcCone.z } };
-
-
- QColorMatrix chromaticAdaptation = abradinv * (wToD50 * abrad);
- toXyz = chromaticAdaptation * toXyz;
- } else {
- toXyz.r = {0, 0, 0}; // set to invalid value
- }
- }
-
return toXyz;
}
@@ -133,6 +109,7 @@ QColorSpacePrivate::QColorSpacePrivate()
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace)
: namedColorSpace(namedColorSpace)
+ , colorModel(QColorSpace::ColorModel::Rgb)
{
switch (namedColorSpace) {
case QColorSpace::SRgb:
@@ -161,6 +138,21 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSp
transferFunction = QColorSpace::TransferFunction::ProPhotoRgb;
description = QStringLiteral("ProPhoto RGB");
break;
+ case QColorSpace::Bt2020:
+ primaries = QColorSpace::Primaries::Bt2020;
+ transferFunction = QColorSpace::TransferFunction::Bt2020;
+ description = QStringLiteral("BT.2020");
+ break;
+ case QColorSpace::Bt2100Pq:
+ primaries = QColorSpace::Primaries::Bt2020;
+ transferFunction = QColorSpace::TransferFunction::St2084;
+ description = QStringLiteral("BT.2100(PQ)");
+ break;
+ case QColorSpace::Bt2100Hlg:
+ primaries = QColorSpace::Primaries::Bt2020;
+ transferFunction = QColorSpace::TransferFunction::Hlg;
+ description = QStringLiteral("BT.2100(HLG)");
+ break;
default:
Q_UNREACHABLE();
}
@@ -170,6 +162,7 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSp
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma)
: primaries(primaries)
, transferFunction(transferFunction)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(gamma)
{
identifyColorSpace();
@@ -181,18 +174,50 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
float gamma)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(transferFunction)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(gamma)
+ , whitePoint(QColorVector::fromXYChromaticity(primaries.whitePoint))
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
+
identifyColorSpace();
setTransferFunction();
}
+QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint,
+ QColorSpace::TransferFunction transferFunction,
+ float gamma)
+ : primaries(QColorSpace::Primaries::Custom)
+ , transferFunction(transferFunction)
+ , colorModel(QColorSpace::ColorModel::Gray)
+ , gamma(gamma)
+ , whitePoint(QColorVector::fromXYChromaticity(whitePoint))
+{
+ chad = QColorMatrix::chromaticAdaptation(this->whitePoint);
+ toXyz = chad;
+ setTransferFunction();
+}
+
+QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable)
+ : primaries(QColorSpace::Primaries::Custom)
+ , transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Gray)
+ , gamma(0)
+ , whitePoint(QColorVector::fromXYChromaticity(whitePoint))
+{
+ chad = QColorMatrix::chromaticAdaptation(this->whitePoint);
+ toXyz = chad;
+ setTransferFunctionTable(transferFunctionTable);
+ setTransferFunction();
+}
+
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const QList<uint16_t> &transferFunctionTable)
: primaries(primaries)
, transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(0)
{
setTransferFunctionTable(transferFunctionTable);
@@ -203,11 +228,14 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const Q
QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, const QList<uint16_t> &transferFunctionTable)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(0)
+ , whitePoint(QColorVector::fromXYChromaticity(primaries.whitePoint))
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
setTransferFunctionTable(transferFunctionTable);
identifyColorSpace();
initialize();
@@ -219,16 +247,18 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
const QList<uint16_t> &blueTransferFunctionTable)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(0)
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
+ whitePoint = QColorVector::fromXYChromaticity(primaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
setTransferFunctionTables(redTransferFunctionTable,
greenTransferFunctionTable,
blueTransferFunctionTable);
identifyColorSpace();
- setToXyzMatrix();
}
void QColorSpacePrivate::identifyColorSpace()
@@ -283,6 +313,26 @@ void QColorSpacePrivate::identifyColorSpace()
}
}
break;
+ case QColorSpace::Primaries::Bt2020:
+ if (transferFunction == QColorSpace::TransferFunction::Bt2020) {
+ namedColorSpace = QColorSpace::Bt2020;
+ if (description.isEmpty())
+ description = QStringLiteral("BT.2020");
+ return;
+ }
+ if (transferFunction == QColorSpace::TransferFunction::St2084) {
+ namedColorSpace = QColorSpace::Bt2100Pq;
+ if (description.isEmpty())
+ description = QStringLiteral("BT.2100(PQ)");
+ return;
+ }
+ if (transferFunction == QColorSpace::TransferFunction::Hlg) {
+ namedColorSpace = QColorSpace::Bt2100Hlg;
+ if (description.isEmpty())
+ description = QStringLiteral("BT.2100(HLG)");
+ return;
+ }
+ break;
default:
break;
}
@@ -305,7 +355,9 @@ void QColorSpacePrivate::setToXyzMatrix()
}
QColorSpacePrimaries colorSpacePrimaries(primaries);
toXyz = colorSpacePrimaries.toXyzMatrix();
- whitePoint = QColorVector(colorSpacePrimaries.whitePoint);
+ whitePoint = QColorVector::fromXYChromaticity(colorSpacePrimaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
}
void QColorSpacePrivate::setTransferFunctionTable(const QList<uint16_t> &transferFunctionTable)
@@ -326,7 +378,7 @@ void QColorSpacePrivate::setTransferFunctionTable(const QList<uint16_t> &transfe
} else if (curve.isSRgb()) {
transferFunction = QColorSpace::TransferFunction::SRgb;
}
- trc[0].m_type = QColorTrc::Type::Function;
+ trc[0].m_type = QColorTrc::Type::ParameterizedFunction;
trc[0].m_fun = curve;
} else {
trc[0].m_type = QColorTrc::Type::Table;
@@ -352,21 +404,21 @@ void QColorSpacePrivate::setTransferFunctionTables(const QList<uint16_t> &redTra
transferFunction = QColorSpace::TransferFunction::Custom;
QColorTransferFunction curve;
if (redTable.asColorTransferFunction(&curve)) {
- trc[0].m_type = QColorTrc::Type::Function;
+ trc[0].m_type = QColorTrc::Type::ParameterizedFunction;
trc[0].m_fun = curve;
} else {
trc[0].m_type = QColorTrc::Type::Table;
trc[0].m_table = redTable;
}
if (greenTable.asColorTransferFunction(&curve)) {
- trc[1].m_type = QColorTrc::Type::Function;
+ trc[1].m_type = QColorTrc::Type::ParameterizedFunction;
trc[1].m_fun = curve;
} else {
trc[1].m_type = QColorTrc::Type::Table;
trc[1].m_table = greenTable;
}
if (blueTable.asColorTransferFunction(&curve)) {
- trc[2].m_type = QColorTrc::Type::Function;
+ trc[2].m_type = QColorTrc::Type::ParameterizedFunction;
trc[2].m_fun = curve;
} else {
trc[2].m_type = QColorTrc::Type::Table;
@@ -379,27 +431,34 @@ void QColorSpacePrivate::setTransferFunction()
{
switch (transferFunction) {
case QColorSpace::TransferFunction::Linear:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction();
+ trc[0] = QColorTransferFunction();
if (qFuzzyIsNull(gamma))
gamma = 1.0f;
break;
case QColorSpace::TransferFunction::Gamma:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction::fromGamma(gamma);
+ trc[0] = QColorTransferFunction::fromGamma(gamma);
break;
case QColorSpace::TransferFunction::SRgb:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction::fromSRgb();
+ trc[0] = QColorTransferFunction::fromSRgb();
if (qFuzzyIsNull(gamma))
gamma = 2.31f;
break;
case QColorSpace::TransferFunction::ProPhotoRgb:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction::fromProPhotoRgb();
+ trc[0] = QColorTransferFunction::fromProPhotoRgb();
if (qFuzzyIsNull(gamma))
gamma = 1.8f;
break;
+ case QColorSpace::TransferFunction::Bt2020:
+ trc[0] = QColorTransferFunction::fromBt2020();
+ if (qFuzzyIsNull(gamma))
+ gamma = 2.1f;
+ break;
+ case QColorSpace::TransferFunction::St2084:
+ trc[0] = QColorTransferGenericFunction::pq();
+ break;
+ case QColorSpace::TransferFunction::Hlg:
+ trc[0] = QColorTransferGenericFunction::hlg();
+ break;
case QColorSpace::TransferFunction::Custom:
break;
default:
@@ -441,6 +500,9 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const
ptr->colorMatrix = toXyz;
else
ptr->colorMatrix = QColorMatrix::identity();
+ // Convert to XYZ relative to our white point, not the regular D50 white point.
+ if (!chad.isNull())
+ ptr->colorMatrix = chad.inverted() * ptr->colorMatrix;
return transform;
}
@@ -456,6 +518,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
Q_ASSERT(transferFunction == QColorSpace::TransferFunction::Custom);
transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
+ colorModel = QColorSpace::ColorModel::Rgb;
isPcsLab = false;
mAB.clear();
mBA.clear();
@@ -483,9 +546,10 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
A color space can generally speaking be conceived as a combination of set of primary
colors and a transfer function. The primaries defines the axes of the color space, and
the transfer function how values are mapped on the axes.
- The primaries are defined by three primary colors that represent exactly how red, green,
- and blue look in this particular color space, and a white color that represents where
- and how bright pure white is. The range of colors expressible by the primary colors is
+ The primaries are for ColorModel::Rgb color spaces defined by three primary colors that
+ represent exactly how red, green, and blue look in this particular color space, and a white
+ color that represents where and how bright pure white is. For grayscale color spaces, only
+ a single white primary is needed. The range of colors expressible by the primary colors is
called the gamut, and a color space that can represent a wider range of colors is also
known as a wide-gamut color space.
@@ -512,6 +576,13 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\l{http://www.color.org/chardata/rgb/DCIP3.xalter}{ICC registration of DCI-P3}
\value ProPhotoRgb The Pro Photo RGB color space, also known as ROMM RGB is a very wide gamut color space.
\l{http://www.color.org/chardata/rgb/rommrgb.xalter}{ICC registration of ROMM RGB}
+ \value [since 6.8] Bt2020 BT.2020, also known as Rec.2020 is a basic colorspace of HDR TVs.
+ \l{http://www.color.org/chardata/rgb/BT2020.xalter}{ICC registration of BT.2020}
+ \value [since 6.8] Bt2100Pq BT.2100(PQ), also known as Rec.2100 or HDR10 is an HDR encoding with the same
+ primaries as Bt2020 but using the Perceptual Quantizer transfer function.
+ \l{http://www.color.org/chardata/rgb/BT2100.xalter}{ICC registration of BT.2100}
+ \value [since 6.8] Bt2100Hlg BT.2100 (HLG) is an HDR encoding with the same
+ primaries as Bt2020 but using the Hybrid Log-Gamma transfer function.
*/
/*!
@@ -524,6 +595,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\value AdobeRgb The Adobe RGB primaries
\value DciP3D65 The DCI-P3 primaries with the D65 whitepoint
\value ProPhotoRgb The ProPhoto RGB primaries with the D50 whitepoint
+ \value [since 6.8] Bt2020 The BT.2020 primaries with a D65 whitepoint
*/
/*!
@@ -536,6 +608,10 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\value Gamma A transfer function that is a real gamma curve based on the value of gamma()
\value SRgb The sRGB transfer function, composed of linear and gamma parts
\value ProPhotoRgb The ProPhoto RGB transfer function, composed of linear and gamma parts
+ \value [since 6.8] Bt2020 The BT.2020 transfer function, composited of linear and gamma parts
+ \value [since 6.8] St2084 The SMPTE ST 2084 transfer function, also known Perceptual Quantizer(PQ).
+ \value [since 6.8] Hlg The Hybrid log-gamma transfer function.
+
*/
/*!
@@ -555,6 +631,19 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
*/
/*!
+ \enum QColorSpace::ColorModel
+ \since 6.8
+
+ Defines the color model used by the color space data.
+
+ \value Undefined No color model
+ \value Rgb An RGB color model with red, green, and blue colors. Can apply to RGB and grayscale data.
+ \value Gray A gray scale color model. Can only apply to grayscale data.
+ \value Cmyk Can only represent color data defined with cyan, magenta, yellow, and black colors.
+ In effect only QImage::Format_CMYK32. Note Cmyk color spaces will be TransformModel::ElementListProcessing.
+*/
+
+/*!
\fn QColorSpace::QColorSpace()
Creates a new colorspace object that represents an undefined and invalid colorspace.
@@ -565,7 +654,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
*/
QColorSpace::QColorSpace(NamedColorSpace namedColorSpace)
{
- if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::ProPhotoRgb) {
+ if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::Bt2100Hlg) {
qWarning() << "QColorSpace attempted constructed from invalid QColorSpace::NamedColorSpace: " << int(namedColorSpace);
return;
}
@@ -617,6 +706,28 @@ QColorSpace::QColorSpace(QColorSpace::Primaries gamut, const QList<uint16_t> &tr
}
/*!
+ Creates a custom grayscale color space with the white point \a whitePoint, using the transfer function \a transferFunction and
+ optionally \a gamma.
+
+ \since 6.8
+*/
+QColorSpace::QColorSpace(const QPointF &whitePoint, TransferFunction transferFunction, float gamma)
+ : d_ptr(new QColorSpacePrivate(whitePoint, transferFunction, gamma))
+{
+}
+
+/*!
+ Creates a custom grayscale color space with white point \a whitePoint, and using the custom transfer function described by
+ \a transferFunctionTable.
+
+ \since 6.8
+*/
+QColorSpace::QColorSpace(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable)
+ : d_ptr(new QColorSpacePrivate(whitePoint, transferFunctionTable))
+{
+}
+
+/*!
Creates a custom colorspace with a primaries based on the chromaticities of the primary colors \a whitePoint,
\a redPoint, \a greenPoint and \a bluePoint, and using the transfer function \a transferFunction and optionally \a gamma.
*/
@@ -869,6 +980,7 @@ void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId)
d_ptr->iccProfile = {};
d_ptr->description = QString();
d_ptr->primaries = primariesId;
+ d_ptr->colorModel = QColorSpace::ColorModel::Rgb;
d_ptr->identifyColorSpace();
d_ptr->setToXyzMatrix();
}
@@ -890,7 +1002,10 @@ void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoin
return;
}
QColorMatrix toXyz = primaries.toXyzMatrix();
- if (QColorVector(primaries.whitePoint) == d_ptr->whitePoint && toXyz == d_ptr->toXyz)
+ QColorMatrix chad = QColorMatrix::chromaticAdaptation(QColorVector::fromXYChromaticity(whitePoint));
+ toXyz = chad * toXyz;
+ if (QColorVector::fromXYChromaticity(primaries.whitePoint) == d_ptr->whitePoint
+ && toXyz == d_ptr->toXyz && chad == d_ptr->chad)
return;
detach();
if (d_ptr->transformModel == TransformModel::ElementListProcessing)
@@ -898,14 +1013,68 @@ void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoin
d_ptr->iccProfile = {};
d_ptr->description = QString();
d_ptr->primaries = QColorSpace::Primaries::Custom;
+ d_ptr->colorModel = QColorSpace::ColorModel::Rgb;
d_ptr->toXyz = toXyz;
- d_ptr->whitePoint = QColorVector(primaries.whitePoint);
+ d_ptr->chad = chad;
+ d_ptr->whitePoint = QColorVector::fromXYChromaticity(primaries.whitePoint);
d_ptr->identifyColorSpace();
}
/*!
+ Returns the white point used for this color space. Returns a null QPointF if not defined.
+
+ \since 6.8
+*/
+QPointF QColorSpace::whitePoint() const
+{
+ if (Q_UNLIKELY(!d_ptr))
+ return QPointF();
+ return d_ptr->whitePoint.toChromaticity();
+}
+
+/*!
+ Sets the white point to used for this color space to \a whitePoint.
+
\since 6.8
+*/
+void QColorSpace::setWhitePoint(const QPointF &whitePoint)
+{
+ if (Q_UNLIKELY(!d_ptr)) {
+ d_ptr = new QColorSpacePrivate(whitePoint, TransferFunction::Custom, 0.0f);
+ return;
+ }
+ if (QColorVector::fromXYChromaticity(whitePoint) == d_ptr->whitePoint)
+ return;
+ detach();
+ if (d_ptr->transformModel == TransformModel::ElementListProcessing)
+ d_ptr->clearElementListProcessingForEdit();
+ d_ptr->iccProfile = {};
+ d_ptr->description = QString();
+ d_ptr->primaries = QColorSpace::Primaries::Custom;
+ // An RGB color model stays RGB, a gray stays gray, but an undefined one can now be considered gray
+ if (d_ptr->colorModel == QColorSpace::ColorModel::Undefined)
+ d_ptr->colorModel = QColorSpace::ColorModel::Gray;
+ QColorVector wXyz(QColorVector::fromXYChromaticity(whitePoint));
+ if (d_ptr->transformModel == QColorSpace::TransformModel::ThreeComponentMatrix) {
+ if (d_ptr->colorModel == QColorSpace::ColorModel::Rgb) {
+ // Rescale toXyz to new whitepoint
+ QColorMatrix rawToXyz = d_ptr->chad.inverted() * d_ptr->toXyz;
+ QColorVector whiteScale = rawToXyz.inverted().map(wXyz);
+ rawToXyz = rawToXyz * QColorMatrix::fromScale(whiteScale);
+ d_ptr->chad = QColorMatrix::chromaticAdaptation(wXyz);
+ d_ptr->toXyz = d_ptr->chad * rawToXyz;
+ } else if (d_ptr->colorModel == QColorSpace::ColorModel::Gray) {
+ d_ptr->chad = d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz);
+ }
+ }
+ d_ptr->whitePoint = wXyz;
+ d_ptr->identifyColorSpace();
+}
+
+/*!
Returns the transfrom processing model used for this color space.
+
+ \since 6.8
*/
QColorSpace::TransformModel QColorSpace::transformModel() const noexcept
{
@@ -915,6 +1084,18 @@ QColorSpace::TransformModel QColorSpace::transformModel() const noexcept
}
/*!
+ Returns the color model this color space can represent
+
+ \since 6.8
+*/
+QColorSpace::ColorModel QColorSpace::colorModel() const noexcept
+{
+ if (Q_UNLIKELY(!d_ptr))
+ return QColorSpace::ColorModel::Undefined;
+ return d_ptr->colorModel;
+}
+
+/*!
\internal
*/
void QColorSpace::detach()
@@ -1007,8 +1188,15 @@ bool QColorSpacePrivate::isValid() const noexcept
return !mAB.isEmpty();
if (!toXyz.isValid())
return false;
- if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
+ if (colorModel == QColorSpace::ColorModel::Gray) {
+ if (!trc[0].isValid())
+ return false;
+ } else if (colorModel == QColorSpace::ColorModel::Rgb){
+ if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
+ return false;
+ } else {
return false;
+ }
return true;
}
@@ -1031,7 +1219,8 @@ static bool compareElement(const QColorSpacePrivate::TransferElement &element,
{
return element.trc[0] == other.trc[0]
&& element.trc[1] == other.trc[1]
- && element.trc[2] == other.trc[2];
+ && element.trc[2] == other.trc[2]
+ && element.trc[3] == other.trc[3];
}
static bool compareElement(const QColorMatrix &element,
@@ -1055,6 +1244,8 @@ static bool compareElement(const QColorCLUT &element,
return false;
if (element.gridPointsZ != other.gridPointsZ)
return false;
+ if (element.gridPointsW != other.gridPointsW)
+ return false;
if (element.table.size() != other.table.size())
return false;
for (qsizetype i = 0; i < element.table.size(); ++i) {
@@ -1111,6 +1302,8 @@ bool QColorSpacePrivate::equals(const QColorSpacePrivate *other) const
if (!isThreeComponentMatrix()) {
if (isPcsLab != other->isPcsLab)
return false;
+ if (colorModel != other->colorModel)
+ return false;
if (mAB.count() != other->mAB.count())
return false;
if (mBA.count() != other->mBA.count())
@@ -1290,13 +1483,16 @@ QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace)
if (colorSpace.d_ptr) {
if (colorSpace.d_ptr->namedColorSpace)
dbg << colorSpace.d_ptr->namedColorSpace << ", ";
+ else
+ dbg << colorSpace.colorModel() << ", ";
if (!colorSpace.isValid()) {
dbg << "Invalid";
if (!colorSpace.d_ptr->iccProfile.isEmpty())
dbg << " with profile data";
} else if (colorSpace.d_ptr->isThreeComponentMatrix()) {
dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction();
- dbg << ", gamma=" << colorSpace.gamma();
+ if (colorSpace.transferFunction() == QColorSpace::TransferFunction::Gamma)
+ dbg << "=" << colorSpace.gamma();
} else {
if (colorSpace.d_ptr->isPcsLab)
dbg << "PCSLab, ";
diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h
index 848bc0898e..ecf6614561 100644
--- a/src/gui/painting/qcolorspace.h
+++ b/src/gui/painting/qcolorspace.h
@@ -26,7 +26,10 @@ public:
SRgbLinear,
AdobeRgb,
DisplayP3,
- ProPhotoRgb
+ ProPhotoRgb,
+ Bt2020,
+ Bt2100Pq,
+ Bt2100Hlg,
};
Q_ENUM(NamedColorSpace)
enum class Primaries {
@@ -34,7 +37,8 @@ public:
SRgb,
AdobeRgb,
DciP3D65,
- ProPhotoRgb
+ ProPhotoRgb,
+ Bt2020,
};
Q_ENUM(Primaries)
enum class TransferFunction {
@@ -42,7 +46,10 @@ public:
Linear,
Gamma,
SRgb,
- ProPhotoRgb
+ ProPhotoRgb,
+ Bt2020,
+ St2084,
+ Hlg,
};
Q_ENUM(TransferFunction)
enum class TransformModel : uint8_t {
@@ -50,9 +57,18 @@ public:
ElementListProcessing,
};
Q_ENUM(TransformModel)
+ enum class ColorModel : uint8_t {
+ Undefined = 0,
+ Rgb = 1,
+ Gray = 2,
+ Cmyk = 3,
+ };
+ Q_ENUM(ColorModel)
QColorSpace() noexcept = default;
QColorSpace(NamedColorSpace namedColorSpace);
+ QColorSpace(const QPointF &whitePoint, TransferFunction transferFunction, float gamma = 0.0f);
+ QColorSpace(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable);
QColorSpace(Primaries primaries, TransferFunction transferFunction, float gamma = 0.0f);
QColorSpace(Primaries primaries, float gamma);
QColorSpace(Primaries primaries, const QList<uint16_t> &transferFunctionTable);
@@ -104,8 +120,11 @@ public:
void setPrimaries(Primaries primariesId);
void setPrimaries(const QPointF &whitePoint, const QPointF &redPoint,
const QPointF &greenPoint, const QPointF &bluePoint);
+ void setWhitePoint(const QPointF &whitePoint);
+ QPointF whitePoint() const;
TransformModel transformModel() const noexcept;
+ ColorModel colorModel() const noexcept;
void detach();
bool isValid() const noexcept;
bool isValidTarget() const noexcept;
diff --git a/src/gui/painting/qcolorspace_p.h b/src/gui/painting/qcolorspace_p.h
index 03872ab2c5..4ec801b16b 100644
--- a/src/gui/painting/qcolorspace_p.h
+++ b/src/gui/painting/qcolorspace_p.h
@@ -66,6 +66,8 @@ public:
const QList<uint16_t> &redTransferFunctionTable,
const QList<uint16_t> &greenTransferFunctionTable,
const QList<uint16_t> &blueRransferFunctionTable);
+ QColorSpacePrivate(const QPointF &whitePoint, QColorSpace::TransferFunction transferFunction, float gamma);
+ QColorSpacePrivate(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable);
QColorSpacePrivate(const QColorSpacePrivate &other) = default;
static const QColorSpacePrivate *get(const QColorSpace &colorSpace)
@@ -101,16 +103,18 @@ public:
QColorSpace::Primaries primaries = QColorSpace::Primaries::Custom;
QColorSpace::TransferFunction transferFunction = QColorSpace::TransferFunction::Custom;
QColorSpace::TransformModel transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
+ QColorSpace::ColorModel colorModel = QColorSpace::ColorModel::Undefined;
float gamma = 0.0f;
QColorVector whitePoint;
// Three component matrix data:
QColorTrc trc[3];
QColorMatrix toXyz;
+ QColorMatrix chad;
// Element list processing data:
struct TransferElement {
- QColorTrc trc[3];
+ QColorTrc trc[4];
};
using Element = std::variant<TransferElement, QColorMatrix, QColorVector, QColorCLUT>;
bool isPcsLab = false;
diff --git a/src/gui/painting/qcolortransferfunction_p.h b/src/gui/painting/qcolortransferfunction_p.h
index 6afbfd25c2..b9a09b4646 100644
--- a/src/gui/painting/qcolortransferfunction_p.h
+++ b/src/gui/painting/qcolortransferfunction_p.h
@@ -16,37 +16,39 @@
//
#include <QtGui/private/qtguiglobal_p.h>
+#include <QtCore/QFlags>
#include <cmath>
QT_BEGIN_NAMESPACE
// Defines a ICC parametric curve type 4
-class Q_GUI_EXPORT QColorTransferFunction
+class QColorTransferFunction
{
public:
QColorTransferFunction() noexcept
- : m_a(1.0f), m_b(0.0f), m_c(1.0f), m_d(0.0f), m_e(0.0f), m_f(0.0f), m_g(1.0f)
- , m_flags(quint32(Hints::Calculated) | quint32(Hints::IsGamma) | quint32(Hints::IsIdentity))
+ : m_a(1.0f), m_b(0.0f), m_c(1.0f), m_d(0.0f), m_e(0.0f), m_f(0.0f), m_g(1.0f)
+ , m_flags(Hints(Hint::Calculated) | Hint::IsGamma | Hint::IsIdentity)
{ }
+
QColorTransferFunction(float a, float b, float c, float d, float e, float f, float g) noexcept
- : m_a(a), m_b(b), m_c(c), m_d(d), m_e(e), m_f(f), m_g(g), m_flags(0)
+ : m_a(a), m_b(b), m_c(c), m_d(d), m_e(e), m_f(f), m_g(g), m_flags()
{ }
bool isGamma() const
{
updateHints();
- return m_flags & quint32(Hints::IsGamma);
+ return m_flags & Hint::IsGamma;
}
bool isIdentity() const
{
updateHints();
- return m_flags & quint32(Hints::IsIdentity);
+ return m_flags & Hint::IsIdentity;
}
bool isSRgb() const
{
updateHints();
- return m_flags & quint32(Hints::IsSRgb);
+ return m_flags & Hint::IsSRgb;
}
float apply(float x) const
@@ -99,18 +101,23 @@ public:
static QColorTransferFunction fromGamma(float gamma)
{
return QColorTransferFunction(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, gamma,
- quint32(Hints::Calculated) | quint32(Hints::IsGamma) |
- (paramCompare(gamma, 1.0f) ? quint32(Hints::IsIdentity) : 0));
+ Hints(Hint::Calculated) | Hint::IsGamma |
+ (paramCompare(gamma, 1.0f) ? Hint::IsIdentity : Hint::NoHint));
}
static QColorTransferFunction fromSRgb()
{
return QColorTransferFunction(1.0f / 1.055f, 0.055f / 1.055f, 1.0f / 12.92f, 0.04045f, 0.0f, 0.0f, 2.4f,
- quint32(Hints::Calculated) | quint32(Hints::IsSRgb));
+ Hints(Hint::Calculated) | Hint::IsSRgb);
}
static QColorTransferFunction fromProPhotoRgb()
{
return QColorTransferFunction(1.0f, 0.0f, 1.0f / 16.0f, 16.0f / 512.0f, 0.0f, 0.0f, 1.8f,
- quint32(Hints::Calculated));
+ Hints(Hint::Calculated));
+ }
+ static QColorTransferFunction fromBt2020()
+ {
+ return QColorTransferFunction(1.0f / 1.0993f, 0.0993f / 1.0993f, 1.0f / 4.5f, 0.08145f, 0.0f, 0.0f, 2.2f,
+ Hints(Hint::Calculated));
}
bool matches(const QColorTransferFunction &o) const
{
@@ -130,8 +137,18 @@ public:
float m_f;
float m_g;
+ enum class Hint : quint32 {
+ NoHint = 0,
+ Calculated = 1,
+ IsGamma = 2,
+ IsIdentity = 4,
+ IsSRgb = 8
+ };
+
+ Q_DECLARE_FLAGS(Hints, Hint);
+
private:
- QColorTransferFunction(float a, float b, float c, float d, float e, float f, float g, quint32 flags) noexcept
+ QColorTransferFunction(float a, float b, float c, float d, float e, float f, float g, Hints flags) noexcept
: m_a(a), m_b(b), m_c(c), m_d(d), m_e(e), m_f(f), m_g(g), m_flags(flags)
{ }
static inline bool paramCompare(float p1, float p2)
@@ -144,7 +161,7 @@ private:
void updateHints() const
{
- if (m_flags & quint32(Hints::Calculated))
+ if (m_flags & Hint::Calculated)
return;
// We do not consider the case with m_d = 1.0f linear or simple,
// since it wouldn't be linear for applyExtended().
@@ -152,24 +169,21 @@ private:
&& paramCompare(m_d, 0.0f)
&& paramCompare(m_e, 0.0f);
if (simple) {
- m_flags |= quint32(Hints::IsGamma);
+ m_flags |= Hint::IsGamma;
if (qFuzzyCompare(m_g, 1.0f))
- m_flags |= quint32(Hints::IsIdentity);
+ m_flags |= Hint::IsIdentity;
} else {
if (*this == fromSRgb())
- m_flags |= quint32(Hints::IsSRgb);
+ m_flags |= Hint::IsSRgb;
}
- m_flags |= quint32(Hints::Calculated);
+ m_flags |= Hint::Calculated;
}
- enum class Hints : quint32 {
- Calculated = 1,
- IsGamma = 2,
- IsIdentity = 4,
- IsSRgb = 8
- };
- mutable quint32 m_flags;
+
+ mutable Hints m_flags;
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QColorTransferFunction::Hints);
+
inline bool operator==(const QColorTransferFunction &f1, const QColorTransferFunction &f2)
{
return f1.matches(f2);
diff --git a/src/gui/painting/qcolortransfergeneric_p.h b/src/gui/painting/qcolortransfergeneric_p.h
new file mode 100644
index 0000000000..b4e6e8dec7
--- /dev/null
+++ b/src/gui/painting/qcolortransfergeneric_p.h
@@ -0,0 +1,109 @@
+// 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
+
+#ifndef QCOLORTRANSFERGENERIC_P_H
+#define QCOLORTRANSFERGENERIC_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/private/qtguiglobal_p.h>
+
+#include <cmath>
+
+QT_BEGIN_NAMESPACE
+
+// Defines the a generic transfer function for our HDR functions
+class QColorTransferGenericFunction
+{
+public:
+ using ConverterPtr = float (*)(float);
+ constexpr QColorTransferGenericFunction(ConverterPtr toLinear = nullptr, ConverterPtr fromLinear = nullptr) noexcept
+ : m_toLinear(toLinear), m_fromLinear(fromLinear)
+ {}
+
+ static QColorTransferGenericFunction hlg()
+ {
+ return QColorTransferGenericFunction(hlgToLinear, hlgFromLinear);
+ }
+ static QColorTransferGenericFunction pq()
+ {
+ return QColorTransferGenericFunction(pqToLinear, pqFromLinear);
+ }
+
+ float apply(float x) const
+ {
+ return m_toLinear(x);
+ }
+
+ float applyInverse(float x) const
+ {
+ return m_fromLinear(x);
+ }
+
+ bool operator==(const QColorTransferGenericFunction &o) const noexcept
+ {
+ return m_toLinear == o.m_toLinear && m_fromLinear == o.m_fromLinear;
+ }
+ bool operator!=(const QColorTransferGenericFunction &o) const noexcept
+ {
+ return m_toLinear != o.m_toLinear || m_fromLinear != o.m_fromLinear;
+ }
+
+private:
+ ConverterPtr m_toLinear = nullptr;
+ ConverterPtr m_fromLinear = nullptr;
+
+ // HLG from linear [0-12] -> [0-1]
+ static float hlgFromLinear(float x)
+ {
+ if (x > 1.f)
+ return m_hlg_a * std::log(x - m_hlg_b) + m_hlg_c;
+ return std::sqrt(x * 0.25f);
+ }
+
+ // HLG to linear [0-1] -> [0-12]
+ static float hlgToLinear(float x)
+ {
+ if (x < 0.5f)
+ return (x * x) * 4.f;
+ return std::exp((x - m_hlg_c) / m_hlg_a) + m_hlg_b;
+ }
+
+ constexpr static float m_hlg_a = 0.17883277f;
+ constexpr static float m_hlg_b = 1.f - (4.f * m_hlg_a);
+ constexpr static float m_hlg_c = 0.55991073f; // 0.5 - a * ln(4 * a)
+
+ // PQ from linear [0-64] -> [0-1]
+ static float pqFromLinear(float x)
+ {
+ x = std::pow(x * (1.f / m_pq_f), (1.f / m_pq_m1));
+ return std::pow((m_pq_c1 - x) / (m_pq_c3 * x - m_pq_c2), (1.f / m_pq_m2));
+ }
+
+ // PQ from linear [0-1] -> [0-64]
+ static float pqToLinear(float x)
+ {
+ x = std::pow(x, m_pq_m1);
+ return std::pow((m_pq_c1 + m_pq_c2 * x) / (1.f + m_pq_c3 * x), m_pq_m2) * m_pq_f;
+ }
+
+ constexpr static float m_pq_c1 = 107.f / 128.f; // c3 - c2 + 1
+ constexpr static float m_pq_c2 = 2413.f / 128.f;
+ constexpr static float m_pq_c3 = 2392.f / 128.f;
+ constexpr static float m_pq_m1 = 1305.f / 8192.f;
+ constexpr static float m_pq_m2 = 2523.f / 32.f;
+ constexpr static float m_pq_f = 64.f; // This might need to be set based on scene metadata
+};
+
+QT_END_NAMESPACE
+
+#endif // QCOLORTRANSFERGENERIC_P_H
diff --git a/src/gui/painting/qcolortransfertable_p.h b/src/gui/painting/qcolortransfertable_p.h
index 5b1bd67476..ce6ad0c4b2 100644
--- a/src/gui/painting/qcolortransfertable_p.h
+++ b/src/gui/painting/qcolortransfertable_p.h
@@ -45,7 +45,7 @@ public:
Q_ASSERT(qsizetype(size) <= table.size());
}
- bool isEmpty() const
+ bool isEmpty() const noexcept
{
return m_tableSize == 0;
}
@@ -97,9 +97,11 @@ public:
float apply(float x) const
{
+ if (isEmpty())
+ return x;
x = std::clamp(x, 0.0f, 1.0f);
x *= m_tableSize - 1;
- const uint32_t lo = static_cast<uint32_t>(std::floor(x));
+ const uint32_t lo = static_cast<uint32_t>(x);
const uint32_t hi = std::min(lo + 1, m_tableSize - 1);
const float frac = x - lo;
if (!m_table16.isEmpty())
@@ -118,36 +120,10 @@ public:
return 0.0f;
if (x >= 1.0f)
return 1.0f;
- if (!m_table16.isEmpty()) {
- const float v = x * 65535.0f;
- uint32_t i = static_cast<uint32_t>(std::floor(resultLargerThan * (m_tableSize - 1)));
- auto it = std::lower_bound(m_table16.cbegin() + i, m_table16.cend(), v);
- i = it - m_table16.cbegin();
- if (i == 0)
- return 0.0f;
- if (i >= m_tableSize - 1)
- return 1.0f;
- const float y1 = m_table16[i - 1];
- const float y2 = m_table16[i];
- Q_ASSERT(v >= y1 && v <= y2);
- const float fr = (v - y1) / (y2 - y1);
- return (i + fr) * (1.0f / (m_tableSize - 1));
- }
- if (!m_table8.isEmpty()) {
- const float v = x * 255.0f;
- uint32_t i = static_cast<uint32_t>(std::floor(resultLargerThan * (m_tableSize - 1)));
- auto it = std::lower_bound(m_table8.cbegin() + i, m_table8.cend(), v);
- i = it - m_table8.cbegin();
- if (i == 0)
- return 0.0f;
- if (i >= m_tableSize - 1)
- return 1.0f;
- const float y1 = m_table8[i - 1];
- const float y2 = m_table8[i];
- Q_ASSERT(v >= y1 && v <= y2);
- const float fr = (v - y1) / (y2 - y1);
- return (i + fr) * (1.0f / (m_tableSize - 1));
- }
+ if (!m_table16.isEmpty())
+ return inverseLookup(x * 65535.0f, resultLargerThan, m_table16, m_tableSize - 1);
+ if (!m_table8.isEmpty())
+ return inverseLookup(x * 255.0f, resultLargerThan, m_table8, m_tableSize - 1);
return x;
}
@@ -211,6 +187,24 @@ public:
uint32_t m_tableSize = 0;
QList<uint8_t> m_table8;
QList<uint16_t> m_table16;
+private:
+ template<typename T>
+ static float inverseLookup(float needle, float resultLargerThan, const QList<T> &table, quint32 tableMax)
+ {
+ uint32_t i = static_cast<uint32_t>(resultLargerThan * tableMax);
+ auto it = std::lower_bound(table.cbegin() + i, table.cend(), needle);
+ i = it - table.cbegin();
+ if (i == 0)
+ return 0.0f;
+ if (i >= tableMax)
+ return 1.0f;
+ const float y1 = table[i - 1];
+ const float y2 = table[i];
+ Q_ASSERT(needle >= y1 && needle <= y2);
+ const float fr = (needle - y1) / (y2 - y1);
+ return (i + fr) * (1.0f / tableMax);
+ }
+
};
inline bool operator!=(const QColorTransferTable &t1, const QColorTransferTable &t2)
diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp
index 8d578d7af3..f54dac9f26 100644
--- a/src/gui/painting/qcolortransform.cpp
+++ b/src/gui/painting/qcolortransform.cpp
@@ -4,6 +4,7 @@
#include "qcolortransform.h"
#include "qcolortransform_p.h"
+#include "qcmyk_p.h"
#include "qcolorclut_p.h"
#include "qcolormatrix_p.h"
#include "qcolorspace_p.h"
@@ -21,16 +22,6 @@
QT_BEGIN_NAMESPACE
-std::shared_ptr<QColorTrcLut> lutFromTrc(const QColorTrc &trc)
-{
- if (trc.m_type == QColorTrc::Type::Table)
- return QColorTrcLut::fromTransferTable(trc.m_table);
- if (trc.m_type == QColorTrc::Type::Function)
- return QColorTrcLut::fromTransferFunction(trc.m_fun);
- qWarning() << "TRC uninitialized";
- return nullptr;
-}
-
void QColorTransformPrivate::updateLutsIn() const
{
if (colorSpaceIn->lut.generated.loadAcquire())
@@ -45,12 +36,12 @@ void QColorTransformPrivate::updateLutsIn() const
}
if (colorSpaceIn->trc[0] == colorSpaceIn->trc[1] && colorSpaceIn->trc[0] == colorSpaceIn->trc[2]) {
- colorSpaceIn->lut[0] = lutFromTrc(colorSpaceIn->trc[0]);
+ colorSpaceIn->lut[0] = QColorTrcLut::fromTrc(colorSpaceIn->trc[0]);
colorSpaceIn->lut[1] = colorSpaceIn->lut[0];
colorSpaceIn->lut[2] = colorSpaceIn->lut[0];
} else {
for (int i = 0; i < 3; ++i)
- colorSpaceIn->lut[i] = lutFromTrc(colorSpaceIn->trc[i]);
+ colorSpaceIn->lut[i] = QColorTrcLut::fromTrc(colorSpaceIn->trc[i]);
}
colorSpaceIn->lut.generated.storeRelease(1);
@@ -69,12 +60,12 @@ void QColorTransformPrivate::updateLutsOut() const
}
if (colorSpaceOut->trc[0] == colorSpaceOut->trc[1] && colorSpaceOut->trc[0] == colorSpaceOut->trc[2]) {
- colorSpaceOut->lut[0] = lutFromTrc(colorSpaceOut->trc[0]);
+ colorSpaceOut->lut[0] = QColorTrcLut::fromTrc(colorSpaceOut->trc[0]);
colorSpaceOut->lut[1] = colorSpaceOut->lut[0];
colorSpaceOut->lut[2] = colorSpaceOut->lut[0];
} else {
for (int i = 0; i < 3; ++i)
- colorSpaceOut->lut[i] = lutFromTrc(colorSpaceOut->trc[i]);
+ colorSpaceOut->lut[i] = QColorTrcLut::fromTrc(colorSpaceOut->trc[i]);
}
colorSpaceOut->lut.generated.storeRelease(1);
@@ -244,38 +235,31 @@ 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;
}
@@ -346,6 +330,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();
@@ -362,9 +379,37 @@ inline int getAlpha<QRgb>(const QRgb &p)
template<>
inline int getAlpha<QRgba64>(const QRgba64 &p)
{ return p.alpha(); }
+
+template<typename T>
+static inline constexpr int getFactor();
+template<>
+inline constexpr int getFactor<QRgb>()
+{ return 255; }
+template<>
+inline constexpr int getFactor<QRgba64>()
+{ return 65535; }
#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);
@@ -400,7 +445,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) {
@@ -419,7 +464,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);
@@ -435,7 +480,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);
@@ -457,7 +502,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);
@@ -476,7 +521,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);
@@ -490,7 +535,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<>
@@ -503,7 +548,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>
@@ -528,7 +573,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);
@@ -538,7 +583,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);
@@ -599,7 +644,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);
@@ -612,7 +657,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);
@@ -620,7 +665,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<>
@@ -629,7 +674,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>
@@ -658,7 +703,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);
@@ -678,7 +723,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);
@@ -768,17 +813,19 @@ 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>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
__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);
@@ -789,21 +836,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);
@@ -811,7 +858,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);
@@ -850,16 +897,18 @@ 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>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
__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);
@@ -867,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);
@@ -907,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);
@@ -928,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));
@@ -943,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);
@@ -976,16 +1022,18 @@ 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>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
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);
@@ -998,7 +1046,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);
}
}
@@ -1020,34 +1068,35 @@ 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>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
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);
@@ -1064,9 +1113,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);
}
}
@@ -1082,10 +1131,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);
@@ -1094,34 +1142,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);
@@ -1131,11 +1181,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;
@@ -1143,11 +1194,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);
@@ -1155,10 +1207,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);
@@ -1167,20 +1218,59 @@ 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)
+{
+ if (d_ptr->colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray ||
+ (d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[1] &&
+ d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[2])) {
+ 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;
+ }
+ } else {
+ for (qsizetype i = 0; i < len; ++i) {
+ QColorVector v;
+ v.x = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]);
+ v.y = d_ptr->colorSpaceIn->lut[1]->u8ToLinearF32(src[i]);
+ v.z = d_ptr->colorSpaceIn->lut[2]->u8ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->toXyz.map(v);
+ }
+ }
+}
+
+static void loadGray(QColorVector *buffer, const quint16 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
+{
+ if (d_ptr->colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray ||
+ (d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[1] &&
+ d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[2])) {
+ 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;
+ }
+ } else {
+ for (qsizetype i = 0; i < len; ++i) {
+ QColorVector v;
+ v.x = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]);
+ v.y = d_ptr->colorSpaceIn->lut[1]->u16ToLinearF32(src[i]);
+ v.z = d_ptr->colorSpaceIn->lut[2]->u16ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->toXyz.map(v);
+ }
+ }
+}
+
+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;
@@ -1194,6 +1284,28 @@ private:
alignas(T) char data[sizeof(T) * Count];
};
+void loadUnpremultipliedLUT(QColorVector *buffer, const uchar *src, const qsizetype len)
+{
+ const float f = 1.0f / 255.f;
+ for (qsizetype i = 0; i < len; ++i) {
+ const float p = src[i] * f;
+ buffer[i].x = p;
+ buffer[i].y = p;
+ buffer[i].z = p;
+ }
+}
+
+void loadUnpremultipliedLUT(QColorVector *buffer, const quint16 *src, const qsizetype len)
+{
+ const float f = 1.0f / 65535.f;
+ for (qsizetype i = 0; i < len; ++i) {
+ const float p = src[i] * f;
+ buffer[i].x = p;
+ buffer[i].y = p;
+ buffer[i].z = p;
+ }
+}
+
void loadUnpremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len)
{
const float f = 1.0f / 255.f;
@@ -1205,6 +1317,18 @@ void loadUnpremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizety
}
}
+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;
@@ -1224,6 +1348,16 @@ void loadUnpremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const
}
}
+void loadPremultipliedLUT(QColorVector *, const uchar *, const qsizetype)
+{
+ Q_UNREACHABLE();
+}
+
+void loadPremultipliedLUT(QColorVector *, const quint16 *, const qsizetype)
+{
+ Q_UNREACHABLE();
+}
+
void loadPremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len)
{
for (qsizetype i = 0; i < len; ++i) {
@@ -1235,6 +1369,11 @@ void loadPremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype
}
}
+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) {
@@ -1254,8 +1393,19 @@ void loadPremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const q
buffer[i].z = src[i].b * f;
}
}
+template<typename T>
+static void storeUnpremultipliedLUT(QRgb *dst, const T *, 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);
+ }
+}
-static void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
+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;
@@ -1265,29 +1415,81 @@ static void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVect
}
}
-static void storeUnpremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
+
+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());
}
}
-static void storeUnpremultipliedLUT(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
+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, src[i].a};
+ dst[i] = QRgbaFloat32{r, g, b, getAlphaF(src[i])};
}
}
-static void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
+template<typename T>
+static void storePremultipliedLUT(QRgb *dst, const T *, 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<>
+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]);
@@ -1298,8 +1500,37 @@ static void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector
}
}
-static void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
- const QColorVector *buffer, const qsizetype len)
+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 *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 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 QRgba64 *src, const QColorVector *buffer, const qsizetype len)
{
for (qsizetype i = 0; i < len; ++i) {
const int a = src[i].alpha();
@@ -1310,11 +1541,11 @@ static void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
}
}
-static void storePremultipliedLUT(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
- const QColorVector *buffer, const qsizetype len)
+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 = src[i].a;
+ 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;
@@ -1324,10 +1555,13 @@ static void storePremultipliedLUT(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
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);
}
}
@@ -1372,6 +1606,9 @@ QColorVector QColorTransformPrivate::map(QColorVector c) const
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)
@@ -1380,11 +1617,12 @@ QColorVector QColorTransformPrivate::map(QColorVector c) const
c = c.labToXyz();
if (colorSpaceOut->isThreeComponentMatrix()) {
- if (!colorSpaceIn->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);
+ 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);
@@ -1398,6 +1636,9 @@ QColorVector QColorTransformPrivate::map(QColorVector c) const
// 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;
}
@@ -1439,66 +1680,164 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const
}
template<typename T>
-void QColorTransformPrivate::applyConvertIn(const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
-{
- 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.
- } else {
- if (flags & InputPremultiplied)
- loadPremultipliedLUT(buffer, src, len);
- else
- loadUnpremultipliedLUT(buffer, src, len);
+constexpr bool IsGrayscale = std::is_same_v<T, uchar> || std::is_same_v<T, quint16>;
+template<typename T>
+constexpr bool IsAlwaysOpaque = std::is_same_v<T, QCmyk32> || IsGrayscale<T>;
+template<typename T>
+constexpr bool CanUseThreeComponent = !std::is_same_v<T, QCmyk32>;
+template<typename T>
+constexpr bool UnclampedValues = std::is_same_v<T, QRgbaFloat16> || std::is_same_v<T, QRgbaFloat32>;
+
+// Possible combos for data and color spaces:
+// DataCM ColorSpaceCM ColorSpacePM Notes
+// Gray Gray ThreeMatrix
+// Gray Rgb ThreeMatrix Invalid colorMatrix
+// Rgb Rgb ThreeMatrix
+// Rgb Rgb ElementProc
+// Gray Rgb ElementProc Only possible for input data
+// Cmyk Cmyk ElementProc
+//
+// Gray data can be uchar, quint16, and is always Opaque
+// Rgb data can be QRgb, QRgba64, or QRgbaFloat32, and is Unpremultiplied, Premultiplied, or Opaque
+// Cmyk data can be Cmyk32, and is always Opaque
+//
+// colorMatrix as setup for Gray on Gray or Rgb on Rgb, but not Gray data on Rgb colorspace.
+
+template<typename S>
+void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
+{
+ if constexpr (IsGrayscale<S>) {
+ if (colorSpaceIn->isThreeComponentMatrix()) {
+ loadGray(buffer, src, len, this);
+ if (!colorSpaceOut->isThreeComponentMatrix() || colorSpaceIn->colorModel != QColorSpace::ColorModel::Gray) {
+ if (!colorSpaceIn->chad.isNull())
+ applyMatrix<DoClamp>(buffer, len, colorSpaceIn->chad);
+ }
+ return;
+ }
+ } else if constexpr (CanUseThreeComponent<S>) {
+ if (colorSpaceIn->isThreeComponentMatrix()) {
+ if (flags & InputPremultiplied)
+ loadPremultiplied(buffer, src, len, this);
+ else
+ loadUnpremultiplied(buffer, src, len, this);
- // Do element based conversion
- for (auto &&element : colorSpaceIn->mAB)
- std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
+ if (!colorSpaceOut->isThreeComponentMatrix())
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
+ return;
+ }
}
+ Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix());
+
+ if (flags & InputPremultiplied)
+ loadPremultipliedLUT(buffer, src, len);
+ else
+ loadUnpremultipliedLUT(buffer, src, len);
+
+ // Do element based conversion
+ for (auto &&element : colorSpaceIn->mAB)
+ std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
}
-template<typename T>
-void QColorTransformPrivate::applyConvertOut(T *dst, const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
-{
- if (colorSpaceOut->isThreeComponentMatrix()) {
- applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
+template<typename D, typename S>
+void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
+{
+ constexpr ApplyMatrixForm doClamp = UnclampedValues<D> ? DoNotClamp : DoClamp;
+ if constexpr (IsGrayscale<D>) {
+ Q_UNUSED(src); // dealing with buggy warnings in gcc 9
+ Q_UNUSED(flags);
+ // Calculate the matrix for grayscale conversion
+ QColorMatrix grayMatrix;
+ if (colorSpaceIn == colorSpaceOut ||
+ (colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray &&
+ colorSpaceOut->colorModel == QColorSpace::ColorModel::Gray)) {
+ // colorMatrix already has the right form
+ grayMatrix = colorMatrix;
+ } else {
+ if constexpr (IsGrayscale<S>) {
+ if (colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray)
+ grayMatrix = colorSpaceIn->chad;
+ else
+ grayMatrix = QColorMatrix::identity(); // Otherwise already handled in applyConvertIn
+ } else {
+ if (colorSpaceIn->isThreeComponentMatrix())
+ grayMatrix = colorSpaceIn->toXyz;
+ else
+ grayMatrix = QColorMatrix::identity();
+ }
+ if (!colorSpaceOut->chad.isNull())
+ grayMatrix = colorSpaceOut->chad.inverted() * grayMatrix;
+ }
+
+ applyMatrix<doClamp>(buffer, len, grayMatrix);
+ storeOpaque(dst, buffer, len, this);
+ return;
+ } else if constexpr (CanUseThreeComponent<D>) {
+ if (colorSpaceOut->isThreeComponentMatrix()) {
+ if (IsGrayscale<S> && colorSpaceIn->colorModel != QColorSpace::ColorModel::Gray)
+ applyMatrix<doClamp>(buffer, len, colorSpaceOut->toXyz.inverted()); // colorMatrix wasnt prepared for gray input
+ else
+ applyMatrix<doClamp>(buffer, len, colorMatrix);
+
+ if constexpr (IsAlwaysOpaque<S>) {
+ 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;
+ }
+ }
+ if constexpr (!IsGrayscale<D>) {
+ Q_ASSERT(!colorSpaceOut->isThreeComponentMatrix());
- if (flags & InputOpaque)
- storeOpaque(dst, src, buffer, len, this);
- else if (flags & OutputPremultiplied)
- storePremultiplied(dst, src, buffer, len, this);
- else
- storeUnpremultiplied(dst, src, buffer, len, this);
- } else {
// Do element based conversion
for (auto &&element : colorSpaceOut->mBA)
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
- for (qsizetype j = 0; j < len; ++j) {
- buffer[j].x = std::clamp(buffer[j].x, 0.f, 1.f);
- buffer[j].y = std::clamp(buffer[j].y, 0.f, 1.f);
- buffer[j].z = std::clamp(buffer[j].z, 0.f, 1.f);
- }
+ clampIfNeeded<doClamp>(buffer, len);
if (flags & OutputPremultiplied)
storePremultipliedLUT(dst, src, buffer, len);
else
storeUnpremultipliedLUT(dst, src, buffer, len);
+ } else {
+ Q_UNREACHABLE();
}
}
-template<typename T>
-void QColorTransformPrivate::applyElementListTransform(T *dst, const T *src, qsizetype count, TransformFlags flags) const
+/*!
+ \internal
+ Adapt Profile Connection Spaces.
+*/
+void QColorTransformPrivate::pcsAdapt(QColorVector *buffer, qsizetype count) const
{
- Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix());
+ // Match Profile Connection Spaces (PCS):
+ if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
+ for (qsizetype j = 0; j < count; ++j)
+ buffer[j] = buffer[j].xyzToLab();
+ } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
+ for (qsizetype j = 0; j < count; ++j)
+ buffer[j] = buffer[j].labToXyz();
+ }
+}
- if (!colorMatrix.isValid())
- return;
+/*!
+ \internal
+ Applies the color transformation on \a count S pixels starting from
+ \a src and stores the result in \a dst as D pixels .
+ Assumes unpremultiplied data by default. Set \a flags to change defaults.
+
+ \sa prepare()
+*/
+template<typename D, typename S>
+void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const
+{
if (colorSpaceIn->isThreeComponentMatrix())
updateLutsIn();
if (colorSpaceOut->isThreeComponentMatrix())
@@ -1511,14 +1850,7 @@ void QColorTransformPrivate::applyElementListTransform(T *dst, const T *src, qsi
applyConvertIn(src + i, buffer, len, 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();
- }
+ pcsAdapt(buffer, len);
applyConvertOut(dst + i, src + i, buffer, len, flags);
@@ -1526,111 +1858,11 @@ void QColorTransformPrivate::applyElementListTransform(T *dst, const T *src, qsi
}
}
-template<typename T>
-void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsizetype count, TransformFlags flags) const
-{
- Q_ASSERT(colorSpaceIn->isThreeComponentMatrix() && colorSpaceOut->isThreeComponentMatrix());
-
- if (!colorMatrix.isValid())
- return;
-
- updateLutsIn();
- updateLutsOut();
-
- bool doApplyMatrix = !colorMatrix.isIdentity();
- constexpr ApplyMatrixForm doClamp = (std::is_same_v<T, QRgbaFloat16> || std::is_same_v<T, QRgbaFloat32>) ? DoNotClamp : DoClamp;
-
- 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<doClamp>(buffer, len, colorMatrix);
-
- 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);
-
- i += len;
- }
-}
-
-template<typename T>
-void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const
-{
- if (colorSpaceIn->isThreeComponentMatrix() && colorSpaceOut->isThreeComponentMatrix())
- applyThreeComponentMatrix<T>(dst, src, count, flags);
- else
- applyElementListTransform<T>(dst, src, count, flags);
-}
-
-/*!
- \internal
- Is to be called on a color-transform to XYZ, returns only luminance values.
-
- */
-template<typename D, typename S>
-void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
-{
- if (!colorSpaceIn->isThreeComponentMatrix()) {
- QUninitialized<QColorVector, WorkBlockSize> buffer;
-
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
- if (flags & InputPremultiplied)
- loadPremultipliedLUT(buffer, src + i, len);
- else
- loadUnpremultipliedLUT(buffer, src + i, len);
-
- // Do element based conversion
- for (auto &&element : colorSpaceIn->mAB)
- std::visit([&](auto &&elm) { visitElement(elm, buffer, len); }, element);
-
- storeGray(dst + i, src + i, buffer, len, this);
-
- i += len;
- }
- return;
- }
-
- if (!colorMatrix.isValid())
- return;
-
- updateLutsIn();
- 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);
-
- applyMatrix<DoClamp>(buffer, len, colorMatrix);
-
- storeGray(dst + i, src + i, buffer, len, this);
-
- i += len;
- }
-}
-
/*!
\internal
\enum QColorTransformPrivate::TransformFlag
- Defines how the transform is to be applied.
+ 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.
@@ -1654,67 +1886,31 @@ void QColorTransformPrivate::prepare()
updateLutsOut();
}
-/*!
- \internal
- Applies the color transformation on \a count QRgb 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(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const
-{
- apply<QRgb>(dst, src, count, flags);
-}
-
-/*!
- \internal
- Applies the color transformation on \a count QRgba64 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(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const
-{
- 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);
-}
-
-
-template void QColorTransformPrivate::applyReturnGray<quint8, QRgb>(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyReturnGray<quint16, QRgba64>(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
-
-bool QColorTransformPrivate::isThreeComponentMatrix() const
-{
- if (colorSpaceIn && !colorSpaceIn->isThreeComponentMatrix())
- return false;
- if (colorSpaceOut && !colorSpaceOut->isThreeComponentMatrix())
- return false;
- return true;
-}
+// Only some versions increasing precision 14/36 combos
+template void QColorTransformPrivate::apply<quint8, quint8>(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint8, QRgb>(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint8, QCmyk32>(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, quint8>(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, quint16>(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, QCmyk32>(quint16 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, QRgba64>(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgb, quint8>(QRgb *dst, const quint8 *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, quint8>(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QCmyk32, quint16>(QCmyk32 *dst, const quint16 *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, quint16>(QRgba64 *dst, const quint16 *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;
/*!
\internal
@@ -1728,7 +1924,7 @@ bool QColorTransformPrivate::isIdentity() const
if (colorSpaceIn && colorSpaceOut) {
if (colorSpaceIn->equals(colorSpaceOut.constData()))
return true;
- if (!isThreeComponentMatrix())
+ if (!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix())
return false;
if (colorSpaceIn->transferFunction != colorSpaceOut->transferFunction)
return false;
@@ -1738,7 +1934,9 @@ bool QColorTransformPrivate::isIdentity() const
&& colorSpaceIn->trc[2] == colorSpaceOut->trc[2];
}
} else {
- if (!isThreeComponentMatrix())
+ if (colorSpaceIn && !colorSpaceIn->isThreeComponentMatrix())
+ return false;
+ if (colorSpaceOut && !colorSpaceOut->isThreeComponentMatrix())
return false;
if (colorSpaceIn && colorSpaceIn->transferFunction != QColorSpace::TransferFunction::Linear)
return false;
diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h
index 1d54aced1b..3f11603420 100644
--- a/src/gui/painting/qcolortransform_p.h
+++ b/src/gui/painting/qcolortransform_p.h
@@ -22,11 +22,12 @@
#include <QtGui/qrgbafloat.h>
QT_BEGIN_NAMESPACE
+class QCmyk32;
class QColorTransformPrivate : public QSharedData
{
public:
- QColorMatrix colorMatrix;
+ QColorMatrix colorMatrix; // Combined colorSpaceIn->toXyz and colorSpaceOut->toXyz.inverted()
QExplicitlySharedDataPointer<const QColorSpacePrivate> colorSpaceIn;
QExplicitlySharedDataPointer<const QColorSpacePrivate> colorSpaceOut;
@@ -36,7 +37,6 @@ public:
void updateLutsIn() const;
void updateLutsOut() const;
bool isIdentity() const;
- bool isThreeComponentMatrix() const;
Q_GUI_EXPORT void prepare();
enum TransformFlag {
@@ -51,26 +51,15 @@ public:
QColorVector map(QColorVector color) const;
QColorVector mapExtended(QColorVector color) const;
- 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;
-
- template<typename T>
- void apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
-
- template<typename T>
- void applyConvertIn(const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
- template<typename T>
- void applyConvertOut(T *dst, const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
- template<typename T>
- void applyElementListTransform(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
- template<typename T>
- void applyThreeComponentMatrix(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
-
template<typename D, typename S>
- void applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+ void apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+private:
+ void pcsAdapt(QColorVector *buffer, qsizetype len) const;
+ template<typename S>
+ void applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
+ template<typename D, typename S>
+ void applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
};
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrc_p.h b/src/gui/painting/qcolortrc_p.h
index d1ad1987df..dbca91c7f6 100644
--- a/src/gui/painting/qcolortrc_p.h
+++ b/src/gui/painting/qcolortrc_p.h
@@ -17,6 +17,7 @@
#include <QtGui/private/qtguiglobal_p.h>
#include "qcolortransferfunction_p.h"
+#include "qcolortransfergeneric_p.h"
#include "qcolortransfertable_p.h"
QT_BEGIN_NAMESPACE
@@ -26,20 +27,23 @@ class Q_GUI_EXPORT QColorTrc
{
public:
QColorTrc() noexcept : m_type(Type::Uninitialized) { }
- QColorTrc(const QColorTransferFunction &fun) : m_type(Type::Function), m_fun(fun) { }
+ QColorTrc(const QColorTransferFunction &fun) : m_type(Type::ParameterizedFunction), m_fun(fun) { }
QColorTrc(const QColorTransferTable &table) : m_type(Type::Table), m_table(table) { }
- QColorTrc(QColorTransferFunction &&fun) noexcept : m_type(Type::Function), m_fun(std::move(fun)) { }
+ QColorTrc(const QColorTransferGenericFunction &hdr) : m_type(Type::GenericFunction), m_hdr(hdr) { }
+ QColorTrc(QColorTransferFunction &&fun) noexcept : m_type(Type::ParameterizedFunction), m_fun(std::move(fun)) { }
QColorTrc(QColorTransferTable &&table) noexcept : m_type(Type::Table), m_table(std::move(table)) { }
+ QColorTrc(QColorTransferGenericFunction &&hdr) noexcept : m_type(Type::GenericFunction), m_hdr(std::move(hdr)) { }
enum class Type {
Uninitialized,
- Function,
- Table
+ ParameterizedFunction,
+ GenericFunction,
+ Table,
};
bool isIdentity() const
{
- return (m_type == Type::Function && m_fun.isIdentity())
+ return (m_type == Type::ParameterizedFunction && m_fun.isIdentity())
|| (m_type == Type::Table && m_table.isIdentity());
}
bool isValid() const
@@ -48,62 +52,87 @@ public:
}
float apply(float x) const
{
- if (m_type == Type::Table)
- return m_table.apply(x);
- if (m_type == Type::Function)
- return m_fun.apply(x);
+ switch (m_type) {
+ case Type::ParameterizedFunction:
+ return fun().apply(x);
+ case Type::GenericFunction:
+ return hdr().apply(x);
+ case Type::Table:
+ return table().apply(x);
+ default:
+ break;
+ }
return x;
}
float applyExtended(float x) const
{
- if (x >= 0.0f && x <= 1.0f)
- return apply(x);
- if (m_type == Type::Function)
- return std::copysign(m_fun.apply(std::abs(x)), x);
- if (m_type == Type::Table)
- return x < 0.0f ? 0.0f : 1.0f;
+ switch (m_type) {
+ case Type::ParameterizedFunction:
+ return std::copysign(fun().apply(std::abs(x)), x);
+ case Type::GenericFunction:
+ return hdr().apply(x);
+ case Type::Table:
+ return table().apply(x);
+ default:
+ break;
+ }
return x;
}
float applyInverse(float x) const
{
- if (m_type == Type::Table)
- return m_table.applyInverse(x);
- if (m_type == Type::Function)
- return m_fun.inverted().apply(x);
+ switch (m_type) {
+ case Type::ParameterizedFunction:
+ return fun().inverted().apply(x);
+ case Type::GenericFunction:
+ return hdr().applyInverse(x);
+ case Type::Table:
+ return table().applyInverse(x);
+ default:
+ break;
+ }
return x;
}
float applyInverseExtended(float x) const
{
- if (x >= 0.0f && x <= 1.0f)
- return applyInverse(x);
- if (m_type == Type::Function)
+ switch (m_type) {
+ case Type::ParameterizedFunction:
return std::copysign(applyInverse(std::abs(x)), x);
- if (m_type == Type::Table)
- return x < 0.0f ? 0.0f : 1.0f;
+ case Type::GenericFunction:
+ return hdr().applyInverse(x);
+ case Type::Table:
+ return table().applyInverse(x);
+ default:
+ break;
+ }
return x;
}
- friend inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2);
- friend inline bool operator==(const QColorTrc &o1, const QColorTrc &o2);
+ const QColorTransferTable &table() const { return m_table; }
+ const QColorTransferFunction &fun() const{ return m_fun; }
+ const QColorTransferGenericFunction &hdr() const { return m_hdr; }
+ Type type() const noexcept { return m_type; }
Type m_type;
+
+ friend inline bool comparesEqual(const QColorTrc &lhs, const QColorTrc &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QColorTrc);
+
QColorTransferFunction m_fun;
QColorTransferTable m_table;
+ QColorTransferGenericFunction m_hdr;
};
-inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2)
+inline bool comparesEqual(const QColorTrc &o1, const QColorTrc &o2)
{
if (o1.m_type != o2.m_type)
- return true;
- if (o1.m_type == QColorTrc::Type::Function)
- return o1.m_fun != o2.m_fun;
+ return false;
+ if (o1.m_type == QColorTrc::Type::ParameterizedFunction)
+ return o1.m_fun == o2.m_fun;
if (o1.m_type == QColorTrc::Type::Table)
- return o1.m_table != o2.m_table;
- return false;
-}
-inline bool operator==(const QColorTrc &o1, const QColorTrc &o2)
-{
- return !(o1 != o2);
+ return o1.m_table == o2.m_table;
+ if (o1.m_type == QColorTrc::Type::GenericFunction)
+ return o1.m_hdr == o2.m_hdr;
+ return true;
}
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrclut.cpp b/src/gui/painting/qcolortrclut.cpp
index 6f1cacea75..1357aa41a6 100644
--- a/src/gui/painting/qcolortrclut.cpp
+++ b/src/gui/painting/qcolortrclut.cpp
@@ -3,7 +3,9 @@
#include "qcolortrclut_p.h"
#include "qcolortransferfunction_p.h"
+#include "qcolortransfergeneric_p.h"
#include "qcolortransfertable_p.h"
+#include "qcolortrc_p.h"
#include <qmath.h>
QT_BEGIN_NAMESPACE
@@ -13,43 +15,111 @@ std::shared_ptr<QColorTrcLut> QColorTrcLut::create()
return std::make_shared<Access>();
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromGamma(qreal gamma)
+std::shared_ptr<QColorTrcLut> QColorTrcLut::fromGamma(float gamma, Direction dir)
{
auto cp = create();
+ cp->setFromGamma(gamma, dir);
+ return cp;
+}
+
+std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTrc(const QColorTrc &trc, Direction dir)
+{
+ if (!trc.isValid())
+ return nullptr;
+ auto cp = create();
+ cp->setFromTrc(trc, dir);
+ return cp;
+}
- for (int i = 0; i <= (255 * 16); ++i) {
- cp->m_toLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), gamma) * (255 * 256)));
- cp->m_fromLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), qreal(1) / gamma) * (255 * 256)));
+void QColorTrcLut::setFromGamma(float gamma, Direction dir)
+{
+ constexpr float iRes = 1.f / float(Resolution);
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qRound(qBound(0.f, qPow(i * iRes, gamma), 1.f) * (255 * 256)));
}
- return cp;
+ if (dir & FromLinear) {
+ const float iGamma = 1.f / gamma;
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_fromLinear[i] = ushort(qRound(qBound(0.f, qPow(i * iRes, iGamma), 1.f) * (255 * 256)));
+ }
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferFunction(const QColorTransferFunction &fun)
+void QColorTrcLut::setFromTransferFunction(const QColorTransferFunction &fun, Direction dir)
{
- auto cp = create();
- QColorTransferFunction inv = fun.inverted();
+ constexpr float iRes = 1.f / float(Resolution);
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qRound(qBound(0.f, fun.apply(i * iRes), 1.f) * (255 * 256)));
+ }
- for (int i = 0; i <= (255 * 16); ++i) {
- cp->m_toLinear[i] = ushort(qRound(fun.apply(i / qreal(255 * 16)) * (255 * 256)));
- cp->m_fromLinear[i] = ushort(qRound(inv.apply(i / qreal(255 * 16)) * (255 * 256)));
+ if (dir & FromLinear) {
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ QColorTransferFunction inv = fun.inverted();
+ for (int i = 0; i <= Resolution; ++i)
+ m_fromLinear[i] = ushort(qRound(qBound(0.f, inv.apply(i * iRes), 1.f) * (255 * 256)));
}
+}
- return cp;
+void QColorTrcLut::setFromTransferGenericFunction(const QColorTransferGenericFunction &fun, Direction dir)
+{
+ constexpr float iRes = 1.f / float(Resolution);
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qRound(qBound(0.f, fun.apply(i * iRes), 1.f) * (255 * 256)));
+ }
+
+ if (dir & FromLinear) {
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_fromLinear[i] = ushort(qRound(qBound(0.f, fun.applyInverse(i * iRes), 1.f) * (255 * 256)));
+ }
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferTable(const QColorTransferTable &table)
+void QColorTrcLut::setFromTransferTable(const QColorTransferTable &table, Direction dir)
{
- auto cp = create();
+ constexpr float iRes = 1.f / float(Resolution);
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qRound(table.apply(i * iRes) * (255 * 256)));
+ }
- float minInverse = 0.0f;
- for (int i = 0; i <= (255 * 16); ++i) {
- cp->m_toLinear[i] = ushort(qBound(0, qRound(table.apply(i / qreal(255 * 16)) * (255 * 256)), 65280));
- minInverse = table.applyInverse(i / qreal(255 * 16), minInverse);
- cp->m_fromLinear[i] = ushort(qBound(0, qRound(minInverse * (255 * 256)), 65280));
+ if (dir & FromLinear) {
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ float minInverse = 0.0f;
+ for (int i = 0; i <= Resolution; ++i) {
+ minInverse = table.applyInverse(i * iRes, minInverse);
+ m_fromLinear[i] = ushort(qRound(minInverse * (255 * 256)));
+ }
}
+}
- return cp;
+void QColorTrcLut::setFromTrc(const QColorTrc &trc, Direction dir)
+{
+ switch (trc.m_type) {
+ case QColorTrc::Type::ParameterizedFunction:
+ return setFromTransferFunction(trc.fun(), dir);
+ case QColorTrc::Type::Table:
+ return setFromTransferTable(trc.table(), dir);
+ case QColorTrc::Type::GenericFunction:
+ return setFromTransferGenericFunction(trc.hdr(), dir);
+ case QColorTrc::Type::Uninitialized:
+ break;
+ }
}
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrclut_p.h b/src/gui/painting/qcolortrclut_p.h
index c6b73d9f69..15f7348836 100644
--- a/src/gui/painting/qcolortrclut_p.h
+++ b/src/gui/painting/qcolortrclut_p.h
@@ -30,15 +30,31 @@
QT_BEGIN_NAMESPACE
+class QColorTransferGenericFunction;
class QColorTransferFunction;
class QColorTransferTable;
+class QColorTrc;
class Q_GUI_EXPORT QColorTrcLut
{
public:
- static std::shared_ptr<QColorTrcLut> fromGamma(qreal gamma);
- static std::shared_ptr<QColorTrcLut> fromTransferFunction(const QColorTransferFunction &transfn);
- static std::shared_ptr<QColorTrcLut> fromTransferTable(const QColorTransferTable &transTable);
+ static constexpr uint32_t ShiftUp = 4; // Amount to shift up from 1->255
+ static constexpr uint32_t ShiftDown = (8 - ShiftUp); // Amount to shift down from 1->65280
+ static constexpr qsizetype Resolution = (1 << ShiftUp) * 255; // Number of entries in table
+
+ enum Direction {
+ ToLinear = 1,
+ FromLinear = 2,
+ BiLinear = ToLinear | FromLinear
+ };
+
+ static std::shared_ptr<QColorTrcLut> fromGamma(float gamma, Direction dir = BiLinear);
+ static std::shared_ptr<QColorTrcLut> fromTrc(const QColorTrc &trc, Direction dir = BiLinear);
+ void setFromGamma(float gamma, Direction dir = BiLinear);
+ void setFromTransferFunction(const QColorTransferFunction &transFn, Direction dir = BiLinear);
+ void setFromTransferTable(const QColorTransferTable &transTable, Direction dir = BiLinear);
+ void setFromTransferGenericFunction(const QColorTransferGenericFunction &transfn, Direction dir);
+ void setFromTrc(const QColorTrc &trc, Direction dir);
// The following methods all convert opaque or unpremultiplied colors:
@@ -47,7 +63,7 @@ public:
#if defined(__SSE2__)
__m128i v = _mm_cvtsi32_si128(rgb32);
v = _mm_unpacklo_epi8(v, _mm_setzero_si128());
- const __m128i vidx = _mm_slli_epi16(v, 4);
+ const __m128i vidx = _mm_slli_epi16(v, ShiftUp);
const int ridx = _mm_extract_epi16(vidx, 2);
const int gidx = _mm_extract_epi16(vidx, 1);
const int bidx = _mm_extract_epi16(vidx, 0);
@@ -62,7 +78,7 @@ public:
#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN
uint8x8_t v8 = vreinterpret_u8_u32(vmov_n_u32(rgb32));
uint16x4_t v16 = vget_low_u16(vmovl_u8(v8));
- const uint16x4_t vidx = vshl_n_u16(v16, 4);
+ const uint16x4_t vidx = vshl_n_u16(v16, ShiftUp);
const int ridx = vget_lane_u16(vidx, 2);
const int gidx = vget_lane_u16(vidx, 1);
const int bidx = vget_lane_u16(vidx, 0);
@@ -73,9 +89,9 @@ public:
v16 = vadd_u16(v16, vshr_n_u16(v16, 8));
return QRgba64::fromRgba64(vget_lane_u64(vreinterpret_u64_u16(v16), 0));
#else
- uint r = m_toLinear[qRed(rgb32) << 4];
- uint g = m_toLinear[qGreen(rgb32) << 4];
- uint b = m_toLinear[qBlue(rgb32) << 4];
+ uint r = m_toLinear[qRed(rgb32) << ShiftUp];
+ uint g = m_toLinear[qGreen(rgb32) << ShiftUp];
+ uint b = m_toLinear[qBlue(rgb32) << ShiftUp];
r = r + (r >> 8);
g = g + (g >> 8);
b = b + (b >> 8);
@@ -86,30 +102,30 @@ public:
QRgb toLinear(QRgb rgb32) const
{
- return convertWithTable(rgb32, m_toLinear);
+ return convertWithTable(rgb32, m_toLinear.get());
}
QRgba64 toLinear(QRgba64 rgb64) const
{
- return convertWithTable(rgb64, m_toLinear);
+ return convertWithTable(rgb64, m_toLinear.get());
}
float u8ToLinearF32(int c) const
{
- ushort v = m_toLinear[c << 4];
+ ushort v = m_toLinear[c << ShiftUp];
return v * (1.0f / (255*256));
}
float u16ToLinearF32(int c) const
{
c -= (c >> 8);
- ushort v = m_toLinear[c >> 4];
+ ushort v = m_toLinear[c >> ShiftDown];
return v * (1.0f / (255*256));
}
float toLinear(float f) const
{
- ushort v = m_toLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_toLinear[(int)(f * Resolution + 0.5f)];
return v * (1.0f / (255*256));
}
@@ -118,7 +134,7 @@ public:
#if defined(__SSE2__)
__m128i v = _mm_loadl_epi64(reinterpret_cast<const __m128i *>(&rgb64));
v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8));
- const __m128i vidx = _mm_srli_epi16(v, 4);
+ const __m128i vidx = _mm_srli_epi16(v, ShiftDown);
const int ridx = _mm_extract_epi16(vidx, 0);
const int gidx = _mm_extract_epi16(vidx, 1);
const int bidx = _mm_extract_epi16(vidx, 2);
@@ -132,7 +148,7 @@ public:
#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN
uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64));
v = vsub_u16(v, vshr_n_u16(v, 8));
- const uint16x4_t vidx = vshr_n_u16(v, 4);
+ const uint16x4_t vidx = vshr_n_u16(v, ShiftDown);
const int ridx = vget_lane_u16(vidx, 0);
const int gidx = vget_lane_u16(vidx, 1);
const int bidx = vget_lane_u16(vidx, 2);
@@ -151,56 +167,56 @@ public:
g = g - (g >> 8);
b = b - (b >> 8);
a = (a + 0x80) >> 8;
- r = (m_fromLinear[r >> 4] + 0x80) >> 8;
- g = (m_fromLinear[g >> 4] + 0x80) >> 8;
- b = (m_fromLinear[b >> 4] + 0x80) >> 8;
+ r = (m_fromLinear[r >> ShiftDown] + 0x80) >> 8;
+ g = (m_fromLinear[g >> ShiftDown] + 0x80) >> 8;
+ b = (m_fromLinear[b >> ShiftDown] + 0x80) >> 8;
return (a << 24) | (r << 16) | (g << 8) | b;
#endif
}
QRgb fromLinear(QRgb rgb32) const
{
- return convertWithTable(rgb32, m_fromLinear);
+ return convertWithTable(rgb32, m_fromLinear.get());
}
QRgba64 fromLinear(QRgba64 rgb64) const
{
- return convertWithTable(rgb64, m_fromLinear);
+ return convertWithTable(rgb64, m_fromLinear.get());
}
int u8FromLinearF32(float f) const
{
- ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_fromLinear[(int)(f * Resolution + 0.5f)];
return (v + 0x80) >> 8;
}
int u16FromLinearF32(float f) const
{
- ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_fromLinear[(int)(f * Resolution + 0.5f)];
return v + (v >> 8);
}
float fromLinear(float f) const
{
- ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_fromLinear[(int)(f * Resolution + 0.5f)];
return v * (1.0f / (255*256));
}
// We translate to 0-65280 (255*256) instead to 0-65535 to make simple
// shifting an accurate conversion.
- // We translate from 0-4080 (255*16) for the same speed up, and to keep
- // the tables small enough to fit in most inner caches.
- ushort m_toLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
- ushort m_fromLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
+ // We translate from 0->Resolution (4080 = 255*16) for the same speed up,
+ // and to keep the tables small enough to fit in most inner caches.
+ std::unique_ptr<ushort[]> m_toLinear; // [0->Resolution] -> [0-65280]
+ std::unique_ptr<ushort[]> m_fromLinear; // [0->Resolution] -> [0-65280]
private:
- QColorTrcLut() { } // force uninitialized members
+ QColorTrcLut() = default;
static std::shared_ptr<QColorTrcLut> create();
Q_ALWAYS_INLINE static QRgb convertWithTable(QRgb rgb32, const ushort *table)
{
- const int r = (table[qRed(rgb32) << 4] + 0x80) >> 8;
- const int g = (table[qGreen(rgb32) << 4] + 0x80) >> 8;
- const int b = (table[qBlue(rgb32) << 4] + 0x80) >> 8;
+ const int r = (table[qRed(rgb32) << ShiftUp] + 0x80) >> 8;
+ const int g = (table[qGreen(rgb32) << ShiftUp] + 0x80) >> 8;
+ const int b = (table[qBlue(rgb32) << ShiftUp] + 0x80) >> 8;
return (rgb32 & 0xff000000) | (r << 16) | (g << 8) | b;
}
Q_ALWAYS_INLINE static QRgba64 convertWithTable(QRgba64 rgb64, const ushort *table)
@@ -208,7 +224,7 @@ private:
#if defined(__SSE2__)
__m128i v = _mm_loadl_epi64(reinterpret_cast<const __m128i *>(&rgb64));
v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8));
- const __m128i vidx = _mm_srli_epi16(v, 4);
+ const __m128i vidx = _mm_srli_epi16(v, ShiftDown);
const int ridx = _mm_extract_epi16(vidx, 2);
const int gidx = _mm_extract_epi16(vidx, 1);
const int bidx = _mm_extract_epi16(vidx, 0);
@@ -222,7 +238,7 @@ private:
#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN
uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64));
v = vsub_u16(v, vshr_n_u16(v, 8));
- const uint16x4_t vidx = vshr_n_u16(v, 4);
+ const uint16x4_t vidx = vshr_n_u16(v, ShiftDown);
const int ridx = vget_lane_u16(vidx, 2);
const int gidx = vget_lane_u16(vidx, 1);
const int bidx = vget_lane_u16(vidx, 0);
@@ -238,9 +254,9 @@ private:
r = r - (r >> 8);
g = g - (g >> 8);
b = b - (b >> 8);
- r = table[r >> 4];
- g = table[g >> 4];
- b = table[b >> 4];
+ r = table[r >> ShiftDown];
+ g = table[g >> ShiftDown];
+ b = table[b >> ShiftDown];
r = r + (r >> 8);
g = g + (g >> 8);
b = b + (b >> 8);
diff --git a/src/gui/painting/qcoregraphics.mm b/src/gui/painting/qcoregraphics.mm
index 7b64106323..27b46202f5 100644
--- a/src/gui/painting/qcoregraphics.mm
+++ b/src/gui/painting/qcoregraphics.mm
@@ -185,7 +185,7 @@ QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size)
#endif // Q_OS_MACOS
-#ifdef Q_OS_IOS
+#ifdef QT_PLATFORM_UIKIT
QImage qt_mac_toQImage(const UIImage *image, QSizeF size)
{
@@ -202,7 +202,7 @@ QImage qt_mac_toQImage(const UIImage *image, QSizeF size)
return ret;
}
-#endif // Q_OS_IOS
+#endif // QT_PLATFORM_UIKIT
// ---------------------- Colors and Brushes ----------------------
diff --git a/src/gui/painting/qcoregraphics_p.h b/src/gui/painting/qcoregraphics_p.h
index f2c2ba1db1..a35f27a730 100644
--- a/src/gui/painting/qcoregraphics_p.h
+++ b/src/gui/painting/qcoregraphics_p.h
@@ -26,10 +26,8 @@
#if defined(__OBJC__)
# if defined(Q_OS_MACOS)
# include <AppKit/AppKit.h>
-# define HAVE_APPKIT
-# elif defined(Q_OS_IOS)
+# elif defined(QT_PLATFORM_UIKIT)
# include <UIKit/UIKit.h>
-# define HAVE_UIKIT
# endif
#endif
@@ -37,11 +35,11 @@ QT_BEGIN_NAMESPACE
Q_GUI_EXPORT CGBitmapInfo qt_mac_bitmapInfoForImage(const QImage &image);
-#ifdef HAVE_UIKIT
+#ifdef QT_PLATFORM_UIKIT
Q_GUI_EXPORT QImage qt_mac_toQImage(const UIImage *image, QSizeF size);
#endif
-#ifdef HAVE_APPKIT
+#ifdef Q_OS_MACOS
Q_GUI_EXPORT QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size);
QT_END_NAMESPACE
@@ -66,7 +64,7 @@ Q_GUI_EXPORT void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBou
Q_GUI_EXPORT void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform);
-#ifdef HAVE_APPKIT
+#ifdef Q_OS_MACOS
Q_GUI_EXPORT QColor qt_mac_toQColor(const NSColor *color);
Q_GUI_EXPORT QBrush qt_mac_toQBrush(const NSColor *color, QPalette::ColorGroup colorGroup = QPalette::Normal);
#endif
@@ -90,6 +88,4 @@ private:
QT_END_NAMESPACE
-#undef HAVE_APPKIT
-
#endif // QCOREGRAPHICS_P_H
diff --git a/src/gui/painting/qdatabuffer_p.h b/src/gui/painting/qdatabuffer_p.h
index 8f467afe4e..aa8335542d 100644
--- a/src/gui/painting/qdatabuffer_p.h
+++ b/src/gui/painting/qdatabuffer_p.h
@@ -16,7 +16,9 @@
//
#include <QtGui/private/qtguiglobal_p.h>
+
#include "QtCore/qbytearray.h"
+#include "QtCore/qtypeinfo.h"
#include <stdlib.h>
@@ -43,6 +45,7 @@ public:
~QDataBuffer()
{
+ static_assert(!QTypeInfo<Type>::isComplex);
if (buffer)
free(buffer);
}
diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp
index 218c9d1656..aaf57e19d1 100644
--- a/src/gui/painting/qdrawhelper.cpp
+++ b/src/gui/painting/qdrawhelper.cpp
@@ -176,7 +176,7 @@ static void QT_FASTCALL convertRGBA32FPMToRGBA64PM(QRgba64 *buffer, int count)
}
}
-static Convert64Func convert64ToRGBA64PM[QImage::NImageFormats] = {
+static Convert64Func convert64ToRGBA64PM[] = {
nullptr,
nullptr,
nullptr,
@@ -213,7 +213,10 @@ static Convert64Func convert64ToRGBA64PM[QImage::NImageFormats] = {
convertRGBA32FPMToRGBA64PM,
convertRGBA32FToRGBA64PM,
convertRGBA32FPMToRGBA64PM,
+ nullptr,
};
+
+static_assert(std::size(convert64ToRGBA64PM) == QImage::NImageFormats);
#endif
#if QT_CONFIG(raster_fp)
@@ -247,7 +250,7 @@ static void QT_FASTCALL convertRGBA16FToRGBA32F(QRgbaFloat32 *buffer, const quin
qFloatFromFloat16((float *)buffer, (const qfloat16 *)src, count * 4);
}
-static Convert64ToFPFunc convert64ToRGBA32F[QImage::NImageFormats] = {
+static Convert64ToFPFunc convert64ToRGBA32F[] = {
nullptr,
nullptr,
nullptr,
@@ -284,8 +287,11 @@ static Convert64ToFPFunc convert64ToRGBA32F[QImage::NImageFormats] = {
nullptr,
nullptr,
nullptr,
+ nullptr,
};
+static_assert(std::size(convert64ToRGBA32F) == QImage::NImageFormats);
+
static void convertRGBA32FToRGBA32FPM(QRgbaFloat32 *buffer, int count)
{
for (int i = 0; i < count; ++i)
@@ -353,7 +359,7 @@ static uint *QT_FASTCALL destFetchUndefined(uint *buffer, QRasterBuffer *, int,
return buffer;
}
-static DestFetchProc destFetchProc[QImage::NImageFormats] =
+static DestFetchProc destFetchProc[] =
{
nullptr, // Format_Invalid
destFetchMono, // Format_Mono,
@@ -391,8 +397,11 @@ static DestFetchProc destFetchProc[QImage::NImageFormats] =
destFetch, // Format_RGBX32FPx4
destFetch, // Format_RGBA32FPx4
destFetch, // Format_RGBA32FPx4_Premultiplied
+ destFetch, // Format_CMYK8888
};
+static_assert(std::size(destFetchProc) == QImage::NImageFormats);
+
#if QT_CONFIG(raster_64bit)
static QRgba64 *QT_FASTCALL destFetch64(QRgba64 *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length)
{
@@ -410,7 +419,7 @@ static QRgba64 * QT_FASTCALL destFetch64Undefined(QRgba64 *buffer, QRasterBuffer
return buffer;
}
-static DestFetchProc64 destFetchProc64[QImage::NImageFormats] =
+static DestFetchProc64 destFetchProc64[] =
{
nullptr, // Format_Invalid
nullptr, // Format_Mono,
@@ -448,7 +457,10 @@ static DestFetchProc64 destFetchProc64[QImage::NImageFormats] =
destFetch64, // Format_RGBX32FPx4
destFetch64, // Format_RGBA32FPx4
destFetch64, // Format_RGBA32FPx4_Premultiplied
+ destFetch64, // Format_CMYK8888
};
+
+static_assert(std::size(destFetchProc64) == QImage::NImageFormats);
#endif
#if QT_CONFIG(raster_fp)
@@ -466,7 +478,7 @@ static QRgbaFloat32 *QT_FASTCALL destFetchFPUndefined(QRgbaFloat32 *buffer, QRas
{
return buffer;
}
-static DestFetchProcFP destFetchProcFP[QImage::NImageFormats] =
+static DestFetchProcFP destFetchProcFP[] =
{
nullptr, // Format_Invalid
nullptr, // Format_Mono,
@@ -504,7 +516,10 @@ static DestFetchProcFP destFetchProcFP[QImage::NImageFormats] =
destFetchRGBFP, // Format_RGBX32FPx4
destFetchFP, // Format_RGBA32FPx4
destFetchRGBFP, // Format_RGBA32FPx4_Premultiplied
+ destFetchFP, // Format_CMYK8888
};
+
+static_assert(std::size(destFetchProcFP) == QImage::NImageFormats);
#endif
/*
@@ -629,7 +644,7 @@ static void QT_FASTCALL destStoreGray8(QRasterBuffer *rasterBuffer, int x, int y
QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ();
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
- tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
}
}
@@ -653,11 +668,11 @@ static void QT_FASTCALL destStoreGray16(QRasterBuffer *rasterBuffer, int x, int
QRgba64 tmp_line[BufferSize];
for (int k = 0; k < length; ++k)
tmp_line[k] = QRgba64::fromArgb32(buffer[k]);
- tfd->applyReturnGray(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied);
}
}
-static DestStoreProc destStoreProc[QImage::NImageFormats] =
+static DestStoreProc destStoreProc[] =
{
nullptr, // Format_Invalid
destStoreMono, // Format_Mono,
@@ -695,8 +710,11 @@ static DestStoreProc destStoreProc[QImage::NImageFormats] =
destStore, // Format_RGBX32FPx4
destStore, // Format_RGBA32FPx4
destStore, // Format_RGBA32FPx4_Premultiplied
+ destStore, // Format_CMYK8888
};
+static_assert(std::size(destStoreProc) == QImage::NImageFormats);
+
#if QT_CONFIG(raster_64bit)
static void QT_FASTCALL destStore64(QRasterBuffer *rasterBuffer, int x, int y, const QRgba64 *buffer, int length)
{
@@ -731,7 +749,7 @@ static void QT_FASTCALL destStore64Gray8(QRasterBuffer *rasterBuffer, int x, int
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
quint16 gray_line[BufferSize];
- tfd->applyReturnGray(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied);
for (int k = 0; k < length; ++k)
data[k] = qt_div_257(gray_line[k]);
}
@@ -753,11 +771,11 @@ static void QT_FASTCALL destStore64Gray16(QRasterBuffer *rasterBuffer, int x, in
QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb;
QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ();
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
- tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
}
}
-static DestStoreProc64 destStoreProc64[QImage::NImageFormats] =
+static DestStoreProc64 destStoreProc64[] =
{
nullptr, // Format_Invalid
nullptr, // Format_Mono,
@@ -795,7 +813,10 @@ static DestStoreProc64 destStoreProc64[QImage::NImageFormats] =
destStore64, // Format_RGBX32FPx4
destStore64, // Format_RGBA32FPx4
destStore64, // Format_RGBA32FPx4_Premultiplied
+ destStore64, // Format_CMYK8888
};
+
+static_assert(std::size(destStoreProc64) == QImage::NImageFormats);
#endif
#if QT_CONFIG(raster_fp)
@@ -3070,7 +3091,7 @@ static const QRgbaFloat32 *QT_FASTCALL fetchTransformedBilinearFP(QRgbaFloat32 *
#endif // QT_CONFIG(raster_fp)
// FetchUntransformed can have more specialized methods added depending on SIMD features.
-static SourceFetchProc sourceFetchUntransformed[QImage::NImageFormats] = {
+static SourceFetchProc sourceFetchUntransformed[] = {
nullptr, // Invalid
fetchUntransformed, // Mono
fetchUntransformed, // MonoLsb
@@ -3107,9 +3128,12 @@ static SourceFetchProc sourceFetchUntransformed[QImage::NImageFormats] = {
fetchUntransformed, // RGBX32Px4
fetchUntransformed, // RGBA32FPx4
fetchUntransformed, // RGBA32FPx4_Premultiplied
+ fetchUntransformed, // CMYK8888
};
-static const SourceFetchProc sourceFetchGeneric[NBlendTypes] = {
+static_assert(std::size(sourceFetchUntransformed) == QImage::NImageFormats);
+
+static const SourceFetchProc sourceFetchGeneric[] = {
fetchUntransformed, // Untransformed
fetchUntransformed, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPPNone>, // Transformed
@@ -3118,7 +3142,9 @@ static const SourceFetchProc sourceFetchGeneric[NBlendTypes] = {
fetchTransformedBilinear<BlendTransformedBilinearTiled, QPixelLayout::BPPNone> // TransformedBilinearTiled
};
-static SourceFetchProc sourceFetchARGB32PM[NBlendTypes] = {
+static_assert(std::size(sourceFetchGeneric) == NBlendTypes);
+
+static SourceFetchProc sourceFetchARGB32PM[] = {
fetchUntransformedARGB32PM, // Untransformed
fetchUntransformedARGB32PM, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPP32>, // Transformed
@@ -3127,7 +3153,9 @@ static SourceFetchProc sourceFetchARGB32PM[NBlendTypes] = {
fetchTransformedBilinearARGB32PM<BlendTransformedBilinearTiled> // BilinearTiled
};
-static SourceFetchProc sourceFetchAny16[NBlendTypes] = {
+static_assert(std::size(sourceFetchARGB32PM) == NBlendTypes);
+
+static SourceFetchProc sourceFetchAny16[] = {
fetchUntransformed, // Untransformed
fetchUntransformed, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPP16>, // Transformed
@@ -3136,7 +3164,9 @@ static SourceFetchProc sourceFetchAny16[NBlendTypes] = {
fetchTransformedBilinear<BlendTransformedBilinearTiled, QPixelLayout::BPP16> // TransformedBilinearTiled
};
-static SourceFetchProc sourceFetchAny32[NBlendTypes] = {
+static_assert(std::size(sourceFetchAny16) == NBlendTypes);
+
+static SourceFetchProc sourceFetchAny32[] = {
fetchUntransformed, // Untransformed
fetchUntransformed, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPP32>, // Transformed
@@ -3145,6 +3175,8 @@ static SourceFetchProc sourceFetchAny32[NBlendTypes] = {
fetchTransformedBilinear<BlendTransformedBilinearTiled, QPixelLayout::BPP32> // TransformedBilinearTiled
};
+static_assert(std::size(sourceFetchAny32) == NBlendTypes);
+
static inline SourceFetchProc getSourceFetch(TextureBlendType blendType, QImage::Format format)
{
if (format == QImage::Format_RGB32 || format == QImage::Format_ARGB32_Premultiplied)
@@ -3159,7 +3191,7 @@ static inline SourceFetchProc getSourceFetch(TextureBlendType blendType, QImage:
}
#if QT_CONFIG(raster_64bit)
-static const SourceFetchProc64 sourceFetchGeneric64[NBlendTypes] = {
+static const SourceFetchProc64 sourceFetchGeneric64[] = {
fetchUntransformed64, // Untransformed
fetchUntransformed64, // Tiled
fetchTransformed64<BlendTransformed>, // Transformed
@@ -3168,7 +3200,9 @@ static const SourceFetchProc64 sourceFetchGeneric64[NBlendTypes] = {
fetchTransformedBilinear64<BlendTransformedBilinearTiled> // BilinearTiled
};
-static const SourceFetchProc64 sourceFetchRGBA64PM[NBlendTypes] = {
+static_assert(std::size(sourceFetchGeneric64) == NBlendTypes);
+
+static const SourceFetchProc64 sourceFetchRGBA64PM[] = {
fetchUntransformedRGBA64PM, // Untransformed
fetchUntransformedRGBA64PM, // Tiled
fetchTransformed64<BlendTransformed>, // Transformed
@@ -3177,6 +3211,8 @@ static const SourceFetchProc64 sourceFetchRGBA64PM[NBlendTypes] = {
fetchTransformedBilinear64<BlendTransformedBilinearTiled> // BilinearTiled
};
+static_assert(std::size(sourceFetchRGBA64PM) == NBlendTypes);
+
static inline SourceFetchProc64 getSourceFetch64(TextureBlendType blendType, QImage::Format format)
{
if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64_Premultiplied)
@@ -3186,7 +3222,7 @@ static inline SourceFetchProc64 getSourceFetch64(TextureBlendType blendType, QIm
#endif
#if QT_CONFIG(raster_fp)
-static const SourceFetchProcFP sourceFetchGenericFP[NBlendTypes] = {
+static const SourceFetchProcFP sourceFetchGenericFP[] = {
fetchUntransformedFP, // Untransformed
fetchUntransformedFP, // Tiled
fetchTransformedFP<BlendTransformed>, // Transformed
@@ -3195,6 +3231,8 @@ static const SourceFetchProcFP sourceFetchGenericFP[NBlendTypes] = {
fetchTransformedBilinearFP<BlendTransformedBilinearTiled> // BilinearTiled
};
+static_assert(std::size(sourceFetchGenericFP) == NBlendTypes);
+
static inline SourceFetchProcFP getSourceFetchFP(TextureBlendType blendType, QImage::Format /*format*/)
{
return sourceFetchGenericFP[blendType];
@@ -3612,7 +3650,6 @@ static inline Operator getOperator(const QSpanData *data, const QT_FT_Span *span
{
Operator op;
bool solidSource = false;
-
switch(data->type) {
case QSpanData::Solid:
solidSource = data->solidColor.alphaF() >= 1.0f;
@@ -4973,16 +5010,11 @@ void qBlendTexture(int count, const QT_FT_Span *spans, void *userData)
proc(count, spans, userData);
}
-static void blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, void *userData)
+static inline bool calculate_fixed_gradient_factors(int count, const QT_FT_Span *spans,
+ const QSpanData *data,
+ const LinearGradientValues &linear,
+ int *pyinc, int *poff)
{
- QSpanData *data = reinterpret_cast<QSpanData *>(userData);
-
- LinearGradientValues linear;
- getLinearGradientValues(&linear, data);
-
- CompositionFunctionSolid funcSolid =
- functionForModeSolid[data->rasterBuffer->compositionMode];
-
/*
The logic for vertical gradient calculations is a mathematically
reduced copy of that in fetchLinearGradient() - which is basically:
@@ -4997,8 +5029,32 @@ static void blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, voi
This has then been converted to fixed point to improve performance.
*/
const int gss = GRADIENT_STOPTABLE_SIZE - 1;
- int yinc = int((linear.dy * data->m22 * gss) * FIXPT_SIZE);
- int off = int((((linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss) * FIXPT_SIZE));
+ qreal ryinc = linear.dy * data->m22 * gss * FIXPT_SIZE;
+ qreal roff = (linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss * FIXPT_SIZE;
+ const int limit = std::numeric_limits<int>::max() - FIXPT_SIZE;
+ if (count && (std::fabs(ryinc) < limit) && (std::fabs(roff) < limit)
+ && (std::fabs(ryinc * spans->y + roff) < limit)
+ && (std::fabs(ryinc * (spans + count - 1)->y + roff) < limit)) {
+ *pyinc = int(ryinc);
+ *poff = int(roff);
+ return true;
+ }
+ return false;
+}
+
+static bool blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, void *userData)
+{
+ QSpanData *data = reinterpret_cast<QSpanData *>(userData);
+
+ LinearGradientValues linear;
+ getLinearGradientValues(&linear, data);
+
+ CompositionFunctionSolid funcSolid =
+ functionForModeSolid[data->rasterBuffer->compositionMode];
+
+ int yinc(0), off(0);
+ if (!calculate_fixed_gradient_factors(count, spans, data, linear, &yinc, &off))
+ return false;
while (count--) {
int y = spans->y;
@@ -5011,21 +5067,20 @@ static void blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, voi
funcSolid(dst, spans->len, color, spans->coverage);
++spans;
}
+ return true;
}
template<ProcessSpans blend_color>
-static void blend_vertical_gradient(int count, const QT_FT_Span *spans, void *userData)
+static bool blend_vertical_gradient(int count, const QT_FT_Span *spans, void *userData)
{
QSpanData *data = reinterpret_cast<QSpanData *>(userData);
LinearGradientValues linear;
getLinearGradientValues(&linear, data);
- // Based on the same logic as blend_vertical_gradient_argb.
-
- const int gss = GRADIENT_STOPTABLE_SIZE - 1;
- int yinc = int((linear.dy * data->m22 * gss) * FIXPT_SIZE);
- int off = int((((linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss) * FIXPT_SIZE));
+ int yinc(0), off(0);
+ if (!calculate_fixed_gradient_factors(count, spans, data, linear, &yinc, &off))
+ return false;
while (count--) {
int y = spans->y;
@@ -5038,6 +5093,7 @@ static void blend_vertical_gradient(int count, const QT_FT_Span *spans, void *us
blend_color(1, spans, userData);
++spans;
}
+ return true;
}
void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
@@ -5052,8 +5108,8 @@ void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
break;
case QImage::Format_RGB32:
case QImage::Format_ARGB32_Premultiplied:
- if (isVerticalGradient)
- return blend_vertical_gradient_argb(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient_argb(count, spans, userData))
+ return;
return blend_src_generic(count, spans, userData);
#if defined(__SSE2__) || defined(__ARM_NEON__) || (Q_PROCESSOR_WORDSIZE == 8)
case QImage::Format_ARGB32:
@@ -5075,8 +5131,8 @@ void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
case QImage::Format_RGBA32FPx4_Premultiplied:
#endif
#if QT_CONFIG(raster_64bit)
- if (isVerticalGradient)
- return blend_vertical_gradient<blend_color_generic_rgb64>(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient<blend_color_generic_rgb64>(count, spans, userData))
+ return;
return blend_src_generic_rgb64(count, spans, userData);
#endif // QT_CONFIG(raster_64bit)
#if QT_CONFIG(raster_fp)
@@ -5086,13 +5142,13 @@ void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
case QImage::Format_RGBX32FPx4:
case QImage::Format_RGBA32FPx4:
case QImage::Format_RGBA32FPx4_Premultiplied:
- if (isVerticalGradient)
- return blend_vertical_gradient<blend_color_generic_fp>(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient<blend_color_generic_fp>(count, spans, userData))
+ return;
return blend_src_generic_fp(count, spans, userData);
#endif
default:
- if (isVerticalGradient)
- return blend_vertical_gradient<blend_color_generic>(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient<blend_color_generic>(count, spans, userData))
+ return;
return blend_src_generic(count, spans, userData);
}
Q_UNREACHABLE();
@@ -5962,7 +6018,7 @@ static void qt_rectfill_fp32x4(QRasterBuffer *rasterBuffer,
// Map table for destination image format. Contains function pointers
// for blends of various types unto the destination
-DrawHelper qDrawHelper[QImage::NImageFormats] =
+DrawHelper qDrawHelper[] =
{
// Format_Invalid,
{ nullptr, nullptr, nullptr, nullptr, nullptr },
@@ -6239,6 +6295,8 @@ DrawHelper qDrawHelper[QImage::NImageFormats] =
},
};
+static_assert(std::size(qDrawHelper) == QImage::NImageFormats);
+
#if !defined(Q_PROCESSOR_X86)
void qt_memfill64(quint64 *dest, quint64 color, qsizetype count)
{
diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp
index 77f1a7aa08..70bdf28b21 100644
--- a/src/gui/painting/qicc.cpp
+++ b/src/gui/painting/qicc.cpp
@@ -116,6 +116,7 @@ enum class Tag : quint32 {
mAB_ = IccTag('m', 'A', 'B', ' '),
mBA_ = IccTag('m', 'B', 'A', ' '),
chad = IccTag('c', 'h', 'a', 'd'),
+ cicp = IccTag('c', 'i', 'c', 'p'),
gamt = IccTag('g', 'a', 'm', 't'),
sf32 = IccTag('s', 'f', '3', '2'),
@@ -244,7 +245,14 @@ struct mpetTagData : GenericTagData {
};
struct Sf32TagData : GenericTagData {
- quint32_be value[1];
+ quint32_be value[9];
+};
+
+struct CicpTagData : GenericTagData {
+ quint8 colorPrimaries;
+ quint8 transferCharacteristics;
+ quint8 matrixCoefficients;
+ quint8 videoFullRangeFlag;
};
struct MatrixElement {
@@ -264,7 +272,11 @@ struct MatrixElement {
static int toFixedS1516(float x)
{
- return int(x * 65536.0f + 0.5f);
+ if (x < float(SHRT_MIN))
+ return INT_MIN;
+ if (x > float(SHRT_MAX))
+ return INT_MAX;
+ return qRound(x * 65536.0f);
}
static float fromFixedS1516(int x)
@@ -297,12 +309,13 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
return false;
}
if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
- && header.inputColorSpace != uint(ColorSpaceType::Gray)) {
+ && header.inputColorSpace != uint(ColorSpaceType::Gray)
+ && header.inputColorSpace != uint(ColorSpaceType::Cmyk)) {
qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace));
return false;
}
if (header.pcs != uint(Tag::XYZ_) && header.pcs != uint(Tag::Lab_)) {
- qCInfo(lcIcc, "Unsupported ICC profile connection space 0x%x", quint32(header.pcs));
+ qCInfo(lcIcc, "Invalid ICC profile connection space 0x%x", quint32(header.pcs));
return false;
}
@@ -326,7 +339,7 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
return 12;
}
- if (trc.m_type == QColorTrc::Type::Function) {
+ if (trc.m_type == QColorTrc::Type::ParameterizedFunction) {
const QColorTransferFunction &fun = trc.m_fun;
stream << uint(Tag::para) << uint(0);
if (fun.isGamma()) {
@@ -347,6 +360,14 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
stream << toFixedS1516(fun.m_f);
return 12 + 7 * 4;
}
+ if (trc.m_type != QColorTrc::Type::Table) {
+ stream << uint(Tag::curv) << uint(0);
+ stream << uint(16);
+ for (uint i = 0; i < 16; ++i) {
+ stream << ushort(qBound(0, qRound(trc.apply(i / 15.f) * 65535.f), 65535));
+ }
+ return 12 + 16 * 2;
+ }
Q_ASSERT(trc.m_type == QColorTrc::Type::Table);
stream << uint(Tag::curv) << uint(0);
@@ -367,24 +388,442 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
return 12 + 2 * trc.m_table.m_tableSize;
}
+// very simple version for small values (<=4) of exp.
+static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
+{
+ return (exp <= 1) ? x : x * intPow(x, exp - 1);
+}
+
+struct ElementCombo
+{
+ const QColorMatrix *inMatrix = nullptr;
+ const QColorSpacePrivate::TransferElement *inTable = nullptr;
+ const QColorCLUT *clut = nullptr;
+ const QColorSpacePrivate::TransferElement *midTable = nullptr;
+ const QColorMatrix *midMatrix = nullptr;
+ const QColorVector *midOffset = nullptr;
+ const QColorSpacePrivate::TransferElement *outTable = nullptr;
+};
+
+static void visitElement(ElementCombo &combo, const QColorSpacePrivate::TransferElement &element, int number,
+ const QList<QColorSpacePrivate::Element> &list)
+{
+ if (number == 0)
+ combo.inTable = &element;
+ else if (number == list.size() - 1)
+ combo.outTable = &element;
+ else if (number == 1 && combo.inMatrix)
+ combo.inTable = &element;
+ else
+ combo.midTable = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorMatrix &element, int number,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ if (number == 0)
+ combo.inMatrix = &element;
+ else
+ combo.midMatrix = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorVector &element, int,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ combo.midOffset = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorCLUT &element, int,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ combo.clut = &element;
+}
+
+static bool isTableTrc(const QColorSpacePrivate::TransferElement *transfer)
+{
+ int i = 0;
+ while (i < 4 && transfer->trc[i].isValid()) {
+ if (transfer->trc[i].m_type != QColorTrc::Type::Table)
+ return false;
+ i++;
+ }
+ return i > 0;
+}
+
+static bool isTableTrcSingleSize(const QColorSpacePrivate::TransferElement *transfer)
+{
+ Q_ASSERT(transfer->trc[0].m_type == QColorTrc::Type::Table);
+ int i = 1;
+ const uint32_t size = transfer->trc[0].table().m_tableSize;
+ while (i < 4 && transfer->trc[i].isValid()) {
+ if (transfer->trc[i].table().m_tableSize != size)
+ return false;
+ i++;
+ }
+ return true;
+}
+
+static int writeMab(QDataStream &stream, const QList<QColorSpacePrivate::Element> &abList, bool isAb, bool pcsLab, bool isCmyk)
+{
+ int number = 0;
+ ElementCombo combo;
+ for (auto &&element : abList)
+ std::visit([&](auto &&elm) { visitElement(combo, elm, number++, abList); }, element);
+
+ Q_ASSERT(!(combo.inMatrix && combo.midMatrix));
+
+ // qWarning() << Q_FUNC_INFO << bool(combo.inMatrix) << bool(combo.inTable) << bool(combo.clut) << bool(combo.midTable) << bool(combo.midMatrix) << bool(combo.midOffset) << bool(combo.outTable);
+ bool lut16 = true;
+ if (combo.midMatrix || combo.midTable || combo.midOffset)
+ lut16 = false;
+ if (combo.clut && (combo.clut->gridPointsX != combo.clut->gridPointsY ||
+ combo.clut->gridPointsX != combo.clut->gridPointsZ ||
+ (combo.clut->gridPointsW > 1 && combo.clut->gridPointsX != combo.clut->gridPointsW)))
+ lut16 = false;
+ if (lut16 && combo.inTable)
+ lut16 = isTableTrc(combo.inTable) && isTableTrcSingleSize(combo.inTable);
+ if (lut16 && combo.outTable)
+ lut16 = isTableTrc(combo.outTable) && isTableTrcSingleSize(combo.outTable);
+
+ if (!lut16) {
+ if (combo.inMatrix)
+ qSwap(combo.inMatrix, combo.midMatrix);
+ if (isAb)
+ stream << uint(Tag::mAB_) << uint(0);
+ else
+ stream << uint(Tag::mBA_) << uint(0);
+ } else {
+ stream << uint(Tag::mft2) << uint(0);
+ }
+
+ const int inChannels = (isCmyk && isAb) ? 4 : 3;
+ const int outChannels = (isCmyk && !isAb) ? 4 : 3;
+ stream << uchar(inChannels) << uchar(outChannels);
+ qsizetype gridPointsLut16 = 0;
+ if (lut16 && combo.clut)
+ gridPointsLut16 = combo.clut->gridPointsX;
+ if (lut16)
+ stream << uchar(gridPointsLut16) << uchar(0);
+ else
+ stream << quint16(0);
+ if (lut16) {
+ if (combo.inMatrix) {
+ stream << toFixedS1516(combo.inMatrix->r.x);
+ stream << toFixedS1516(combo.inMatrix->g.x);
+ stream << toFixedS1516(combo.inMatrix->b.x);
+ stream << toFixedS1516(combo.inMatrix->r.y);
+ stream << toFixedS1516(combo.inMatrix->g.y);
+ stream << toFixedS1516(combo.inMatrix->b.y);
+ stream << toFixedS1516(combo.inMatrix->r.z);
+ stream << toFixedS1516(combo.inMatrix->g.z);
+ stream << toFixedS1516(combo.inMatrix->b.z);
+ } else {
+ stream << toFixedS1516(1.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(1.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(1.0f);
+ }
+ int inputEntries = 0, outputEntries = 0;
+ if (combo.inTable)
+ inputEntries = combo.inTable->trc[0].table().m_tableSize;
+ else
+ inputEntries = 2;
+ if (combo.outTable)
+ outputEntries = combo.outTable->trc[0].table().m_tableSize;
+ else
+ outputEntries = 2;
+ stream << quint16(inputEntries);
+ stream << quint16(outputEntries);
+ auto writeTable = [&](const QColorSpacePrivate::TransferElement *table, int entries, int channels) {
+ if (table) {
+ for (int j = 0; j < channels; ++j) {
+ if (!table->trc[j].table().m_table16.isEmpty()) {
+ for (int i = 0; i < entries; ++i)
+ stream << table->trc[j].table().m_table16[i];
+ } else {
+ for (int i = 0; i < entries; ++i)
+ stream << quint16(table->trc[j].table().m_table8[i] * 257);
+ }
+ }
+ } else {
+ for (int j = 0; j < channels; ++j)
+ stream << quint16(0) << quint16(65535);
+ }
+ };
+
+ writeTable(combo.inTable, inputEntries, inChannels);
+
+ if (combo.clut) {
+ if (isAb && pcsLab) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65280.0f + 0.5f);
+ stream << quint16(v.y * 65280.0f + 0.5f);
+ stream << quint16(v.z * 65280.0f + 0.5f);
+ }
+ } else {
+ if (outChannels == 4) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65535.0f + 0.5f);
+ stream << quint16(v.y * 65535.0f + 0.5f);
+ stream << quint16(v.z * 65535.0f + 0.5f);
+ stream << quint16(v.w * 65535.0f + 0.5f);
+ }
+ } else {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65535.0f + 0.5f);
+ stream << quint16(v.y * 65535.0f + 0.5f);
+ stream << quint16(v.z * 65535.0f + 0.5f);
+ }
+ }
+ }
+ }
+
+ writeTable(combo.outTable, outputEntries, outChannels);
+
+ qsizetype offset = sizeof(Lut16TagData) + 2 * inChannels * inputEntries
+ + 2 * outChannels * outputEntries
+ + 2 * outChannels * intPow(gridPointsLut16, inChannels);
+ if (offset & 0x2) {
+ stream << quint16(0);
+ offset += 2;
+ }
+ return offset;
+ } else {
+ // mAB/mBA tag:
+ if (isAb) {
+ if (!combo.clut && combo.inTable && combo.midMatrix && !combo.midTable)
+ std::swap(combo.inTable, combo.midTable);
+ } else {
+ if (!combo.clut && combo.outTable && combo.midMatrix && !combo.midTable)
+ std::swap(combo.outTable, combo.midTable);
+ }
+ quint32 offset = sizeof(mABTagData);
+ QBuffer buffer2;
+ buffer2.open(QIODevice::WriteOnly);
+ QDataStream stream2(&buffer2);
+ quint32 bOffset = offset;
+ quint32 matrixOffset = 0;
+ quint32 mOffset = 0;
+ quint32 clutOffset = 0;
+ quint32 aOffset = 0;
+ // Tags must start on 4 byte offsets, but sampled curves might have sizes 2-byte aligned
+ auto alignTag = [&]() {
+ if (offset & 0x2) {
+ stream2 << quint16(0);
+ offset += 2;
+ }
+ };
+
+ const QColorSpacePrivate::TransferElement *aCurve, *bCurve;
+ int aChannels;
+ if (isAb) {
+ aCurve = combo.inTable;
+ aChannels = inChannels;
+ bCurve = combo.outTable;
+ Q_ASSERT(outChannels == 3);
+ } else {
+ aCurve = combo.outTable;
+ aChannels = outChannels;
+ bCurve = combo.inTable;
+ Q_ASSERT(inChannels == 3);
+ }
+ if (bCurve) {
+ offset += writeColorTrc(stream2, bCurve->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, bCurve->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, bCurve->trc[2]);
+ alignTag();
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * 3;
+ }
+ if (combo.midMatrix || combo.midOffset || combo.midTable) {
+ matrixOffset = offset;
+ if (combo.midMatrix) {
+ stream2 << toFixedS1516(combo.midMatrix->r.x);
+ stream2 << toFixedS1516(combo.midMatrix->g.x);
+ stream2 << toFixedS1516(combo.midMatrix->b.x);
+ stream2 << toFixedS1516(combo.midMatrix->r.y);
+ stream2 << toFixedS1516(combo.midMatrix->g.y);
+ stream2 << toFixedS1516(combo.midMatrix->b.y);
+ stream2 << toFixedS1516(combo.midMatrix->r.z);
+ stream2 << toFixedS1516(combo.midMatrix->g.z);
+ stream2 << toFixedS1516(combo.midMatrix->b.z);
+ } else {
+ stream2 << toFixedS1516(1.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(1.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(1.0f);
+ }
+ if (combo.midOffset) {
+ stream2 << toFixedS1516(combo.midOffset->x);
+ stream2 << toFixedS1516(combo.midOffset->y);
+ stream2 << toFixedS1516(combo.midOffset->z);
+ } else {
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ }
+ offset += 12 * 4;
+ mOffset = offset;
+ if (combo.midTable) {
+ offset += writeColorTrc(stream2, combo.midTable->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, combo.midTable->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, combo.midTable->trc[2]);
+ alignTag();
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * 3;
+ }
+ }
+ if (combo.clut || aCurve) {
+ clutOffset = offset;
+ if (combo.clut) {
+ stream2 << uchar(combo.clut->gridPointsX);
+ stream2 << uchar(combo.clut->gridPointsY);
+ stream2 << uchar(combo.clut->gridPointsZ);
+ if (inChannels == 4)
+ stream2 << uchar(combo.clut->gridPointsW);
+ else
+ stream2 << uchar(0);
+ for (int i = 0; i < 12; ++i)
+ stream2 << uchar(0);
+ stream2 << uchar(2) << uchar(0) << uchar(0) << uchar(0);
+ offset += 20;
+ if (outChannels == 4) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream2 << quint16(v.x * 65535.0f + 0.5f);
+ stream2 << quint16(v.y * 65535.0f + 0.5f);
+ stream2 << quint16(v.z * 65535.0f + 0.5f);
+ stream2 << quint16(v.w * 65535.0f + 0.5f);
+ }
+ } else {
+ for (const QColorVector &v : combo.clut->table) {
+ stream2 << quint16(v.x * 65535.0f + 0.5f);
+ stream2 << quint16(v.y * 65535.0f + 0.5f);
+ stream2 << quint16(v.z * 65535.0f + 0.5f);
+ }
+ }
+ offset += 2 * outChannels * combo.clut->table.size();
+ alignTag();
+ } else {
+ for (int i = 0; i < 16; ++i)
+ stream2 << uchar(0);
+ stream2 << uchar(1) << uchar(0) << uchar(0) << uchar(0);
+ offset += 20;
+ }
+ aOffset = offset;
+ if (aCurve) {
+ offset += writeColorTrc(stream2, aCurve->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, aCurve->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, aCurve->trc[2]);
+ alignTag();
+ if (aChannels == 4) {
+ offset += writeColorTrc(stream2, aCurve->trc[3]);
+ alignTag();
+ }
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ if (aChannels == 4)
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * aChannels;
+ }
+ }
+ buffer2.close();
+ QByteArray tagData = buffer2.buffer();
+ stream << quint32(bOffset);
+ stream << quint32(matrixOffset);
+ stream << quint32(mOffset);
+ stream << quint32(clutOffset);
+ stream << quint32(aOffset);
+ stream.writeRawData(tagData.data(), tagData.size());
+
+ return int(sizeof(mABTagData) + tagData.size());
+ }
+}
+
QByteArray toIccProfile(const QColorSpace &space)
{
if (!space.isValid())
return QByteArray();
const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
- // This should catch anything not three component matrix based as we can only get that from parsed ICC
if (!spaceDPtr->iccProfile.isEmpty())
return spaceDPtr->iccProfile;
- Q_ASSERT(spaceDPtr->isThreeComponentMatrix());
- constexpr int tagCount = 9;
- constexpr uint profileDataOffset = 128 + 4 + 12 * tagCount;
- constexpr uint variableTagTableOffsets = 128 + 4 + 12 * 5;
+ int fixedLengthTagCount = 5;
+ if (!spaceDPtr->isThreeComponentMatrix())
+ fixedLengthTagCount = 2;
+ else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ fixedLengthTagCount = 2;
+ bool writeChad = false;
+ bool writeB2a = true;
+ bool writeCicp = false;
+ if (spaceDPtr->isThreeComponentMatrix() && !spaceDPtr->chad.isIdentity()) {
+ writeChad = true;
+ fixedLengthTagCount++;
+ }
+ int varLengthTagCount = 4;
+ if (!spaceDPtr->isThreeComponentMatrix())
+ varLengthTagCount = 3;
+ else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ varLengthTagCount = 2;
+
+ if (!space.isValidTarget()) {
+ writeB2a = false;
+ Q_ASSERT(!spaceDPtr->isThreeComponentMatrix());
+ varLengthTagCount--;
+ }
+ switch (spaceDPtr->transferFunction) {
+ case QColorSpace::TransferFunction::St2084:
+ case QColorSpace::TransferFunction::Hlg:
+ writeCicp = true;
+ fixedLengthTagCount++;
+ break;
+ default:
+ break;
+ }
+
+ const int tagCount = fixedLengthTagCount + varLengthTagCount;
+ const uint profileDataOffset = 128 + 4 + 12 * tagCount;
+ uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
+
uint currentOffset = 0;
- uint rTrcOffset, gTrcOffset, bTrcOffset;
- uint rTrcSize, gTrcSize, bTrcSize;
- uint descOffset, descSize;
+ uint rTrcOffset = 0;
+ uint gTrcOffset = 0;
+ uint bTrcOffset = 0;
+ uint kTrcOffset = 0;
+ uint rTrcSize = 0;
+ uint gTrcSize = 0;
+ uint bTrcSize = 0;
+ uint kTrcSize = 0;
+ uint descOffset = 0;
+ uint descSize = 0;
+ uint mA2bOffset = 0;
+ uint mB2aOffset = 0;
+ uint mA2bSize = 0;
+ uint mB2aSize = 0;
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
@@ -393,15 +832,27 @@ QByteArray toIccProfile(const QColorSpace &space)
// Profile header:
stream << uint(0); // Size, we will update this later
stream << uint(0);
- stream << uint(0x02400000); // Version 2.4 (note we use 'para' from version 4)
+ stream << uint(0x04400000); // Version 4.4
stream << uint(ProfileClass::Display);
- stream << uint(Tag::RGB_);
+ switch (spaceDPtr->colorModel) {
+ case QColorSpace::ColorModel::Rgb:
+ stream << uint(ColorSpaceType::Rgb);
+ break;
+ case QColorSpace::ColorModel::Gray:
+ stream << uint(ColorSpaceType::Gray);
+ break;
+ case QColorSpace::ColorModel::Cmyk:
+ stream << uint(ColorSpaceType::Cmyk);
+ break;
+ case QColorSpace::ColorModel::Undefined:
+ Q_UNREACHABLE();
+ }
stream << (spaceDPtr->isPcsLab ? uint(Tag::Lab_) : uint(Tag::XYZ_));
stream << uint(0) << uint(0) << uint(0);
stream << uint(Tag::acsp);
stream << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0);
- stream << uint(1); // Rendering intent
+ stream << uint(0); // Rendering intent
stream << uint(0x0000f6d6); // D50 X
stream << uint(0x00010000); // D50 Y
stream << uint(0x0000d32d); // D50 Z
@@ -409,73 +860,164 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << uint(0) << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
- // Tag table:
- stream << uint(tagCount);
- stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
- stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
- stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
- stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
- stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(12);
- // From here the offset and size will be updated later:
- stream << uint(Tag::rTRC) << uint(0) << uint(0);
- stream << uint(Tag::gTRC) << uint(0) << uint(0);
- stream << uint(Tag::bTRC) << uint(0) << uint(0);
- stream << uint(Tag::desc) << uint(0) << uint(0);
- // TODO: consider adding 'chad' tag (required in ICC >=4 when we have non-D50 whitepoint)
currentOffset = profileDataOffset;
+ if (spaceDPtr->isThreeComponentMatrix()) {
+ // Tag table:
+ stream << uint(tagCount);
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::rXYZ) << uint(currentOffset + 00) << uint(20);
+ stream << uint(Tag::gXYZ) << uint(currentOffset + 20) << uint(20);
+ stream << uint(Tag::bXYZ) << uint(currentOffset + 40) << uint(20);
+ currentOffset += 20 + 20 + 20;
+ }
+ stream << uint(Tag::wtpt) << uint(currentOffset + 00) << uint(20);
+ stream << uint(Tag::cprt) << uint(currentOffset + 20) << uint(34);
+ currentOffset += 20 + 34 + 2;
+ if (writeChad) {
+ stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
+ currentOffset += 44;
+ }
+ if (writeCicp) {
+ stream << uint(Tag::cicp) << uint(currentOffset) << uint(12);
+ currentOffset += 12;
+ }
+ // From here the offset and size will be updated later:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::rTRC) << uint(0) << uint(0);
+ stream << uint(Tag::gTRC) << uint(0) << uint(0);
+ stream << uint(Tag::bTRC) << uint(0) << uint(0);
+ } else {
+ stream << uint(Tag::kTRC) << uint(0) << uint(0);
+ }
+ stream << uint(Tag::desc) << uint(0) << uint(0);
+
+ // Tag data:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.z);
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.z);
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.z);
+ }
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->whitePoint.x);
+ stream << toFixedS1516(spaceDPtr->whitePoint.y);
+ stream << toFixedS1516(spaceDPtr->whitePoint.z);
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(6) << uint(28);
+ stream << ushort('N') << ushort('/') << ushort('A');
+ stream << ushort(0); // 4-byte alignment
+ if (writeChad) {
+ const QColorMatrix &chad = spaceDPtr->chad;
+ stream << uint(Tag::sf32) << uint(0);
+ stream << toFixedS1516(chad.r.x);
+ stream << toFixedS1516(chad.g.x);
+ stream << toFixedS1516(chad.b.x);
+ stream << toFixedS1516(chad.r.y);
+ stream << toFixedS1516(chad.g.y);
+ stream << toFixedS1516(chad.b.y);
+ stream << toFixedS1516(chad.r.z);
+ stream << toFixedS1516(chad.g.z);
+ stream << toFixedS1516(chad.b.z);
+ }
+ if (writeCicp) {
+ stream << uint(Tag::cicp) << uint(0);
+ stream << uchar(2); // Unspecified primaries
+ if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::St2084)
+ stream << uchar(16);
+ else if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::Hlg)
+ stream << uchar(18);
+ else
+ Q_UNREACHABLE();
+ stream << uchar(0); // Only for YCbCr, otherwise 0
+ stream << uchar(1); // Video full range
+ }
- // Tag data:
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.r.x);
- stream << toFixedS1516(spaceDPtr->toXyz.r.y);
- stream << toFixedS1516(spaceDPtr->toXyz.r.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.g.x);
- stream << toFixedS1516(spaceDPtr->toXyz.g.y);
- stream << toFixedS1516(spaceDPtr->toXyz.g.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.b.x);
- stream << toFixedS1516(spaceDPtr->toXyz.b.y);
- stream << toFixedS1516(spaceDPtr->toXyz.b.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->whitePoint.x);
- stream << toFixedS1516(spaceDPtr->whitePoint.y);
- stream << toFixedS1516(spaceDPtr->whitePoint.z);
- stream << uint(Tag::text) << uint(0);
- stream << uint(IccTag('N', '/', 'A', '\0'));
- currentOffset += 92;
-
- // From now on the data is variable sized:
- rTrcOffset = currentOffset;
- rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
- currentOffset += rTrcSize;
- if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
- gTrcOffset = rTrcOffset;
- gTrcSize = rTrcSize;
- } else {
- gTrcOffset = currentOffset;
- gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
- currentOffset += gTrcSize;
- }
- if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
- bTrcOffset = rTrcOffset;
- bTrcSize = rTrcSize;
+ // From now on the data is variable sized:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ rTrcOffset = currentOffset;
+ rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
+ currentOffset += rTrcSize;
+ if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
+ gTrcOffset = rTrcOffset;
+ gTrcSize = rTrcSize;
+ } else {
+ gTrcOffset = currentOffset;
+ gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
+ currentOffset += gTrcSize;
+ }
+ if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
+ bTrcOffset = rTrcOffset;
+ bTrcSize = rTrcSize;
+ } else {
+ bTrcOffset = currentOffset;
+ bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
+ currentOffset += bTrcSize;
+ }
+ } else {
+ Q_ASSERT(spaceDPtr->colorModel == QColorSpace::ColorModel::Gray);
+ kTrcOffset = currentOffset;
+ kTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
+ currentOffset += kTrcSize;
+ }
} else {
- bTrcOffset = currentOffset;
- bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
- currentOffset += bTrcSize;
+ // Tag table:
+ stream << uint(tagCount);
+ stream << uint(Tag::wtpt) << uint(profileDataOffset + 00) << uint(20);
+ stream << uint(Tag::cprt) << uint(profileDataOffset + 20) << uint(34);
+ currentOffset += 20 + 34 + 2;
+ // From here the offset and size will be updated later:
+ stream << uint(Tag::A2B0) << uint(0) << uint(0);
+ if (writeB2a)
+ stream << uint(Tag::B2A0) << uint(0) << uint(0);
+ stream << uint(Tag::desc) << uint(0) << uint(0);
+
+ // Fixed tag data
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->whitePoint.x);
+ stream << toFixedS1516(spaceDPtr->whitePoint.y);
+ stream << toFixedS1516(spaceDPtr->whitePoint.z);
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(6) << uint(28);
+ stream << ushort('N') << ushort('/') << ushort('A');
+ stream << ushort(0); // 4-byte alignment
+
+ // From now on the data is variable sized:
+ mA2bOffset = currentOffset;
+ mA2bSize = writeMab(stream, spaceDPtr->mAB, true, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
+ currentOffset += mA2bSize;
+ if (writeB2a) {
+ mB2aOffset = currentOffset;
+ mB2aSize = writeMab(stream, spaceDPtr->mBA, false, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
+ currentOffset += mB2aSize;
+ }
}
+ // Writing description
descOffset = currentOffset;
- QByteArray description = space.description().toUtf8();
- stream << uint(Tag::desc) << uint(0);
- stream << uint(description.size() + 1);
- stream.writeRawData(description.constData(), description.size() + 1);
- stream << uint(0) << uint(0);
- stream << ushort(0) << uchar(0);
- QByteArray macdesc(67, '\0');
- stream.writeRawData(macdesc.constData(), 67);
- descSize = 90 + description.size() + 1;
+ const QString description = space.description();
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(description.size() * 2) << uint(28);
+ for (QChar ch : description)
+ stream << ushort(ch.unicode());
+ descSize = 28 + description.size() * 2;
+ if (description.size() & 1) {
+ stream << ushort(0);
+ currentOffset += 2;
+ }
currentOffset += descSize;
buffer.close();
@@ -483,14 +1025,34 @@ QByteArray toIccProfile(const QColorSpace &space)
// Now write final size
*(quint32_be *)iccProfile.data() = iccProfile.size();
// And the final indices and sizes of variable size tags:
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
+ if (spaceDPtr->isThreeComponentMatrix()) {
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
+ } else {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = kTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = kTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = descSize;
+ }
+ } else {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mA2bOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mA2bSize;
+ variableTagTableOffsets += 12;
+ if (writeB2a) {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mB2aOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mB2aSize;
+ variableTagTableOffsets += 12;
+ }
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = descSize;
+ }
#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
@@ -527,21 +1089,27 @@ static bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColo
static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorTransferTable::Type type = QColorTransferTable::TwoWay)
{
+ if (tagData.size() < 12)
+ return 0;
const GenericTagData trcData = qFromUnaligned<GenericTagData>(tagData.constData());
if (trcData.type == quint32(Tag::curv)) {
Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
const CurvTagData curv = qFromUnaligned<CurvTagData>(tagData.constData());
- if (curv.valueCount > (1 << 16))
+ if (curv.valueCount > (1 << 16)) {
+ qCWarning(lcIcc) << "Invalid count in curv table";
return 0;
- if (tagData.size() < qsizetype(12 + 2 * curv.valueCount))
+ }
+ if (tagData.size() < qsizetype(12 + 2 * curv.valueCount)) {
+ qCWarning(lcIcc) << "Truncated curv table";
return 0;
+ }
const auto valueOffset = sizeof(CurvTagData);
if (curv.valueCount == 0) {
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(); // Linear
} else if (curv.valueCount == 1) {
const quint16 v = qFromBigEndian<quint16>(tagData.constData() + valueOffset);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction::fromGamma(v * (1.0f / 256.0f));
} else {
QList<quint16> tabl;
@@ -559,7 +1127,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
gamma.m_table = table;
} else {
qCDebug(lcIcc) << "Detected curv table as function";
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = curve;
}
}
@@ -576,7 +1144,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
return 0;
qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 1, parameters);
float g = fromFixedS1516(parameters[0]);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction::fromGamma(g);
return 12 + 1 * 4;
}
@@ -590,7 +1158,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float a = fromFixedS1516(parameters[1]);
float b = fromFixedS1516(parameters[2]);
float d = -b / a;
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
return 12 + 3 * 4;
}
@@ -605,7 +1173,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float b = fromFixedS1516(parameters[2]);
float c = fromFixedS1516(parameters[3]);
float d = -b / a;
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
return 12 + 4 * 4;
}
@@ -618,7 +1186,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float b = fromFixedS1516(parameters[2]);
float c = fromFixedS1516(parameters[3]);
float d = fromFixedS1516(parameters[4]);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
return 12 + 5 * 4;
}
@@ -633,7 +1201,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float d = fromFixedS1516(parameters[4]);
float e = fromFixedS1516(parameters[5]);
float f = fromFixedS1516(parameters[6]);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
return 12 + 7 * 4;
}
@@ -648,22 +1216,26 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
}
template<typename T>
-static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut)
+static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar outputChannels)
{
- for (qsizetype index = 0; index < clut->table.size(); ++index) {
- QColorVector v(tableData[index * 3 + 0] * f,
- tableData[index * 3 + 1] * f,
- tableData[index * 3 + 2] * f);
- clut->table[index] = v;
+ if (outputChannels == 4) {
+ for (qsizetype index = 0; index < clut->table.size(); ++index) {
+ QColorVector v(tableData[index * 4 + 0] * f,
+ tableData[index * 4 + 1] * f,
+ tableData[index * 4 + 2] * f,
+ tableData[index * 4 + 3] * f);
+ clut->table[index] = v;
+ };
+ } else {
+ for (qsizetype index = 0; index < clut->table.size(); ++index) {
+ QColorVector v(tableData[index * 3 + 0] * f,
+ tableData[index * 3 + 1] * f,
+ tableData[index * 3 + 2] * f);
+ clut->table[index] = v;
+ };
}
}
-// very simple version for small values (<=4) of exp.
-static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
-{
- return (exp <= 1) ? x : x * intPow(x, exp - 1);
-}
-
// Parses lut8 and lut16 type elements
template<typename T>
static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
@@ -672,6 +1244,10 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
qCWarning(lcIcc) << "Undersized lut8/lut16 tag";
return false;
}
+ if (qsizetype(tagEntry.size) > data.size()) {
+ qCWarning(lcIcc) << "Truncated lut8/lut16 tag";
+ return false;
+ }
using S = std::conditional_t<std::is_same_v<T, Lut8TagData>, uint8_t, uint16_t>;
const T lut = qFromUnaligned<T>(data.constData() + tagEntry.offset);
int inputTableEntries, outputTableEntries, precision;
@@ -688,6 +1264,10 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
Q_ASSERT(lut.type == quint32(Tag::mft2));
inputTableEntries = lut.inputTableEntries;
outputTableEntries = lut.outputTableEntries;
+ if (inputTableEntries < 2 || inputTableEntries > 4096)
+ return false;
+ if (outputTableEntries < 2 || outputTableEntries > 4096)
+ return false;
precision = 2;
}
@@ -711,27 +1291,34 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
- if (lut.inputChannels != 3) {
+ const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+ const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+
+ if (lut.inputChannels != inputChannels) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
return false;
}
- if (lut.outputChannels != 3) {
+ if (lut.outputChannels != outputChannels) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
return false;
}
- const qsizetype clutTableSize = intPow(lut.clutGridPoints, lut.inputChannels);
- if (tagEntry.size < (sizeof(T) + precision * lut.inputChannels * inputTableEntries
- + precision * lut.outputChannels * outputTableEntries
- + precision * lut.outputChannels * clutTableSize)) {
+ const qsizetype clutTableSize = intPow(lut.clutGridPoints, inputChannels);
+ if (tagEntry.size < (sizeof(T) + precision * inputChannels * inputTableEntries
+ + precision * outputChannels * outputTableEntries
+ + precision * outputChannels * clutTableSize)) {
qCWarning(lcIcc) << "Undersized lut8/lut16 tag, no room for tables";
return false;
}
+ if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && clutTableSize == 0) {
+ qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
+ return false;
+ }
const uint8_t *tableData = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + sizeof(T));
- for (int j = 0; j < lut.inputChannels; ++j) {
+ for (int j = 0; j < inputChannels; ++j) {
QList<S> input(inputTableEntries);
qFromBigEndian<S>(tableData, inputTableEntries, input.data());
QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
@@ -747,19 +1334,22 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
clutElement.table.resize(clutTableSize);
clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
+ if (inputChannels == 4)
+ clutElement.gridPointsW = lut.clutGridPoints;
+
if constexpr (std::is_same_v<T, Lut8TagData>) {
- parseCLUT(tableData, 1.f / 255.f, &clutElement);
+ parseCLUT(tableData, 1.f / 255.f, &clutElement, outputChannels);
} else {
float f = 1.0f / 65535.f;
if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
f = 1.0f / 65280.f;
- QList<S> clutTable(clutTableSize * lut.outputChannels);
+ QList<S> clutTable(clutTableSize * outputChannels);
qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
- parseCLUT(clutTable.constData(), f, &clutElement);
+ parseCLUT(clutTable.constData(), f, &clutElement, outputChannels);
}
- tableData += clutTableSize * lut.outputChannels * precision;
+ tableData += clutTableSize * outputChannels * precision;
- for (int j = 0; j < lut.outputChannels; ++j) {
+ for (int j = 0; j < outputChannels; ++j) {
QList<S> output(outputTableEntries);
qFromBigEndian<S>(tableData, outputTableEntries, output.data());
QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
@@ -778,7 +1368,7 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
colorSpacePrivate->mAB.append(inTableElement);
if (!clutElement.isEmpty())
colorSpacePrivate->mAB.append(clutElement);
- if (!outTableIsLinear)
+ if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
colorSpacePrivate->mAB.append(outTableElement);
} else {
// The matrix is only to be applied if the input color-space is XYZ
@@ -788,7 +1378,7 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
colorSpacePrivate->mBA.append(inTableElement);
if (!clutElement.isEmpty())
colorSpacePrivate->mBA.append(clutElement);
- if (!outTableIsLinear)
+ if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
colorSpacePrivate->mBA.append(outTableElement);
}
return true;
@@ -811,12 +1401,15 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
- if (mab.inputChannels != 3) {
+ const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+ const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+
+ if (mab.inputChannels != inputChannels) {
qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
return false;
}
- if (mab.outputChannels != 3) {
+ if (mab.outputChannels != outputChannels) {
qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
return false;
}
@@ -866,7 +1459,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
// B Curves
- if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, 3)) {
+ if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? outputChannels : inputChannels)) {
qCWarning(lcIcc) << "Invalid B curves";
return false;
} else {
@@ -875,7 +1468,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
// A Curves
if (mab.aCurvesOffset) {
- if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, 3)) {
+ if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? inputChannels : outputChannels)) {
qCWarning(lcIcc) << "Invalid A curves";
return false;
} else {
@@ -916,9 +1509,10 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
// CLUT
if (mab.clutOffset) {
- clutElement.gridPointsX = data[tagEntry.offset + mab.clutOffset];
- clutElement.gridPointsY = data[tagEntry.offset + mab.clutOffset + 1];
- clutElement.gridPointsZ = data[tagEntry.offset + mab.clutOffset + 2];
+ clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab.clutOffset]);
+ clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab.clutOffset + 1]);
+ clutElement.gridPointsZ = uint8_t(data[tagEntry.offset + mab.clutOffset + 2]);
+ clutElement.gridPointsW = std::max(uint8_t(data[tagEntry.offset + mab.clutOffset + 3]), uint8_t(1));
const uchar precision = data[tagEntry.offset + mab.clutOffset + 16];
if (precision > 2 || precision < 1) {
qCWarning(lcIcc) << "Invalid mAB/mBA element CLUT precision";
@@ -928,21 +1522,24 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
qCWarning(lcIcc) << "Empty CLUT";
return false;
}
- const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ;
- if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) {
+ const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
+ if ((mab.clutOffset + 20 + clutTableSize * outputChannels * precision) > tagEntry.size) {
qCWarning(lcIcc) << "CLUT oversized for tag";
return false;
}
clutElement.table.resize(clutTableSize);
if (precision == 2) {
- QList<uint16_t> clutTable(clutTableSize * mab.outputChannels);
+ QList<uint16_t> clutTable(clutTableSize * outputChannels);
qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
- parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement);
+ parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, outputChannels);
} else {
const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20);
- parseCLUT(clutTable, (1.f/255.f), &clutElement);
+ parseCLUT(clutTable, (1.f/255.f), &clutElement, outputChannels);
}
+ } else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
+ qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
+ return false;
}
if (isAb) {
@@ -952,7 +1549,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
if (!clutElement.isEmpty())
colorSpacePrivate->mAB.append(std::move(clutElement));
}
- if (mab.mCurvesOffset) {
+ if (mab.mCurvesOffset && outputChannels == 3) {
if (!mCurvesAreLinear)
colorSpacePrivate->mAB.append(std::move(mTableElement));
if (!matrixElement.isIdentity())
@@ -960,12 +1557,12 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
if (!offsetElement.isNull())
colorSpacePrivate->mAB.append(std::move(offsetElement));
}
- if (!bCurvesAreLinear)
+ if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
colorSpacePrivate->mAB.append(std::move(bTableElement));
} else {
if (!bCurvesAreLinear)
colorSpacePrivate->mBA.append(std::move(bTableElement));
- if (mab.mCurvesOffset) {
+ if (mab.mCurvesOffset && inputChannels == 3) {
if (!matrixElement.isIdentity())
colorSpacePrivate->mBA.append(std::move(matrixElement));
if (!offsetElement.isNull())
@@ -979,6 +1576,8 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
if (!aCurvesAreLinear)
colorSpacePrivate->mBA.append(std::move(aTableElement));
}
+ if (colorSpacePrivate->mBA.isEmpty()) // Ensure non-empty to indicate valid empty transform
+ colorSpacePrivate->mBA.append(std::move(bTableElement));
}
return true;
@@ -1004,6 +1603,8 @@ static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString
// Either 'desc' (ICCv2) or 'mluc' (ICCv4)
if (tag.type == quint32(Tag::desc)) {
+ if (tagEntry.size < sizeof(DescTagData))
+ return false;
Q_STATIC_ASSERT(sizeof(DescTagData) == 12);
const DescTagData desc = qFromUnaligned<DescTagData>(data.constData() + tagEntry.offset);
const quint32 len = desc.asciiDescriptionLength;
@@ -1025,7 +1626,7 @@ static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString
const MlucTagData mluc = qFromUnaligned<MlucTagData>(data.constData() + tagEntry.offset);
if (mluc.recordCount < 1)
return false;
- if (mluc.recordSize < 12)
+ if (mluc.recordSize != 12)
return false;
// We just use the primary record regardless of language or country.
const quint32 stringOffset = mluc.records[0].offset;
@@ -1071,18 +1672,18 @@ static bool parseRgbMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &t
} else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
- }
- if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromBt2020()) {
+ qCDebug(lcIcc) << "fromIccProfile: BT.2020 primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020;
}
return true;
}
static bool parseGrayMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
{
- // We will use sRGB primaries and fit to match the given white-point if
- // it doesn't match sRGB's.
QColorVector whitePoint;
if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
return false;
@@ -1090,27 +1691,39 @@ static bool parseGrayMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &
qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
return false;
}
- if (whitePoint == QColorVector::D65()) {
- colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
- } else {
- colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
- // Calculate chromaticity from xyz (assuming y == 1.0f).
- float y = 1.0f / (1.0f + whitePoint.z + whitePoint.x);
- float x = whitePoint.x * y;
- QColorSpacePrimaries primaries(QColorSpace::Primaries::SRgb);
- primaries.whitePoint = QPointF(x,y);
- if (!primaries.areValid()) {
- qCWarning(lcIcc, "fromIccProfile: Invalid ICC profile - invalid white-point(%f, %f)", x, y);
- return false;
- }
- colorspaceDPtr->toXyz = primaries.toXyzMatrix();
- if (!colorspaceDPtr->toXyz.isValid()) {
- qCWarning(lcIcc, "fromIccProfile: Invalid ICC profile - invalid white-point(%f, %f)", x, y);
- return false;
- }
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
+ colorspaceDPtr->whitePoint = whitePoint;
+ return true;
+}
+
+static bool parseChad(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
+{
+ if (tagEntry.size < sizeof(Sf32TagData) || qsizetype(tagEntry.size) > data.size())
+ return false;
+ const Sf32TagData chadtag = qFromUnaligned<Sf32TagData>(data.constData() + tagEntry.offset);
+ if (chadtag.type != uint32_t(Tag::sf32)) {
+ qCWarning(lcIcc, "fromIccProfile: bad chad data type");
+ return false;
+ }
+ QColorMatrix chad;
+ chad.r.x = fromFixedS1516(chadtag.value[0]);
+ chad.g.x = fromFixedS1516(chadtag.value[1]);
+ chad.b.x = fromFixedS1516(chadtag.value[2]);
+ chad.r.y = fromFixedS1516(chadtag.value[3]);
+ chad.g.y = fromFixedS1516(chadtag.value[4]);
+ chad.b.y = fromFixedS1516(chadtag.value[5]);
+ chad.r.z = fromFixedS1516(chadtag.value[6]);
+ chad.g.z = fromFixedS1516(chadtag.value[7]);
+ chad.b.z = fromFixedS1516(chadtag.value[8]);
+
+ if (!chad.isValid()) {
+ qCWarning(lcIcc, "fromIccProfile: invalid chad matrix");
+ return false;
}
+ colorspaceDPtr->chad = chad;
return true;
}
+
static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr, bool isColorSpaceTypeGray)
{
TagEntry rTrc;
@@ -1152,12 +1765,12 @@ static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagInd
colorspaceDPtr->trc[0] = QColorTransferFunction();
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
colorspaceDPtr->gamma = 1.0f;
- } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isGamma()) {
+ } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isGamma()) {
qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
colorspaceDPtr->gamma = rCurve.m_fun.m_g;
- } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isSRgb()) {
+ } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isSRgb()) {
qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
@@ -1176,6 +1789,63 @@ static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagInd
return true;
}
+static bool parseCicp(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
+{
+ if (colorspaceDPtr->isPcsLab)
+ return false;
+ if (tagEntry.size < sizeof(CicpTagData) || qsizetype(tagEntry.size) > data.size())
+ return false;
+ const CicpTagData cicp = qFromUnaligned<CicpTagData>(data.constData() + tagEntry.offset);
+ if (cicp.type != uint32_t(Tag::cicp)) {
+ qCWarning(lcIcc, "fromIccProfile: bad cicp data type");
+ return false;
+ }
+ switch (cicp.transferCharacteristics) {
+ case 4:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
+ colorspaceDPtr->gamma = 2.2f;
+ break;
+ case 5:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
+ colorspaceDPtr->gamma = 2.8f;
+ break;
+ case 8:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
+ break;
+ case 13:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
+ break;
+ case 1:
+ case 6:
+ case 14:
+ case 15:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Bt2020;
+ break;
+ case 16:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::St2084;
+ break;
+ case 18:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Hlg;
+ break;
+ default:
+ return false;
+ }
+ switch (cicp.colorPrimaries) {
+ case 1:
+ colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
+ break;
+ case 9:
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020;
+ break;
+ case 12:
+ colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
{
if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
@@ -1214,7 +1884,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
return false;
}
- if (tagTable.size < 12) {
+ if (tagTable.size < 8) {
qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
return false;
}
@@ -1238,11 +1908,11 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
// Check the profile is three-component matrix based:
if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
!tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
- !tagIndex.contains(Tag::wtpt)) {
+ !tagIndex.contains(Tag::wtpt) || header.pcs == uint(Tag::Lab_)) {
threeComponentMatrix = false;
// Check if the profile is valid n-LUT based:
if (!tagIndex.contains(Tag::A2B0)) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor LUT";
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT";
return false;
}
}
@@ -1251,6 +1921,12 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
return false;
}
+ } else if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) {
+ threeComponentMatrix = false;
+ if (!tagIndex.contains(Tag::A2B0)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - CMYK, not n-LUT";
+ return false;
+ }
} else {
Q_UNREACHABLE();
}
@@ -1258,46 +1934,76 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorSpace->detach();
QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::get(*colorSpace);
+ colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
+ if (tagIndex.contains(Tag::cicp)) {
+ // Let cicp override nLut profiles if we fully recognize it.
+ if (parseCicp(data, tagIndex[Tag::cicp], colorspaceDPtr))
+ threeComponentMatrix = true;
+ if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
+ colorspaceDPtr->setToXyzMatrix();
+ if (colorspaceDPtr->transferFunction != QColorSpace::TransferFunction::Custom)
+ colorspaceDPtr->setTransferFunction();
+ }
+
if (threeComponentMatrix) {
- colorspaceDPtr->isPcsLab = false;
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
- if (!parseRgbMatrix(data, tagIndex, colorspaceDPtr))
+ if (colorspaceDPtr->primaries == QColorSpace::Primaries::Custom && !parseRgbMatrix(data, tagIndex, colorspaceDPtr))
return false;
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
} else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
return false;
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
} else {
Q_UNREACHABLE();
}
+ if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) {
+ if (!parseChad(data, it.value(), colorspaceDPtr))
+ return false;
+ } else {
+ colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
+ }
+ if (colorspaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ colorspaceDPtr->toXyz = colorspaceDPtr->chad;
// Reset the matrix to our canonical values:
if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
colorspaceDPtr->setToXyzMatrix();
- if (!parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
+ if (colorspaceDPtr->transferFunction == QColorSpace::TransferFunction::Custom &&
+ !parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
return false;
} else {
- colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
+ if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
+ else
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
// Only parse the default perceptual transform for now
if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true))
return false;
- if (tagIndex.contains(Tag::B2A0)) {
- if (!parseA2B(data, tagIndex[Tag::B2A0], colorspaceDPtr, false))
+ if (auto it = tagIndex.constFind(Tag::B2A0); it != tagIndex.constEnd()) {
+ if (!parseA2B(data, it.value(), colorspaceDPtr, false))
return false;
}
- if (tagIndex.contains(Tag::wtpt)) {
- if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
+ if (auto it = tagIndex.constFind(Tag::wtpt); it != tagIndex.constEnd()) {
+ if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint))
+ return false;
+ }
+ if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) {
+ if (!parseChad(data, it.value(), colorspaceDPtr))
return false;
+ } else if (!colorspaceDPtr->whitePoint.isNull()) {
+ colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
}
}
- if (tagIndex.contains(Tag::desc)) {
- if (!parseDesc(data, tagIndex[Tag::desc], colorspaceDPtr->description))
+ if (auto it = tagIndex.constFind(Tag::desc); it != tagIndex.constEnd()) {
+ if (!parseDesc(data, it.value(), colorspaceDPtr->description))
qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
else
qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
@@ -1309,6 +2015,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorspaceDPtr->iccProfile = data;
+ Q_ASSERT(colorspaceDPtr->isValid());
return true;
}
diff --git a/src/gui/painting/qimagescale.cpp b/src/gui/painting/qimagescale.cpp
index a636635fd5..eb5f12389f 100644
--- a/src/gui/painting/qimagescale.cpp
+++ b/src/gui/painting/qimagescale.cpp
@@ -1208,7 +1208,7 @@ QImage qSmoothScaleImage(const QImage &src, int dw, int dh)
dw, dh, dw, src.bytesPerLine() / 8);
else
#endif
- if (src.hasAlphaChannel())
+ if (src.hasAlphaChannel() || src.format() == QImage::Format_CMYK8888)
qt_qimageScaleAARGBA(scaleinfo, (unsigned int *)buffer.scanLine(0),
dw, dh, dw, src.bytesPerLine() / 4);
else
diff --git a/src/gui/painting/qoutlinemapper.cpp b/src/gui/painting/qoutlinemapper.cpp
index 93eac5cced..2f87ff43b7 100644
--- a/src/gui/painting/qoutlinemapper.cpp
+++ b/src/gui/painting/qoutlinemapper.cpp
@@ -50,7 +50,7 @@ void QOutlineMapper::setClipRect(QRect clipRect)
if (clipRect != m_clip_rect) {
m_clip_rect = limitCoords(clipRect);
- const int mw = 64; // margin width. No need to trigger clipping for slight overshooting
+ const int mw = 1 << 10; // margin width. No need to trigger clipping for slight overshooting
m_clip_trigger_rect = QRectF(limitCoords(m_clip_rect.adjusted(-mw, -mw, mw, mw)));
}
}
diff --git a/src/gui/painting/qpagelayout.cpp b/src/gui/painting/qpagelayout.cpp
index ec505f2cce..e60f464d6e 100644
--- a/src/gui/painting/qpagelayout.cpp
+++ b/src/gui/painting/qpagelayout.cpp
@@ -79,7 +79,7 @@ public:
bool isValid() const;
- void clampMargins(const QMarginsF &margins);
+ QMarginsF clampMargins(const QMarginsF &margins) const;
QMarginsF margins(QPageLayout::Unit units) const;
QMarginsF marginsPoints() const;
@@ -151,12 +151,12 @@ bool QPageLayoutPrivate::isValid() const
return m_pageSize.isValid();
}
-void QPageLayoutPrivate::clampMargins(const QMarginsF &margins)
+QMarginsF QPageLayoutPrivate::clampMargins(const QMarginsF &margins) const
{
- m_margins = QMarginsF(qBound(m_minMargins.left(), margins.left(), m_maxMargins.left()),
- qBound(m_minMargins.top(), margins.top(), m_maxMargins.top()),
- qBound(m_minMargins.right(), margins.right(), m_maxMargins.right()),
- qBound(m_minMargins.bottom(), margins.bottom(), m_maxMargins.bottom()));
+ return QMarginsF(qBound(m_minMargins.left(), margins.left(), m_maxMargins.left()),
+ qBound(m_minMargins.top(), margins.top(), m_maxMargins.top()),
+ qBound(m_minMargins.right(), margins.right(), m_maxMargins.right()),
+ qBound(m_minMargins.bottom(), margins.bottom(), m_maxMargins.bottom()));
}
QMarginsF QPageLayoutPrivate::margins(QPageLayout::Unit units) const
@@ -182,7 +182,7 @@ void QPageLayoutPrivate::setDefaultMargins(const QMarginsF &minMargins)
qMax(m_fullSize.width() - m_minMargins.left(), qreal(0)),
qMax(m_fullSize.height() - m_minMargins.top(), qreal(0)));
if (m_mode == QPageLayout::StandardMode)
- clampMargins(m_margins);
+ m_margins = clampMargins(m_margins);
}
QSizeF QPageLayoutPrivate::fullSizeUnits(QPageLayout::Unit units) const
@@ -288,6 +288,27 @@ QRectF QPageLayoutPrivate::paintRect() const
\value StandardMode Paint Rect includes margins, margins must fall between the minimum and maximum.
\value FullPageMode Paint Rect excludes margins, margins can be any value and must be managed manually.
+
+ In StandardMode, when setting margins, use \l{QPageLayout::OutOfBoundsPolicy::}{Clamp} to
+ automatically clamp the margins to fall between the minimum and maximum
+ allowed values.
+
+ \sa OutOfBoundsPolicy
+*/
+
+/*!
+ \enum QPageLayout::OutOfBoundsPolicy
+ \since 6.8
+
+ Defines the policy for margins that are out of bounds
+
+ \value Reject The margins must fall within the minimum and maximum values,
+ otherwise they will be rejected.
+ \value Clamp The margins are clamped between the minimum and maximum
+ values to ensure they are valid.
+
+ \note The policy has no effect in \l{QPageLayout::Mode}{FullPageMode},
+ where all margins are accepted.
*/
/*!
@@ -525,39 +546,52 @@ QPageLayout::Unit QPageLayout::units() const
}
/*!
- Sets the page margins of the page layout to \a margins
+ Sets the page margins of the page layout to \a margins.
Returns true if the margins were successfully set.
The units used are those currently defined for the layout. To use different
units then call setUnits() first.
- If in the default StandardMode then all the new margins must fall between the
- minimum margins set and the maximum margins allowed by the page size,
- otherwise the margins will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa margins(), units()
*/
-bool QPageLayout::setMargins(const QMarginsF &margins)
+bool QPageLayout::setMargins(const QMarginsF &margins, OutOfBoundsPolicy outOfBoundsPolicy)
{
if (d->m_mode == FullPageMode) {
- d.detach();
- d->m_margins = margins;
+ if (margins != d->m_margins) {
+ d.detach();
+ d->m_margins = margins;
+ }
return true;
- } else if (margins.left() >= d->m_minMargins.left()
- && margins.right() >= d->m_minMargins.right()
- && margins.top() >= d->m_minMargins.top()
- && margins.bottom() >= d->m_minMargins.bottom()
- && margins.left() <= d->m_maxMargins.left()
- && margins.right() <= d->m_maxMargins.right()
- && margins.top() <= d->m_maxMargins.top()
- && margins.bottom() <= d->m_maxMargins.bottom()) {
- d.detach();
- d->m_margins = margins;
+ }
+
+ if (outOfBoundsPolicy == OutOfBoundsPolicy::Clamp) {
+ const QMarginsF clampedMargins = d->clampMargins(margins);
+ if (clampedMargins != d->m_margins) {
+ d.detach();
+ d->m_margins = clampedMargins;
+ }
return true;
}
+
+ if (margins.left() >= d->m_minMargins.left()
+ && margins.right() >= d->m_minMargins.right()
+ && margins.top() >= d->m_minMargins.top()
+ && margins.bottom() >= d->m_minMargins.bottom()
+ && margins.left() <= d->m_maxMargins.left()
+ && margins.right() <= d->m_maxMargins.right()
+ && margins.top() <= d->m_maxMargins.top()
+ && margins.bottom() <= d->m_maxMargins.bottom()) {
+ if (margins != d->m_margins) {
+ d.detach();
+ d->m_margins = margins;
+ }
+ return true;
+ }
+
return false;
}
@@ -568,23 +602,27 @@ bool QPageLayout::setMargins(const QMarginsF &margins)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setLeftMargin(qreal leftMargin)
+bool QPageLayout::setLeftMargin(qreal leftMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ leftMargin = qBound(d->m_minMargins.left(), leftMargin, d->m_maxMargins.left());
+
+ if (qFuzzyCompare(leftMargin, d->m_margins.left()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (leftMargin >= d->m_minMargins.left() && leftMargin <= d->m_maxMargins.left())) {
d.detach();
d->m_margins.setLeft(leftMargin);
return true;
}
+
return false;
}
@@ -595,23 +633,27 @@ bool QPageLayout::setLeftMargin(qreal leftMargin)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setRightMargin(qreal rightMargin)
+bool QPageLayout::setRightMargin(qreal rightMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ rightMargin = qBound(d->m_minMargins.right(), rightMargin, d->m_maxMargins.right());
+
+ if (qFuzzyCompare(rightMargin, d->m_margins.right()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (rightMargin >= d->m_minMargins.right() && rightMargin <= d->m_maxMargins.right())) {
d.detach();
d->m_margins.setRight(rightMargin);
return true;
}
+
return false;
}
@@ -622,23 +664,27 @@ bool QPageLayout::setRightMargin(qreal rightMargin)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setTopMargin(qreal topMargin)
+bool QPageLayout::setTopMargin(qreal topMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ topMargin = qBound(d->m_minMargins.top(), topMargin, d->m_maxMargins.top());
+
+ if (qFuzzyCompare(topMargin, d->m_margins.top()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (topMargin >= d->m_minMargins.top() && topMargin <= d->m_maxMargins.top())) {
d.detach();
d->m_margins.setTop(topMargin);
return true;
}
+
return false;
}
@@ -649,23 +695,27 @@ bool QPageLayout::setTopMargin(qreal topMargin)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setBottomMargin(qreal bottomMargin)
+bool QPageLayout::setBottomMargin(qreal bottomMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ bottomMargin = qBound(d->m_minMargins.bottom(), bottomMargin, d->m_maxMargins.bottom());
+
+ if (qFuzzyCompare(bottomMargin, d->m_margins.bottom()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (bottomMargin >= d->m_minMargins.bottom() && bottomMargin <= d->m_maxMargins.bottom())) {
d.detach();
d->m_margins.setBottom(bottomMargin);
return true;
}
+
return false;
}
diff --git a/src/gui/painting/qpagelayout.h b/src/gui/painting/qpagelayout.h
index 29be2f9725..c812f92625 100644
--- a/src/gui/painting/qpagelayout.h
+++ b/src/gui/painting/qpagelayout.h
@@ -40,6 +40,11 @@ public:
FullPageMode // Paint Rect excludes margins
};
+ enum class OutOfBoundsPolicy {
+ Reject,
+ Clamp,
+ };
+
QPageLayout();
QPageLayout(const QPageSize &pageSize, Orientation orientation,
const QMarginsF &margins, Unit units = Point,
@@ -68,11 +73,19 @@ public:
void setUnits(Unit units);
Unit units() const;
+#if QT_GUI_REMOVED_SINCE(6, 8)
bool setMargins(const QMarginsF &margins);
bool setLeftMargin(qreal leftMargin);
bool setRightMargin(qreal rightMargin);
bool setTopMargin(qreal topMargin);
bool setBottomMargin(qreal bottomMargin);
+#endif
+
+ bool setMargins(const QMarginsF &margins, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setLeftMargin(qreal leftMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setRightMargin(qreal rightMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setTopMargin(qreal topMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setBottomMargin(qreal bottomMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
QMarginsF margins() const;
QMarginsF margins(Unit units) const;
diff --git a/src/gui/painting/qpagesize.h b/src/gui/painting/qpagesize.h
index b3629bb8d2..221863aad7 100644
--- a/src/gui/painting/qpagesize.h
+++ b/src/gui/painting/qpagesize.h
@@ -203,7 +203,9 @@ public:
void swap(QPageSize &other) noexcept { d.swap(other.d); }
+#if QT_GUI_REMOVED_SINCE(6, 4)
friend Q_GUI_EXPORT bool operator==(const QPageSize &lhs, const QPageSize &rhs);
+#endif
bool isEquivalentTo(const QPageSize &other) const;
bool isValid() const;
diff --git a/src/gui/painting/qpaintdevice.cpp b/src/gui/painting/qpaintdevice.cpp
index 14e2d7cd8e..de21901e23 100644
--- a/src/gui/painting/qpaintdevice.cpp
+++ b/src/gui/painting/qpaintdevice.cpp
@@ -17,6 +17,39 @@ QPaintDevice::~QPaintDevice()
"painted");
}
+/*!
+ \internal
+*/
+// ### Qt 7: Replace this workaround mechanism: virtual devicePixelRatio() and virtual metricF()
+inline double QPaintDevice::getDecodedMetricF(PaintDeviceMetric metricA, PaintDeviceMetric metricB) const
+{
+ qint32 buf[2];
+ // The Encoded metric enum values come in pairs of one odd and one even value.
+ // We map those to the 0 and 1 indexes of buf by taking just the least significant bit.
+ // Same mapping here as in the encodeMetricF() function, to ensure correct order.
+ buf[metricA & 1] = metric(metricA);
+ buf[metricB & 1] = metric(metricB);
+ double res;
+ memcpy(&res, buf, sizeof(res));
+ return res;
+}
+
+qreal QPaintDevice::devicePixelRatio() const
+{
+ Q_STATIC_ASSERT((PdmDevicePixelRatioF_EncodedA & 1) != (PdmDevicePixelRatioF_EncodedB & 1));
+ double res;
+ int scaledDpr = metric(PdmDevicePixelRatioScaled);
+ if (scaledDpr == int(devicePixelRatioFScale())) {
+ res = 1; // Shortcut for common case
+ } else if (scaledDpr == 2 * int(devicePixelRatioFScale())) {
+ res = 2; // Shortcut for common case
+ } else {
+ res = getDecodedMetricF(PdmDevicePixelRatioF_EncodedA, PdmDevicePixelRatioF_EncodedB);
+ if (res <= 0) // These metrics not implemented, fall back to PdmDevicePixelRatioScaled
+ res = scaledDpr / devicePixelRatioFScale();
+ }
+ return res;
+}
/*!
\internal
@@ -64,6 +97,8 @@ int QPaintDevice::metric(PaintDeviceMetric m) const
return 256;
} else if (m == PdmDevicePixelRatio) {
return 1;
+ } else if (m == PdmDevicePixelRatioF_EncodedA || m == PdmDevicePixelRatioF_EncodedB) {
+ return 0;
} else {
qDebug("Unrecognised metric %d!",m);
return 0;
diff --git a/src/gui/painting/qpaintdevice.h b/src/gui/painting/qpaintdevice.h
index ceccac5e7e..7011684df7 100644
--- a/src/gui/painting/qpaintdevice.h
+++ b/src/gui/painting/qpaintdevice.h
@@ -29,7 +29,9 @@ public:
PdmPhysicalDpiX,
PdmPhysicalDpiY,
PdmDevicePixelRatio,
- PdmDevicePixelRatioScaled
+ PdmDevicePixelRatioScaled,
+ PdmDevicePixelRatioF_EncodedA,
+ PdmDevicePixelRatioF_EncodedB,
};
virtual ~QPaintDevice();
@@ -46,18 +48,20 @@ public:
int logicalDpiY() const { return metric(PdmDpiY); }
int physicalDpiX() const { return metric(PdmPhysicalDpiX); }
int physicalDpiY() const { return metric(PdmPhysicalDpiY); }
- qreal devicePixelRatio() const { return metric(PdmDevicePixelRatioScaled) / devicePixelRatioFScale(); }
+ qreal devicePixelRatio() const;
qreal devicePixelRatioF() const { return devicePixelRatio(); }
int colorCount() const { return metric(PdmNumColors); }
int depth() const { return metric(PdmDepth); }
static inline qreal devicePixelRatioFScale() { return 0x10000; }
+ static inline int encodeMetricF(PaintDeviceMetric metric, double value);
protected:
QPaintDevice() noexcept;
virtual int metric(PaintDeviceMetric metric) const;
virtual void initPainter(QPainter *painter) const;
virtual QPaintDevice *redirected(QPoint *offset) const;
virtual QPainter *sharedPainter() const;
+ double getDecodedMetricF(PaintDeviceMetric metricA, PaintDeviceMetric metricB) const;
ushort painters; // refcount
private:
@@ -80,6 +84,14 @@ inline int QPaintDevice::devType() const
inline bool QPaintDevice::paintingActive() const
{ return painters != 0; }
+inline int QPaintDevice::encodeMetricF(PaintDeviceMetric metric, double value)
+{
+ qint32 buf[2];
+ Q_STATIC_ASSERT(sizeof(buf) == sizeof(double));
+ memcpy(buf, &value, sizeof(buf));
+ return buf[metric & 1];
+}
+
QT_END_NAMESPACE
#endif // QPAINTDEVICE_H
diff --git a/src/gui/painting/qpaintdevice.qdoc b/src/gui/painting/qpaintdevice.qdoc
index d63fdcb92b..9b9a6facb8 100644
--- a/src/gui/painting/qpaintdevice.qdoc
+++ b/src/gui/painting/qpaintdevice.qdoc
@@ -99,6 +99,14 @@
The constant scaling factor used is devicePixelRatioFScale(). This enum value
has been introduced in Qt 5.6.
+ \value [since 6.8] PdmDevicePixelRatioF_EncodedA This enum item, together with the
+ corresponding \c B item, are used together for the device pixel ratio of the device, as an
+ encoded \c double floating point value. A QPaintDevice subclass that supports \e fractional DPR
+ values should implement support for these two enum items in its override of the metric()
+ function. The return value is expected to be the result of the encodeMetricF() function.
+
+ \value [since 6.8] PdmDevicePixelRatioF_EncodedB See PdmDevicePixelRatioF_EncodedA.
+
\sa metric(), devicePixelRatio()
*/
@@ -288,3 +296,15 @@
\since 5.6
*/
+
+/*!
+ \fn int QPaintDevice::encodeMetricF(PaintDeviceMetric metric, double value)
+
+ \internal
+
+ Returns \a value encoded for the metric \a metric. Subclasses implementing metric() should use
+ this function to encode \value as an integer return value when the query metric specifies an
+ encoded floating-point value.
+
+ \since 6.8
+*/
diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp
index cea7024aaa..f62373d4ef 100644
--- a/src/gui/painting/qpaintengine_raster.cpp
+++ b/src/gui/painting/qpaintengine_raster.cpp
@@ -3713,15 +3713,9 @@ bool QRasterPaintEnginePrivate::canUseImageBlitting(QPainter::CompositionMode mo
QImage::Format dFormat = rasterBuffer->format;
QImage::Format sFormat = image.format();
- // Formats must match or source format must be a subset of destination format
- if (dFormat != sFormat && image.pixelFormat().alphaUsage() == QPixelFormat::IgnoresAlpha) {
- if ((sFormat == QImage::Format_RGB32 && dFormat == QImage::Format_ARGB32)
- || (sFormat == QImage::Format_RGBX8888 && dFormat == QImage::Format_RGBA8888)
- || (sFormat == QImage::Format_RGBX64 && dFormat == QImage::Format_RGBA64))
- sFormat = dFormat;
- else
- sFormat = qt_maybeAlphaVersionWithSameDepth(sFormat); // this returns premul formats
- }
+ // Formats must match or source format must be an opaque version of destination format
+ if (dFormat != sFormat && image.pixelFormat().alphaUsage() == QPixelFormat::IgnoresAlpha)
+ dFormat = qt_maybeDataCompatibleOpaqueVersion(dFormat);
return (dFormat == sFormat);
}
diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp
index 5b4a6b05ea..f4b9741eed 100644
--- a/src/gui/painting/qpainter.cpp
+++ b/src/gui/painting/qpainter.cpp
@@ -1765,9 +1765,12 @@ bool QPainter::begin(QPaintDevice *pd)
qWarning("QPainter::begin: Cannot paint on a null image");
qt_cleanup_painter_state(d);
return false;
- } else if (img->format() == QImage::Format_Indexed8) {
- // Painting on indexed8 images is not supported.
- qWarning("QPainter::begin: Cannot paint on an image with the QImage::Format_Indexed8 format");
+ } else if (img->format() == QImage::Format_Indexed8 ||
+ img->format() == QImage::Format_CMYK8888) {
+ // Painting on these formats is not supported.
+ qWarning() << "QPainter::begin: Cannot paint on an image with the"
+ << img->format()
+ << "format";
qt_cleanup_painter_state(d);
return false;
}
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp
index 98a6ca3d4a..716cf35ee6 100644
--- a/src/gui/painting/qpdf.cpp
+++ b/src/gui/painting/qpdf.cpp
@@ -7,6 +7,7 @@
#include "qplatformdefs.h"
+#include <private/qcmyk_p.h>
#include <private/qfont_p.h>
#include <private/qmath_p.h>
#include <private/qpainter_p.h>
@@ -19,8 +20,11 @@
#include <qimagewriter.h>
#include <qnumeric.h>
#include <qtemporaryfile.h>
+#include <qtimezone.h>
#include <quuid.h>
+#include <map>
+
#ifndef QT_NO_COMPRESS
#include <zlib.h>
#endif
@@ -270,13 +274,6 @@ namespace QPdf {
dev->open(QIODevice::ReadWrite | QIODevice::Truncate);
}
- void ByteStream::constructor_helper(QByteArray *ba)
- {
- delete dev;
- dev = new QBuffer(ba);
- dev->open(QIODevice::ReadWrite);
- }
-
void ByteStream::prepareBuffer()
{
Q_ASSERT(!dev->isSequential());
@@ -285,16 +282,17 @@ namespace QPdf {
&& size > maxMemorySize()) {
// Switch to file backing.
QTemporaryFile *newFile = new QTemporaryFile;
- newFile->open();
- dev->reset();
- while (!dev->atEnd()) {
- QByteArray buf = dev->read(chunkSize());
- newFile->write(buf);
+ if (newFile->open()) {
+ dev->reset();
+ while (!dev->atEnd()) {
+ QByteArray buf = dev->read(chunkSize());
+ newFile->write(buf);
+ }
+ delete dev;
+ dev = newFile;
+ ba.clear();
+ fileBackingActive = true;
}
- delete dev;
- dev = newFile;
- ba.clear();
- fileBackingActive = true;
}
if (dev->pos() != size) {
dev->seek(size);
@@ -1040,6 +1038,7 @@ void QPdfEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
*d->currentPage << "Q\n";
}
+// Used by QtWebKit
void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
{
Q_D(QPdfEngine);
@@ -1569,7 +1568,7 @@ void QPdfEnginePrivate::writeHeader()
int metaDataObj = -1;
int outputIntentObj = -1;
if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) {
- metaDataObj = writeXmpDcumentMetaData();
+ metaDataObj = writeXmpDocumentMetaData();
}
if (pdfVersion == QPdfEngine::Version_A1b) {
outputIntentObj = writeOutputIntent();
@@ -1745,11 +1744,12 @@ void QPdfEnginePrivate::writeInfo()
xprintf("+%02d'%02d')\n", hours , mins);
else
xprintf("Z)\n");
+ xprintf("/Trapped /False\n");
xprintf(">>\n"
"endobj\n");
}
-int QPdfEnginePrivate::writeXmpDcumentMetaData()
+int QPdfEnginePrivate::writeXmpDocumentMetaData()
{
const int metaDataObj = addXrefEntry(-1);
QByteArray metaDataContent;
@@ -1757,29 +1757,16 @@ int QPdfEnginePrivate::writeXmpDcumentMetaData()
if (xmpDocumentMetadata.isEmpty()) {
const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
- const QDateTime now = QDateTime::currentDateTime();
- const QDate date = now.date();
- const QTime time = now.time();
- const QString timeStr =
- QString::asprintf("%d-%02d-%02dT%02d:%02d:%02d",
- date.year(), date.month(), date.day(),
- time.hour(), time.minute(), time.second());
-
- const int offset = now.offsetFromUtc();
- const int hours = (offset / 60) / 60;
- const int mins = (offset / 60) % 60;
- QString tzStr;
- if (offset < 0)
- tzStr = QString::asprintf("-%02d:%02d", -hours, -mins);
- else if (offset > 0)
- tzStr = QString::asprintf("+%02d:%02d", hours , mins);
- else
- tzStr = "Z"_L1;
-
- const QString metaDataDate = timeStr + tzStr;
+#if QT_CONFIG(timezone)
+ const QDateTime now = QDateTime::currentDateTime(QTimeZone::systemTimeZone());
+#else
+ const QDateTime now = QDateTime::currentDateTimeUtc();
+#endif
+ const QString metaDataDate = now.toString(Qt::ISODate);
QFile metaDataFile(":/qpdf/qpdfa_metadata.xml"_L1);
- metaDataFile.open(QIODevice::ReadOnly);
+ bool ok = metaDataFile.open(QIODevice::ReadOnly);
+ Q_ASSERT(ok);
metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(),
title.toHtmlEscaped(),
creator.toHtmlEscaped(),
@@ -1805,7 +1792,8 @@ int QPdfEnginePrivate::writeOutputIntent()
const int colorProfile = addXrefEntry(-1);
{
QFile colorProfileFile(":/qpdf/sRGB2014.icc"_L1);
- colorProfileFile.open(QIODevice::ReadOnly);
+ bool ok = colorProfileFile.open(QIODevice::ReadOnly);
+ Q_ASSERT(ok);
const QByteArray colorProfileData = colorProfileFile.readAll();
QByteArray data;
@@ -1816,7 +1804,8 @@ int QPdfEnginePrivate::writeOutputIntent()
s << "/N 3\n";
s << "/Alternate /DeviceRGB\n";
s << "/Length " << length_object << "0 R\n";
- s << "/Filter /FlateDecode\n";
+ if (do_compress)
+ s << "/Filter /FlateDecode\n";
s << ">>\n";
s << "stream\n";
write(data);
@@ -1870,7 +1859,7 @@ void QPdfEnginePrivate::writeDestsRoot()
if (destCache.isEmpty())
return;
- QHash<QString, int> destObjects;
+ std::map<QString, int> destObjects;
QByteArray xs, ys;
for (const DestInfo &destInfo : std::as_const(destCache)) {
int destObj = addXrefEntry(-1);
@@ -1878,21 +1867,19 @@ void QPdfEnginePrivate::writeDestsRoot()
ys.setNum(static_cast<double>(destInfo.coords.y()), 'f');
xprintf("[%d 0 R /XYZ %s %s 0]\n", destInfo.pageObj, xs.constData(), ys.constData());
xprintf("endobj\n");
- destObjects.insert(destInfo.anchor, destObj);
+ destObjects.insert_or_assign(destInfo.anchor, destObj);
}
// names
destsRoot = addXrefEntry(-1);
- QStringList anchors = destObjects.keys();
- anchors.sort();
xprintf("<<\n/Limits [");
- printString(anchors.constFirst());
+ printString(destObjects.begin()->first);
xprintf(" ");
- printString(anchors.constLast());
+ printString(destObjects.rbegin()->first);
xprintf("]\n/Names [\n");
- for (const QString &anchor : std::as_const(anchors)) {
+ for (const auto &[anchor, destObject] : destObjects) {
printString(anchor);
- xprintf(" %d 0 R\n", destObjects[anchor]);
+ xprintf(" %d 0 R\n", destObject);
}
xprintf("]\n>>\n"
"endobj\n");
@@ -1925,7 +1912,8 @@ void QPdfEnginePrivate::writeAttachmentRoot()
attachments.push_back(addXrefEntry(-1));
xprintf("<<\n"
- "/F (%s)", attachment.fileName.toLatin1().constData());
+ "/F ");
+ printString(attachment.fileName);
xprintf("\n/EF <</F %d 0 R>>\n"
"/Type/Filespec\n"
@@ -2138,17 +2126,24 @@ void QPdfEnginePrivate::writePage()
qreal userUnit = calcUserUnit();
addXrefEntry(pages.constLast());
+
+ // make sure we use the pagesize from when we started the page, since the user may have changed it
+ const QByteArray formattedPageWidth = QByteArray::number(currentPage->pageSize.width() / userUnit, 'f');
+ const QByteArray formattedPageHeight = QByteArray::number(currentPage->pageSize.height() / userUnit, 'f');
+
xprintf("<<\n"
"/Type /Page\n"
"/Parent %d 0 R\n"
"/Contents %d 0 R\n"
"/Resources %d 0 R\n"
"/Annots %d 0 R\n"
- "/MediaBox [0 0 %s %s]\n",
+ "/MediaBox [0 0 %s %s]\n"
+ "/TrimBox [0 0 %s %s]\n",
pageRoot, pageStream, resources, annots,
- // make sure we use the pagesize from when we started the page, since the user may have changed it
- QByteArray::number(currentPage->pageSize.width() / userUnit, 'f').constData(),
- QByteArray::number(currentPage->pageSize.height() / userUnit, 'f').constData());
+ formattedPageWidth.constData(),
+ formattedPageHeight.constData(),
+ formattedPageWidth.constData(),
+ formattedPageHeight.constData());
if (pdfVersion >= QPdfEngine::Version_1_6)
xprintf("/UserUnit %s\n", QByteArray::number(userUnit, 'f').constData());
@@ -2424,7 +2419,7 @@ int QPdfEnginePrivate::writeCompressed(const char *src, int len)
return len;
}
-int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth,
+int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, WriteImageOption option,
int maskObject, int softMaskObject, bool dct, bool isMono)
{
int image = addXrefEntry(-1);
@@ -2434,7 +2429,8 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
"/Width %d\n"
"/Height %d\n", width, height);
- if (depth == 1) {
+ switch (option) {
+ case WriteImageOption::Monochrome:
if (!isMono) {
xprintf("/ImageMask true\n"
"/Decode [1 0]\n");
@@ -2442,10 +2438,21 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
xprintf("/BitsPerComponent 1\n"
"/ColorSpace /DeviceGray\n");
}
- } else {
+ break;
+ case WriteImageOption::Grayscale:
xprintf("/BitsPerComponent 8\n"
- "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray");
+ "/ColorSpace /DeviceGray\n");
+ break;
+ case WriteImageOption::RGB:
+ xprintf("/BitsPerComponent 8\n"
+ "/ColorSpace /DeviceRGB\n");
+ break;
+ case WriteImageOption::CMYK:
+ xprintf("/BitsPerComponent 8\n"
+ "/ColorSpace /DeviceCMYK\n");
+ break;
}
+
if (maskObject > 0)
xprintf("/Mask %d 0 R\n", maskObject);
if (softMaskObject > 0)
@@ -3047,7 +3054,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
format = QImage::Format_Mono;
} else {
*bitmap = false;
- if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) {
+ if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_CMYK8888) {
image = image.convertToFormat(QImage::Format_ARGB32);
format = QImage::Format_ARGB32;
}
@@ -3055,7 +3062,6 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
int w = image.width();
int h = image.height();
- int d = image.depth();
if (format == QImage::Format_Mono) {
int bytesPerLine = (w + 7) >> 3;
@@ -3066,7 +3072,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
memcpy(rawdata, image.constScanLine(y), bytesPerLine);
rawdata += bytesPerLine;
}
- object = writeImage(data, w, h, d, 0, 0, false, is_monochrome(img.colorTable()));
+ object = writeImage(data, w, h, WriteImageOption::Monochrome, 0, 0, false, is_monochrome(img.colorTable()));
} else {
QByteArray softMaskData;
bool dct = false;
@@ -3078,10 +3084,14 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
QBuffer buffer(&imageData);
QImageWriter writer(&buffer, "jpeg");
writer.setQuality(94);
+ if (format == QImage::Format_CMYK8888) {
+ // PDFs require CMYK colors not to be inverted in the JPEG encoding
+ writer.setSubType("CMYK");
+ }
writer.write(image);
dct = true;
- if (format != QImage::Format_RGB32) {
+ if (format != QImage::Format_RGB32 && format != QImage::Format_CMYK8888) {
softMaskData.resize(w * h);
uchar *sdata = (uchar *)softMaskData.data();
for (int y = 0; y < h; ++y) {
@@ -3096,41 +3106,59 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
}
} else {
- imageData.resize(grayscale ? w * h : 3 * w * h);
- uchar *data = (uchar *)imageData.data();
- softMaskData.resize(w * h);
- uchar *sdata = (uchar *)softMaskData.data();
- for (int y = 0; y < h; ++y) {
- const QRgb *rgb = (const QRgb *)image.constScanLine(y);
+ if (format == QImage::Format_CMYK8888) {
+ imageData.resize(grayscale ? w * h : w * h * 4);
+ uchar *data = (uchar *)imageData.data();
+ const qsizetype bytesPerLine = image.bytesPerLine();
if (grayscale) {
- for (int x = 0; x < w; ++x) {
- *(data++) = qGray(*rgb);
- uchar alpha = qAlpha(*rgb);
- *sdata++ = alpha;
- hasMask |= (alpha < 255);
- hasAlpha |= (alpha != 0 && alpha != 255);
- ++rgb;
+ for (int y = 0; y < h; ++y) {
+ const uint *cmyk = (const uint *)image.constScanLine(y);
+ for (int x = 0; x < w; ++x)
+ *data++ = qGray(QCmyk32::fromCmyk32(*cmyk++).toColor().rgba());
}
} else {
- for (int x = 0; x < w; ++x) {
- *(data++) = qRed(*rgb);
- *(data++) = qGreen(*rgb);
- *(data++) = qBlue(*rgb);
- uchar alpha = qAlpha(*rgb);
- *sdata++ = alpha;
- hasMask |= (alpha < 255);
- hasAlpha |= (alpha != 0 && alpha != 255);
- ++rgb;
+ for (int y = 0; y < h; ++y) {
+ uchar *start = data + y * w * 4;
+ memcpy(start, image.constScanLine(y), bytesPerLine);
+ }
+ }
+ } else {
+ imageData.resize(grayscale ? w * h : 3 * w * h);
+ uchar *data = (uchar *)imageData.data();
+ softMaskData.resize(w * h);
+ uchar *sdata = (uchar *)softMaskData.data();
+ for (int y = 0; y < h; ++y) {
+ const QRgb *rgb = (const QRgb *)image.constScanLine(y);
+ if (grayscale) {
+ for (int x = 0; x < w; ++x) {
+ *(data++) = qGray(*rgb);
+ uchar alpha = qAlpha(*rgb);
+ *sdata++ = alpha;
+ hasMask |= (alpha < 255);
+ hasAlpha |= (alpha != 0 && alpha != 255);
+ ++rgb;
+ }
+ } else {
+ for (int x = 0; x < w; ++x) {
+ *(data++) = qRed(*rgb);
+ *(data++) = qGreen(*rgb);
+ *(data++) = qBlue(*rgb);
+ uchar alpha = qAlpha(*rgb);
+ *sdata++ = alpha;
+ hasMask |= (alpha < 255);
+ hasAlpha |= (alpha != 0 && alpha != 255);
+ ++rgb;
+ }
}
}
}
- if (format == QImage::Format_RGB32)
+ if (format == QImage::Format_RGB32 || format == QImage::Format_CMYK8888)
hasAlpha = hasMask = false;
}
int maskObject = 0;
int softMaskObject = 0;
if (hasAlpha) {
- softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0);
+ softMaskObject = writeImage(softMaskData, w, h, WriteImageOption::Grayscale, 0, 0);
} else if (hasMask) {
// dither the soft mask to 1bit and add it. This also helps PDF viewers
// without transparency support
@@ -3146,9 +3174,18 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
mdata += bytesPerLine;
}
- maskObject = writeImage(mask, w, h, 1, 0, 0);
+ maskObject = writeImage(mask, w, h, WriteImageOption::Monochrome, 0, 0);
}
- object = writeImage(imageData, w, h, grayscale ? 8 : 32,
+
+ const WriteImageOption option = [&]() {
+ if (grayscale)
+ return WriteImageOption::Grayscale;
+ if (format == QImage::Format_CMYK8888)
+ return WriteImageOption::CMYK;
+ return WriteImageOption::RGB;
+ }();
+
+ object = writeImage(imageData, w, h, option,
maskObject, softMaskObject, dct);
}
imageCache.insert(serial_no, object);
diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h
index b97d0df31f..54d37b00a8 100644
--- a/src/gui/painting/qpdf_p.h
+++ b/src/gui/painting/qpdf_p.h
@@ -60,10 +60,6 @@ namespace QPdf {
static inline int maxMemorySize() { return 100000000; }
static inline int chunkSize() { return 10000000; }
- protected:
- void constructor_helper(QIODevice *dev);
- void constructor_helper(QByteArray *ba);
-
private:
void prepareBuffer();
@@ -294,7 +290,7 @@ private:
QPdfEngine::ColorModel colorModelForColor(const QColor &color) const;
void writeColor(ColorDomain domain, const QColor &color);
void writeInfo();
- int writeXmpDcumentMetaData();
+ int writeXmpDocumentMetaData();
int writeOutputIntent();
void writePageRoot();
void writeDestsRoot();
@@ -308,14 +304,22 @@ private:
QDataStream* stream;
int streampos;
- int writeImage(const QByteArray &data, int width, int height, int depth,
+ enum class WriteImageOption
+ {
+ Monochrome,
+ Grayscale,
+ RGB,
+ CMYK,
+ };
+
+ int writeImage(const QByteArray &data, int width, int height, WriteImageOption option,
int maskObject, int softMaskObject, bool dct = false, bool isMono = false);
void writePage();
int addXrefEntry(int object, bool printostr = true);
void printString(QStringView string);
void xprintf(const char* fmt, ...);
- inline void write(const QByteArray &data) {
+ inline void write(QByteArrayView data) {
stream->writeRawData(data.constData(), data.size());
streampos += data.size();
}
diff --git a/src/gui/painting/qpixellayout.cpp b/src/gui/painting/qpixellayout.cpp
index e6dc774b2d..4f2f0ae13a 100644
--- a/src/gui/painting/qpixellayout.cpp
+++ b/src/gui/painting/qpixellayout.cpp
@@ -7,6 +7,7 @@
#include "qpixellayout_p.h"
#include "qrgba64_p.h"
#include <QtCore/private/qsimd_p.h>
+#include <QtGui/private/qcmyk_p.h>
QT_BEGIN_NAMESPACE
@@ -1657,6 +1658,66 @@ static const QRgba64 *QT_FASTCALL fetchRGBA32FPMToRGBA64PM(QRgba64 *buffer, cons
return buffer;
}
+inline const uint *qt_convertCMYK8888ToARGB32PM(uint *buffer, const uint *src, int count)
+{
+ UNALIASED_CONVERSION_LOOP(buffer, src, count, [](uint s) {
+ const QColor color = QCmyk32::fromCmyk32(s).toColor();
+ return color.rgba();
+ });
+ return buffer;
+}
+
+static void QT_FASTCALL convertCMYK8888ToARGB32PM(uint *buffer, int count, const QList<QRgb> *)
+{
+ qt_convertCMYK8888ToARGB32PM(buffer, buffer, count);
+}
+
+static const QRgba64 *QT_FASTCALL convertCMYK8888ToToRGBA64PM(QRgba64 *buffer, const uint *src, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QCmyk32::fromCmyk32(src[i]).toColor().rgba64();
+ return buffer;
+}
+
+static const uint *QT_FASTCALL fetchCMYK8888ToARGB32PM(uint *buffer, const uchar *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ const uint *s = reinterpret_cast<const uint *>(src) + index;
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QCmyk32::fromCmyk32(s[i]).toColor().rgba();
+ return buffer;
+}
+
+static const QRgba64 *QT_FASTCALL fetchCMYK8888ToRGBA64PM(QRgba64 *buffer, const uchar *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ const uint *s = reinterpret_cast<const uint *>(src) + index;
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QCmyk32::fromCmyk32(s[i]).toColor().rgba64();
+ return buffer;
+}
+
+static void QT_FASTCALL storeCMYK8888FromARGB32PM(uchar *dest, const uint *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i) {
+ QColor c = qUnpremultiply(src[i]);
+ d[i] = QCmyk32::fromColor(c).toUint();
+ }
+}
+
+static void QT_FASTCALL storeCMYK8888FromRGB32(uchar *dest, const uint *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i) {
+ QColor c = src[i];
+ d[i] = QCmyk32::fromColor(c).toUint();
+ }
+}
+
// Note:
// convertToArgb32() assumes that no color channel is less than 4 bits.
// storeRGBFromARGB32PM() assumes that no color channel is more than 8 bits.
@@ -1779,6 +1840,10 @@ QPixelLayout qPixelLayouts[] = {
convertPassThrough, nullptr,
fetchRGB32FToRGB32, fetchRGBA32FPMToRGBA64PM,
storeRGB32FFromRGB32, storeRGB32FFromRGB32 }, // Format_RGBA32FPx4_Premultiplied
+ { false, false, QPixelLayout::BPP32, nullptr,
+ convertCMYK8888ToARGB32PM, convertCMYK8888ToToRGBA64PM,
+ fetchCMYK8888ToARGB32PM, fetchCMYK8888ToRGBA64PM,
+ storeCMYK8888FromARGB32PM, storeCMYK8888FromRGB32 }, // Format_CMYK8888
};
static_assert(std::size(qPixelLayouts) == QImage::NImageFormats);
@@ -1916,6 +1981,14 @@ static void QT_FASTCALL storeRGBA32FPMFromRGBA64PM(uchar *dest, const QRgba64 *s
d[i] = qConvertRgb64ToRgbaF32(src[i]);
}
+static void QT_FASTCALL storeCMYKFromRGBA64PM(uchar *dest, const QRgba64 *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i)
+ d[i] = QCmyk32::fromColor(QColor(src[i])).toUint();
+}
+
ConvertAndStorePixelsFunc64 qStoreFromRGBA64PM[] = {
nullptr,
nullptr,
@@ -1953,6 +2026,7 @@ ConvertAndStorePixelsFunc64 qStoreFromRGBA64PM[] = {
storeRGBX32FFromRGBA64PM,
storeRGBA32FFromRGBA64PM,
storeRGBA32FPMFromRGBA64PM,
+ storeCMYKFromRGBA64PM,
};
static_assert(std::size(qStoreFromRGBA64PM) == QImage::NImageFormats);
@@ -2002,6 +2076,15 @@ static const QRgbaFloat32 * QT_FASTCALL convertRGB30ToRGBA32F(QRgbaFloat32 *buff
return buffer;
}
+static const QRgbaFloat32 * QT_FASTCALL convertCMYKToRGBA32F(QRgbaFloat32 *buffer, const uint *src, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QRgbaFloat32::fromArgb32(QCmyk32::fromCmyk32(src[i]).toColor().rgba());
+
+ return buffer;
+}
+
ConvertToFPFunc qConvertToRGBA32F[] = {
nullptr,
convertIndexedTo<QRgbaFloat32>,
@@ -2039,6 +2122,7 @@ ConvertToFPFunc qConvertToRGBA32F[] = {
nullptr,
nullptr,
nullptr,
+ convertCMYKToRGBA32F,
};
static_assert(std::size(qConvertToRGBA32F) == QImage::NImageFormats);
@@ -2107,6 +2191,16 @@ static const QRgbaFloat32 *QT_FASTCALL fetchRGBA32F(QRgbaFloat32 *, const uchar
return s;
}
+static const QRgbaFloat32 *QT_FASTCALL fetchCMYKToRGBA32F(QRgbaFloat32 *buffer, const uchar *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ const uint *s = reinterpret_cast<const uint *>(src) + index;
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QRgbaFloat32::fromArgb32(QCmyk32::fromCmyk32(s[i]).toColor().rgba());
+
+ return buffer;
+}
+
FetchAndConvertPixelsFuncFP qFetchToRGBA32F[] = {
nullptr,
fetchIndexedToRGBA32F<QPixelLayout::BPP1MSB>,
@@ -2144,6 +2238,7 @@ FetchAndConvertPixelsFuncFP qFetchToRGBA32F[] = {
fetchRGBA32F,
fetchRGBA32FToRGBA32F,
fetchRGBA32F,
+ fetchCMYKToRGBA32F,
};
static_assert(std::size(qFetchToRGBA32F) == QImage::NImageFormats);
@@ -2284,6 +2379,16 @@ static void QT_FASTCALL storeRGBA32FPMFromRGBA32F(uchar *dest, const QRgbaFloat3
}
}
+static void QT_FASTCALL storeCMYKFromRGBA32F(uchar *dest, const QRgbaFloat32 *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i) {
+ // Yikes, this really needs enablers in QColor and friends
+ d[i] = QCmyk32::fromColor(QColor(src[i].toArgb32())).toUint();
+ }
+}
+
ConvertAndStorePixelsFuncFP qStoreFromRGBA32F[] = {
nullptr,
nullptr,
@@ -2321,6 +2426,7 @@ ConvertAndStorePixelsFuncFP qStoreFromRGBA32F[] = {
storeRGBX32FFromRGBA32F,
storeRGBA32FFromRGBA32F,
storeRGBA32FPMFromRGBA32F,
+ storeCMYKFromRGBA32F,
};
static_assert(std::size(qStoreFromRGBA32F) == QImage::NImageFormats);
diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp
index f7c4df40c8..21e89d67fd 100644
--- a/src/gui/painting/qplatformbackingstore.cpp
+++ b/src/gui/painting/qplatformbackingstore.cpp
@@ -10,6 +10,8 @@
#include <QtCore/private/qobject_p.h>
+#include <unordered_map>
+
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaBackingStore, "qt.qpa.backingstore", QtWarningMsg);
@@ -26,11 +28,14 @@ public:
QWindow *window;
QBackingStore *backingStore;
- // The order matters. if it needs to be rearranged in the future, call
- // reset() explicitly from the dtor in the correct order.
- // (first the compositor, then the rhiSupport)
- QBackingStoreRhiSupport rhiSupport;
- QBackingStoreDefaultCompositor compositor;
+ struct SurfaceSupport {
+ // The order matters. if it needs to be rearranged in the future, call
+ // reset() explicitly from the dtor in the correct order.
+ // (first the compositor, then the rhiSupport)
+ QBackingStoreRhiSupport rhiSupport;
+ QBackingStoreDefaultCompositor compositor;
+ };
+ std::unordered_map<QSurface::SurfaceType, SurfaceSupport> surfaceSupport;
};
struct QBackingstoreTextureInfo
@@ -210,8 +215,12 @@ QPlatformBackingStore::FlushResult QPlatformBackingStore::rhiFlush(QWindow *wind
QPlatformTextureList *textures,
bool translucentBackground)
{
- return d_ptr->compositor.flush(this, d_ptr->rhiSupport.rhi(), d_ptr->rhiSupport.swapChainForWindow(window),
- window, sourceDevicePixelRatio, region, offset, textures, translucentBackground);
+ auto &surfaceSupport = d_ptr->surfaceSupport[window->surfaceType()];
+ return surfaceSupport.compositor.flush(this,
+ surfaceSupport.rhiSupport.rhi(),
+ surfaceSupport.rhiSupport.swapChainForWindow(window),
+ window, sourceDevicePixelRatio, region, offset, textures,
+ translucentBackground);
}
/*!
@@ -261,7 +270,10 @@ QRhiTexture *QPlatformBackingStore::toTexture(QRhiResourceUpdateBatch *resourceU
const QRegion &dirtyRegion,
TextureFlags *flags) const
{
- return d_ptr->compositor.toTexture(this, d_ptr->rhiSupport.rhi(), resourceUpdates, dirtyRegion, flags);
+ auto &surfaceSupport = d_ptr->surfaceSupport[window()->surfaceType()];
+ return surfaceSupport.compositor.toTexture(this,
+ surfaceSupport.rhiSupport.rhi(), resourceUpdates,
+ dirtyRegion, flags);
}
/*!
@@ -356,33 +368,44 @@ bool QPlatformBackingStore::scroll(const QRegion &area, int dx, int dy)
return false;
}
-void QPlatformBackingStore::setRhiConfig(const QPlatformBackingStoreRhiConfig &config)
+void QPlatformBackingStore::createRhi(QWindow *window, QPlatformBackingStoreRhiConfig config)
{
if (!config.isEnabled())
return;
- d_ptr->rhiSupport.setConfig(config);
- d_ptr->rhiSupport.setWindow(d_ptr->window);
- d_ptr->rhiSupport.setFormat(d_ptr->window->format());
- d_ptr->rhiSupport.create();
+ qCDebug(lcQpaBackingStore) << "Setting up RHI support in" << this
+ << "for" << window << "with" << window->surfaceType()
+ << "and requested API" << config.api();
+
+ auto &support = d_ptr->surfaceSupport[window->surfaceType()];
+ if (!support.rhiSupport.rhi()) {
+ support.rhiSupport.setConfig(config);
+ support.rhiSupport.setWindow(window);
+ support.rhiSupport.setFormat(window->format());
+ support.rhiSupport.create();
+ } else {
+ qCDebug(lcQpaBackingStore) << "Window already has RHI support"
+ << "with backend" << support.rhiSupport.rhi()->backendName();
+ }
}
-QRhi *QPlatformBackingStore::rhi() const
+QRhi *QPlatformBackingStore::rhi(QWindow *window) const
{
// Returning null is valid, and means this is not a QRhi-capable backingstore.
- return d_ptr->rhiSupport.rhi();
+ return d_ptr->surfaceSupport[window->surfaceType()].rhiSupport.rhi();
}
-void QPlatformBackingStore::graphicsDeviceReportedLost()
+void QPlatformBackingStore::graphicsDeviceReportedLost(QWindow *window)
{
- if (!d_ptr->rhiSupport.rhi())
+ auto &surfaceSupport = d_ptr->surfaceSupport[window->surfaceType()];
+ if (!surfaceSupport.rhiSupport.rhi())
return;
qWarning("Rhi backingstore: graphics device lost, attempting to reinitialize");
- d_ptr->compositor.reset();
- d_ptr->rhiSupport.reset();
- d_ptr->rhiSupport.create();
- if (!d_ptr->rhiSupport.rhi())
+ surfaceSupport.compositor.reset();
+ surfaceSupport.rhiSupport.reset();
+ surfaceSupport.rhiSupport.create();
+ if (!surfaceSupport.rhiSupport.rhi())
qWarning("Rhi backingstore: failed to reinitialize after losing the device");
}
diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h
index 1d55e9ab6a..a6cb43b4e6 100644
--- a/src/gui/painting/qplatformbackingstore.h
+++ b/src/gui/painting/qplatformbackingstore.h
@@ -23,7 +23,7 @@
QT_BEGIN_NAMESPACE
-Q_DECLARE_EXPORTED_LOGGING_CATEGORY(lcQpaBackingStore, Q_GUI_EXPORT)
+QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(lcQpaBackingStore, Q_GUI_EXPORT)
class QRegion;
class QRect;
@@ -39,6 +39,8 @@ class QRhiResourceUpdateBatch;
struct Q_GUI_EXPORT QPlatformBackingStoreRhiConfig
{
+ Q_GADGET
+public:
enum Api {
OpenGL,
Metal,
@@ -47,6 +49,7 @@ struct Q_GUI_EXPORT QPlatformBackingStoreRhiConfig
D3D12,
Null
};
+ Q_ENUM(Api)
QPlatformBackingStoreRhiConfig()
: m_enable(false)
@@ -171,10 +174,10 @@ public:
virtual void beginPaint(const QRegion &);
virtual void endPaint();
- void setRhiConfig(const QPlatformBackingStoreRhiConfig &config);
- QRhi *rhi() const;
+ void createRhi(QWindow *window, QPlatformBackingStoreRhiConfig config);
+ QRhi *rhi(QWindow *window) const;
void surfaceAboutToBeDestroyed();
- void graphicsDeviceReportedLost();
+ void graphicsDeviceReportedLost(QWindow *window);
private:
QPlatformBackingStorePrivate *d_ptr;
diff --git a/src/gui/painting/qregion.cpp b/src/gui/painting/qregion.cpp
index 8b712ee46d..f9089d7bba 100644
--- a/src/gui/painting/qregion.cpp
+++ b/src/gui/painting/qregion.cpp
@@ -876,9 +876,15 @@ QRegion QRegion::intersect(const QRect &r) const
/*!
\fn void QRegion::setRects(const QRect *rects, int number)
+ \overload
+ \obsolete Use the QSpan overload instead.
+*/
+
+/*!
+ \fn void QRegion::setRects(QSpan<const QRect> rects)
+ \since 6.8
- Sets the region using the array of rectangles specified by \a rects and
- \a number.
+ Sets the region using the array of rectangles specified by \a rects.
The rectangles \e must be optimally Y-X sorted and follow these restrictions:
\list
@@ -892,6 +898,11 @@ QRegion QRegion::intersect(const QRect &r) const
\omit
Only some platforms have these restrictions (Qt for Embedded Linux, X11 and \macos).
\endomit
+
+ \note For historical reasons, \c{rects.size()} must be less than \c{INT_MAX}
+ (see rectCount()).
+
+ \sa rects()
*/
namespace {
@@ -4214,18 +4225,39 @@ QRegion::const_iterator QRegion::end() const noexcept
return d->qt_rgn ? d->qt_rgn->end() : nullptr;
}
-void QRegion::setRects(const QRect *rects, int num)
+static Q_DECL_COLD_FUNCTION
+void set_rects_warn(const char *what)
+{
+ qWarning("QRegion::setRects(): %s", what);
+}
+
+void QRegion::setRects(const QRect *r, int n)
{
+ if (!r && n) { // old setRects() allowed this, but QSpan doesn't
+ set_rects_warn("passing num != 0 when rects == nullptr is deprecated.");
+ n = 0;
+ }
+ setRects(QSpan<const QRect>(r, n));
+}
+
+void QRegion::setRects(QSpan<const QRect> rects)
+{
+ const auto num = int(rects.size());
+ if (num != rects.size()) {
+ set_rects_warn("span size exceeds INT_MAX, ignoring");
+ return;
+ }
+
*this = QRegion();
- if (!rects || num == 0 || (num == 1 && rects->isEmpty()))
+ if (!rects.data() || num == 0 || (num == 1 && rects.front().isEmpty()))
return;
detach();
d->qt_rgn->numRects = num;
if (num == 1) {
- d->qt_rgn->extents = *rects;
- d->qt_rgn->innerRect = *rects;
+ d->qt_rgn->extents = rects.front();
+ d->qt_rgn->innerRect = rects.front();
} else {
d->qt_rgn->rects.resize(num);
@@ -4246,12 +4278,30 @@ void QRegion::setRects(const QRect *rects, int num)
}
}
+/*!
+ \since 6.8
+
+ Returns a span of non-overlapping rectangles that make up the region. The
+ span remains valid until the next call of a mutating (non-const) method on
+ this region.
+
+ The union of all the rectangles is equal to the original region.
+
+ \note This functions existed in Qt 5, too, but returned QVector<QRect>
+ instead.
+
+ \sa setRects()
+*/
+QSpan<const QRect> QRegion::rects() const noexcept
+{
+ return {begin(), end()};
+};
+
int QRegion::rectCount() const noexcept
{
return (d->qt_rgn ? d->qt_rgn->numRects : 0);
}
-
bool QRegion::operator==(const QRegion &r) const
{
if (!d->qt_rgn)
diff --git a/src/gui/painting/qregion.h b/src/gui/painting/qregion.h
index b0051b6067..4b852815f3 100644
--- a/src/gui/painting/qregion.h
+++ b/src/gui/painting/qregion.h
@@ -8,11 +8,11 @@
#include <QtCore/qatomic.h>
#include <QtCore/qrect.h>
#include <QtGui/qwindowdefs.h>
-#include <QtCore/qcontainerfwd.h>
#ifndef QT_NO_DATASTREAM
#include <QtCore/qdatastream.h>
#endif
+#include <QtCore/qspan.h>
QT_BEGIN_NAMESPACE
@@ -75,6 +75,8 @@ public:
QRect boundingRect() const noexcept;
void setRects(const QRect *rect, int num);
+ void setRects(QSpan<const QRect> r);
+ QSpan<const QRect> rects() const noexcept;
int rectCount() const noexcept;
QRegion operator|(const QRegion &r) const;
diff --git a/src/gui/painting/qrhibackingstore.cpp b/src/gui/painting/qrhibackingstore.cpp
index 72ab28f1fc..d59cc2d83c 100644
--- a/src/gui/painting/qrhibackingstore.cpp
+++ b/src/gui/painting/qrhibackingstore.cpp
@@ -20,30 +20,27 @@ void QRhiBackingStore::flush(QWindow *flushedWindow, const QRegion &region, cons
Q_UNUSED(region);
Q_UNUSED(offset);
- if (flushedWindow->surfaceType() != window()->surfaceType()) {
- qWarning() << "Cannot flush child window" << flushedWindow
- << "with surface type" << flushedWindow->surfaceType() << ";"
- << "Must match" << window()->surfaceType() << "of" << window();
-
- // FIXME: Support different surface types by not tying the
- // RHI config to the backing store itself (per window config).
- return;
- }
-
- if (!rhi()) {
+ if (!rhi(flushedWindow)) {
QPlatformBackingStoreRhiConfig rhiConfig;
- switch (window()->surfaceType()) {
+ switch (flushedWindow->surfaceType()) {
case QSurface::OpenGLSurface:
rhiConfig.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
break;
case QSurface::MetalSurface:
rhiConfig.setApi(QPlatformBackingStoreRhiConfig::Metal);
break;
+ case QSurface::Direct3DSurface:
+ rhiConfig.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+ break;
+ case QSurface::VulkanSurface:
+ rhiConfig.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
+ break;
default:
Q_UNREACHABLE();
}
+
rhiConfig.setEnabled(true);
- setRhiConfig(rhiConfig);
+ createRhi(flushedWindow, rhiConfig);
}
static QPlatformTextureList emptyTextureList;
@@ -60,7 +57,7 @@ QImage::Format QRhiBackingStore::format() const
// image must have an alpha channel. Hence upgrading the format. Matches
// what other platforms (Windows, xcb) do.
if (QImage::toPixelFormat(fmt).alphaUsage() != QPixelFormat::UsesAlpha)
- fmt = qt_maybeAlphaVersionWithSameDepth(fmt);
+ fmt = qt_maybeDataCompatibleAlphaVersion(fmt);
return fmt;
}