summaryrefslogtreecommitdiffstats
path: root/src/gui
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2024-03-14 10:05:31 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2024-04-05 18:40:47 +0200
commit05b84673045a5f4432a6caa9bea08d8fba1e1a03 (patch)
tree68ecd72c6448dffe970f079120e5c6a14e4c4c63 /src/gui
parenta786404036990c595e25122fae5f710f12bc397b (diff)
Add color space model, making gray color spaces explicit
This also adds image conversion of both format and color space, which will also be required later for conversions to CMYK formats and color spaces. Change-Id: I578c0a010ffcdb4df4cf9080c0621fac8bc342bf Reviewed-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/image/qimage.cpp516
-rw-r--r--src/gui/image/qimage.h11
-rw-r--r--src/gui/image/qimage_p.h37
-rw-r--r--src/gui/painting/qcolormatrix_p.h34
-rw-r--r--src/gui/painting/qcolorspace.cpp182
-rw-r--r--src/gui/painting/qcolorspace.h11
-rw-r--r--src/gui/painting/qcolorspace_p.h3
-rw-r--r--src/gui/painting/qcolortransfertable_p.h2
-rw-r--r--src/gui/painting/qcolortransform.cpp450
-rw-r--r--src/gui/painting/qcolortransform_p.h30
-rw-r--r--src/gui/painting/qicc.cpp12
11 files changed, 1071 insertions, 217 deletions
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp
index 08798c4664..9d9aa31649 100644
--- a/src/gui/image/qimage.cpp
+++ b/src/gui/image/qimage.cpp
@@ -5000,6 +5000,9 @@ void QImage::setColorSpace(const QColorSpace &colorSpace)
return;
if (d->colorSpace == colorSpace)
return;
+ if (colorSpace.isValid() && !qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel()))
+ return;
+
detachMetadata(false);
if (d)
d->colorSpace = colorSpace;
@@ -5012,13 +5015,14 @@ void QImage::setColorSpace(const QColorSpace &colorSpace)
If the image has no valid color space, the method does nothing.
+ \note If \a colorSpace is not compatible with the current format, the image
+ will be converted to one that is.
+
\sa convertedToColorSpace(), setColorSpace()
*/
void QImage::convertToColorSpace(const QColorSpace &colorSpace)
{
- if (!d)
- return;
- if (!d->colorSpace.isValid())
+ if (!d || !d->colorSpace.isValid())
return;
if (!colorSpace.isValidTarget()) {
qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
@@ -5026,24 +5030,63 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace)
}
if (d->colorSpace == colorSpace)
return;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) {
+ *this = convertedToColorSpace(colorSpace);
+ return;
+ }
applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace));
d->colorSpace = colorSpace;
}
/*!
+ \since 6.8
+
+ Converts the image to \a colorSpace and \a format.
+
+ If the image has no valid color space, the method does nothing,
+ nor if the color space is not compatible with with the format.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+
+ \sa convertedToColorSpace(), setColorSpace()
+*/
+void QImage::convertToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags)
+{
+ if (!d || !d->colorSpace.isValid())
+ return;
+ if (!colorSpace.isValidTarget()) {
+ qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
+ return;
+ }
+ if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
+ qWarning() << "QImage::convertToColorSpace: Color space is not compatible with format";
+ return;
+ }
+
+ if (d->colorSpace == colorSpace)
+ return convertTo(format, flags);
+ applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace), format, flags);
+ d->colorSpace = colorSpace;
+}
+
+/*!
\since 5.14
Returns the image converted to \a colorSpace.
If the image has no valid color space, a null QImage is returned.
- \sa convertToColorSpace()
+ \note If \a colorSpace is not compatible with the current format,
+ the returned image will also be converted to a format this is.
+ For more control over returned image format, see the three argument
+ overload of this method.
+
+ \sa convertToColorSpace(), colorTransformed()
*/
QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const
{
- if (!d)
- return QImage();
- if (!d->colorSpace.isValid())
+ if (!d || !d->colorSpace.isValid())
return QImage();
if (!colorSpace.isValidTarget()) {
qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid";
@@ -5051,8 +5094,39 @@ QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const
}
if (d->colorSpace == colorSpace)
return *this;
- QImage image = copy();
- image.convertToColorSpace(colorSpace);
+ QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace));
+ image.setColorSpace(colorSpace);
+ return image;
+}
+
+/*!
+ \since 6.8
+
+ Returns the image converted to \a colorSpace and \a format.
+
+ If the image has no valid color space, a null QImage is returned.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+
+ \sa colorTransformed()
+*/
+QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags) const
+{
+ if (!d || !d->colorSpace.isValid())
+ return QImage();
+ if (!colorSpace.isValidTarget()) {
+ qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid";
+ return QImage();
+ }
+ if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
+ qWarning() << "QImage::convertedToColorSpace: Color space is not compatible with format";
+ return QImage();
+ }
+ if (d->colorSpace == colorSpace)
+ return convertedTo(format, flags);
+ QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace), format, flags);
+ image.setColorSpace(colorSpace);
return image;
}
@@ -5095,7 +5169,8 @@ void QImage::applyColorTransform(const QColorTransform &transform)
&& oldFormat != QImage::Format_RGBA64_Premultiplied)
convertTo(QImage::Format_RGBA64);
} else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32
- && oldFormat != QImage::Format_ARGB32_Premultiplied) {
+ && oldFormat != QImage::Format_ARGB32_Premultiplied
+ && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
if (hasAlphaChannel())
convertTo(QImage::Format_ARGB32);
else
@@ -5109,6 +5184,8 @@ void QImage::applyColorTransform(const QColorTransform &transform)
case Format_RGBA32FPx4_Premultiplied:
flags = QColorTransformPrivate::Premultiplied;
break;
+ case Format_Grayscale8:
+ case Format_Grayscale16:
case Format_RGB32:
case Format_RGBX64:
case Format_RGBX32FPx4:
@@ -5124,7 +5201,21 @@ void QImage::applyColorTransform(const QColorTransform &transform)
std::function<void(int,int)> transformSegment;
- if (qt_fpColorPrecision(format())) {
+ if (format() == Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ uint8_t *scanline = reinterpret_cast<uint8_t *>(d->data + y * d->bytes_per_line);
+ QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
+ }
+ };
+ } else if (format() == Format_Grayscale16) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ uint16_t *scanline = reinterpret_cast<uint16_t *>(d->data + y * d->bytes_per_line);
+ QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
+ }
+ };
+ } else if (qt_fpColorPrecision(format())) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
QRgbaFloat32 *scanline = reinterpret_cast<QRgbaFloat32 *>(d->data + y * d->bytes_per_line);
@@ -5172,23 +5263,388 @@ void QImage::applyColorTransform(const QColorTransform &transform)
}
/*!
+ \since 6.8
+
+ Applies the color transformation \a transform to all pixels in the image, and converts the format of the image to \a toFormat.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+*/
+void QImage::applyColorTransform(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags)
+{
+ if (!d)
+ return;
+ if (transform.isIdentity())
+ return convertTo(toFormat, flags);
+
+ *this = colorTransformed(transform, toFormat, flags);
+}
+
+/*!
\since 6.4
Returns the image color transformed using \a transform on all pixels in the image.
+ \note If \a transform has a source color space which is incompatible with the format of this image,
+ returns a null QImage. If \a transform has a target color space which is incompatible with the format
+ of this image, the image will also be converted to a compatible format. For more control about the
+ choice of the target pixel format, see the three argument overload of this method.
+
\sa applyColorTransform()
*/
QImage QImage::colorTransformed(const QColorTransform &transform) const &
{
- if (!d || !d->colorSpace.isValid())
+ if (!d)
return QImage();
if (transform.isIdentity())
return *this;
+
+ QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
+ QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
+ return QImage();
+ }
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
+ // All model switching transforms are opaque in at least one end.
+ switch (outColorModel) {
+ case QColorSpace::ColorModel::Rgb:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
+ case QColorSpace::ColorModel::Gray:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
+ case QColorSpace::ColorModel::Undefined:
+ break;
+ }
+ return QImage();
+ }
+
QImage image = copy();
image.applyColorTransform(transform);
return image;
}
+static bool isRgb32Data(QImage::Format f)
+{
+ switch (f) {
+ case QImage::Format_RGB32:
+ case QImage::Format_ARGB32:
+ case QImage::Format_ARGB32_Premultiplied:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static bool isRgb64Data(QImage::Format f)
+{
+ switch (f) {
+ case QImage::Format_RGBX64:
+ case QImage::Format_RGBA64:
+ case QImage::Format_RGBA64_Premultiplied:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static bool isRgb32fpx4Data(QImage::Format f)
+{
+ switch (f) {
+ case QImage::Format_RGBX32FPx4:
+ case QImage::Format_RGBA32FPx4:
+ case QImage::Format_RGBA32FPx4_Premultiplied:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+/*!
+ \since 6.8
+
+ Returns the image color transformed using \a transform on all pixels in the image, returning an image of format \a toFormat.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+
+ \note If \a transform has a source color space which is incompatible with the format of this image,
+ or a target color space that is incompatible with \a toFormat, returns a null QImage.
+
+ \sa applyColorTransform()
+*/
+QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags) const &
+{
+ if (!d)
+ return QImage();
+ if (toFormat == QImage::Format_Invalid)
+ toFormat = format();
+ if (transform.isIdentity())
+ return convertedTo(toFormat, flags);
+
+ QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
+ QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
+ return QImage();
+ }
+ if (!qt_compatibleColorModel(toPixelFormat(toFormat).colorModel(), outColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid output color space for transform";
+ return QImage();
+ }
+
+ QImage fromImage = *this;
+
+ QImage::Format tmpFormat = toFormat;
+ switch (toFormat) {
+ case QImage::Format_RGB32:
+ case QImage::Format_ARGB32:
+ case QImage::Format_ARGB32_Premultiplied:
+ case QImage::Format_RGBX32FPx4:
+ case QImage::Format_RGBA32FPx4:
+ case QImage::Format_RGBA32FPx4_Premultiplied:
+ case QImage::Format_RGBX64:
+ case QImage::Format_RGBA64:
+ case QImage::Format_RGBA64_Premultiplied:
+ case QImage::Format_Grayscale8:
+ case QImage::Format_Grayscale16:
+ // can be output natively
+ break;
+ case QImage::Format_RGB16:
+ case QImage::Format_RGB444:
+ case QImage::Format_RGB555:
+ case QImage::Format_RGB666:
+ case QImage::Format_RGB888:
+ case QImage::Format_BGR888:
+ case QImage::Format_RGBX8888:
+ case QImage::Format_CMYK8888:
+ tmpFormat = QImage::Format_RGB32;
+ break;
+ case QImage::Format_Mono:
+ case QImage::Format_MonoLSB:
+ case QImage::Format_Indexed8:
+ case QImage::Format_ARGB8565_Premultiplied:
+ case QImage::Format_ARGB6666_Premultiplied:
+ case QImage::Format_ARGB8555_Premultiplied:
+ case QImage::Format_ARGB4444_Premultiplied:
+ case QImage::Format_RGBA8888:
+ case QImage::Format_RGBA8888_Premultiplied:
+ tmpFormat = QImage::Format_ARGB32;
+ break;
+ case QImage::Format_BGR30:
+ case QImage::Format_RGB30:
+ tmpFormat = QImage::Format_RGBX64;
+ break;
+ case QImage::Format_A2BGR30_Premultiplied:
+ case QImage::Format_A2RGB30_Premultiplied:
+ tmpFormat = QImage::Format_RGBA64;
+ break;
+ case QImage::Format_RGBX16FPx4:
+ case QImage::Format_RGBA16FPx4:
+ case QImage::Format_RGBA16FPx4_Premultiplied:
+ tmpFormat = QImage::Format_RGBA32FPx4;
+ break;
+ case QImage::Format_Alpha8:
+ return convertedTo(QImage::Format_Alpha8);
+ case QImage::Format_Invalid:
+ case QImage::NImageFormats:
+ Q_UNREACHABLE();
+ break;
+ }
+ QColorSpace::ColorModel inColorData = qt_csColorData(pixelFormat().colorModel());
+ QColorSpace::ColorModel outColorData = qt_csColorData(toPixelFormat(toFormat).colorModel());
+ // Ensure only precision increasing transforms
+ if (inColorData != outColorData) {
+ if (fromImage.format() == QImage::Format_Grayscale8 && outColorData == QColorSpace::ColorModel::Rgb)
+ tmpFormat = QImage::Format_RGB32;
+ else if (tmpFormat == QImage::Format_Grayscale8 && qt_highColorPrecision(fromImage.format()))
+ tmpFormat = QImage::Format_Grayscale16;
+ else if (fromImage.format() == QImage::Format_Grayscale16 && outColorData == QColorSpace::ColorModel::Rgb)
+ tmpFormat = QImage::Format_RGBX64;
+ } else {
+ if (tmpFormat == QImage::Format_Grayscale8 && fromImage.format() == QImage::Format_Grayscale16)
+ tmpFormat = QImage::Format_Grayscale16;
+ else if (qt_fpColorPrecision(fromImage.format()) && !qt_fpColorPrecision(tmpFormat))
+ tmpFormat = QImage::Format_RGBA32FPx4;
+ else if (isRgb32Data(tmpFormat) && qt_highColorPrecision(fromImage.format(), true))
+ tmpFormat = QImage::Format_RGBA64;
+ }
+
+ QImage toImage(size(), tmpFormat);
+ copyMetadata(&toImage, *this);
+
+ std::function<void(int, int)> transformSegment;
+ QColorTransformPrivate::TransformFlags transFlags = QColorTransformPrivate::Unpremultiplied;
+
+ if (inColorData != outColorData) {
+ // Needs color model switching transform
+ if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Rgb) {
+ // Gray -> RGB
+ if (format() == QImage::Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line);
+ QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line);
+ QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ }
+ } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Gray) {
+ // RGB -> Gray
+ if (tmpFormat == QImage::Format_Grayscale8) {
+ fromImage.convertTo(QImage::Format_RGB32);
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.bits() + y * fromImage.bytesPerLine());
+ quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else {
+ fromImage.convertTo(QImage::Format_RGBX64);
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.bits() + y * fromImage.bytesPerLine());
+ quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ }
+ } else {
+ Q_UNREACHABLE();
+ }
+ } else {
+ // Conversion on same color model
+ if (pixelFormat().colorModel() == QPixelFormat::Indexed) {
+ for (int i = 0; i < d->colortable.size(); ++i)
+ fromImage.d->colortable[i] = transform.map(d->colortable[i]);
+ return fromImage.convertedTo(toFormat, flags);
+ }
+
+ QImage::Format oldFormat = format();
+ if (qt_fpColorPrecision(oldFormat)) {
+ if (oldFormat != QImage::Format_RGBX32FPx4 && oldFormat != QImage::Format_RGBA32FPx4
+ && oldFormat != QImage::Format_RGBA32FPx4_Premultiplied)
+ fromImage.convertTo(QImage::Format_RGBA32FPx4);
+ } else if (qt_highColorPrecision(oldFormat, true)) {
+ if (oldFormat != QImage::Format_RGBX64 && oldFormat != QImage::Format_RGBA64
+ && oldFormat != QImage::Format_RGBA64_Premultiplied && oldFormat != QImage::Format_Grayscale16)
+ fromImage.convertTo(QImage::Format_RGBA64);
+ } else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32
+ && oldFormat != QImage::Format_ARGB32_Premultiplied
+ && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
+ if (hasAlphaChannel())
+ fromImage.convertTo(QImage::Format_ARGB32);
+ else
+ fromImage.convertTo(QImage::Format_RGB32);
+ }
+
+ if (!fromImage.hasAlphaChannel())
+ transFlags = QColorTransformPrivate::InputOpaque;
+ else if (qPixelLayouts[fromImage.format()].premultiplied)
+ transFlags = QColorTransformPrivate::Premultiplied;
+
+ if (fromImage.format() == Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint8 *in_scanline = reinterpret_cast<const quint8 *>(fromImage.bits() + y * fromImage.bytesPerLine());
+ if (tmpFormat == Format_Grayscale8) {
+ quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ } else {
+ Q_ASSERT(tmpFormat == Format_Grayscale16);
+ quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ }
+ }
+ };
+ } else if (fromImage.format() == Format_Grayscale16) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.bits() + y * fromImage.bytesPerLine());
+ quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ } else if (isRgb32fpx4Data(fromImage.format())) {
+ transformSegment = [&](int yStart, int yEnd) {
+ Q_ASSERT(isRgb32fpx4Data(tmpFormat));
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.bits() + y * fromImage.bytesPerLine());
+ QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ } else if (isRgb64Data(fromImage.format())) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.bits() + y * fromImage.bytesPerLine());
+ if (isRgb32fpx4Data(tmpFormat)) {
+ QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ } else {
+ Q_ASSERT(isRgb64Data(tmpFormat));
+ QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ }
+ };
+ } else {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.bits() + y * fromImage.bytesPerLine());
+ if (isRgb32fpx4Data(tmpFormat)) {
+ QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ } else if (isRgb64Data(tmpFormat)) {
+ QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ } else {
+ Q_ASSERT(isRgb32Data(tmpFormat));
+ QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.bits() + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ }
+ };
+ }
+ }
+
+#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
+ int segments = (qsizetype(width()) * height()) >> 16;
+ segments = std::min(segments, height());
+ QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
+ if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
+ QSemaphore semaphore;
+ int y = 0;
+ for (int i = 0; i < segments; ++i) {
+ int yn = (height() - y) / (segments - i);
+ threadPool->start([&, y, yn]() {
+ transformSegment(y, y + yn);
+ semaphore.release(1);
+ });
+ y += yn;
+ }
+ semaphore.acquire(segments);
+ } else
+#endif
+ transformSegment(0, height());
+
+ if (tmpFormat != toFormat)
+ toImage.convertTo(toFormat);
+
+ return toImage;
+}
+
/*!
\since 6.4
\overload
@@ -5199,12 +5655,46 @@ QImage QImage::colorTransformed(const QColorTransform &transform) const &
*/
QImage QImage::colorTransformed(const QColorTransform &transform) &&
{
- if (!d || !d->colorSpace.isValid())
+ if (!d)
+ return QImage();
+
+ QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
+ QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
return QImage();
+ }
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
+ // There is currently no inplace conversion of both colorspace and format, so just use the normal version.
+ switch (outColorModel) {
+ case QColorSpace::ColorModel::Rgb:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
+ case QColorSpace::ColorModel::Gray:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
+ case QColorSpace::ColorModel::Undefined:
+ break;
+ }
+ return QImage();
+ }
+
applyColorTransform(transform);
return std::move(*this);
}
+/*!
+ \since 6.8
+ \overload
+
+ Returns the image color transformed using \a transform on all pixels in the image.
+
+ \sa applyColorTransform()
+*/
+QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags) &&
+{
+ // There is currently no inplace conversion of both colorspace and format, so just use the normal version.
+ return colorTransformed(transform, format, flags);
+}
+
bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags)
{
if (format == newFormat)
diff --git a/src/gui/image/qimage.h b/src/gui/image/qimage.h
index 1208e9ae67..cba50e5e4c 100644
--- a/src/gui/image/qimage.h
+++ b/src/gui/image/qimage.h
@@ -231,13 +231,18 @@ public:
void invertPixels(InvertMode = InvertRgb);
QColorSpace colorSpace() const;
- [[nodiscard]] QImage convertedToColorSpace(const QColorSpace &) const;
- void convertToColorSpace(const QColorSpace &);
- void setColorSpace(const QColorSpace &);
+ [[nodiscard]] QImage convertedToColorSpace(const QColorSpace &colorSpace) const;
+ [[nodiscard]] QImage convertedToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) const;
+ void convertToColorSpace(const QColorSpace &colorSpace);
+ void convertToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor);
+ void setColorSpace(const QColorSpace &colorSpace);
QImage colorTransformed(const QColorTransform &transform) const &;
+ QImage colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) const &;
QImage colorTransformed(const QColorTransform &transform) &&;
+ QImage colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) &&;
void applyColorTransform(const QColorTransform &transform);
+ void applyColorTransform(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor);
bool load(QIODevice *device, const char *format);
bool load(const QString &fileName, const char *format = nullptr);
diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h
index 2edf03a4f2..9957e93f31 100644
--- a/src/gui/image/qimage_p.h
+++ b/src/gui/image/qimage_p.h
@@ -437,6 +437,43 @@ inline bool qt_fpColorPrecision(QImage::Format format)
return false;
}
+inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format)
+{
+ switch (format) {
+ case QPixelFormat::ColorModel::RGB:
+ case QPixelFormat::ColorModel::BGR:
+ case QPixelFormat::ColorModel::Indexed:
+ return QColorSpace::ColorModel::Rgb;
+ case QPixelFormat::ColorModel::Alpha:
+ return QColorSpace::ColorModel::Undefined; // No valid colors
+ case QPixelFormat::ColorModel::Grayscale:
+ return QColorSpace::ColorModel::Gray;
+ default:
+ break;
+ }
+ return QColorSpace::ColorModel::Undefined;
+}
+
+inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs)
+{
+ QColorSpace::ColorModel dataCs = qt_csColorData(data);
+
+ if (data == QPixelFormat::ColorModel::Alpha)
+ return true; // Alpha data has no colors and can be handled by any color space
+
+ if (cs == QColorSpace::ColorModel::Undefined || dataCs == QColorSpace::ColorModel::Undefined)
+ return false;
+
+ if (dataCs == cs)
+ return true; // Matching color models
+
+ if (cs == QColorSpace::ColorModel::Rgb)
+ // Can apply RGB CS to Gray data
+ return dataCs == QColorSpace::ColorModel::Gray;
+
+ return false;
+}
+
inline QImage::Format qt_maybeDataCompatibleAlphaVersion(QImage::Format format)
{
switch (format) {
diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h
index e586e0dadc..2e6e6b5882 100644
--- a/src/gui/painting/qcolormatrix_p.h
+++ b/src/gui/painting/qcolormatrix_p.h
@@ -64,6 +64,14 @@ public:
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; }
+ 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); }
@@ -279,6 +287,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()
{
diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp
index 334587fd31..044468194a 100644
--- a/src/gui/painting/qcolorspace.cpp
+++ b/src/gui/painting/qcolorspace.cpp
@@ -95,34 +95,7 @@ QColorMatrix QColorSpacePrimaries::toXyzMatrix() const
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
- }
- }
+ toXyz = QColorMatrix::chromaticAdaptation(wXyz) * toXyz;
return toXyz;
}
@@ -133,6 +106,7 @@ QColorSpacePrivate::QColorSpacePrivate()
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace)
: namedColorSpace(namedColorSpace)
+ , colorModel(QColorSpace::ColorModel::Rgb)
{
switch (namedColorSpace) {
case QColorSpace::SRgb:
@@ -170,6 +144,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 +156,45 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
float gamma)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(transferFunction)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(gamma)
+ , whitePoint(primaries.whitePoint)
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
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(whitePoint)
+{
+ toXyz = QColorMatrix::chromaticAdaptation(this->whitePoint);
+ 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(whitePoint)
+{
+ toXyz = QColorMatrix::chromaticAdaptation(this->whitePoint);
+ 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 +205,12 @@ 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(primaries.whitePoint)
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
setTransferFunctionTable(transferFunctionTable);
identifyColorSpace();
initialize();
@@ -219,6 +222,7 @@ 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());
@@ -437,8 +441,9 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const
transform.d = ptr;
ptr->colorSpaceIn = this;
ptr->colorSpaceOut = this;
+ // Convert to XYZ relative to our white point, not the regular D50 white point.
if (isThreeComponentMatrix())
- ptr->colorMatrix = toXyz;
+ ptr->colorMatrix = QColorMatrix::chromaticAdaptation(whitePoint).inverted() * toXyz;
else
ptr->colorMatrix = QColorMatrix::identity();
return transform;
@@ -483,9 +488,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.
@@ -555,6 +561,17 @@ 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.
+*/
+
+/*!
\fn QColorSpace::QColorSpace()
Creates a new colorspace object that represents an undefined and invalid colorspace.
@@ -617,6 +634,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 +908,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();
}
@@ -898,14 +938,67 @@ 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->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(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(whitePoint);
+ if (d_ptr->transformModel == QColorSpace::TransformModel::ThreeComponentMatrix) {
+ if (d_ptr->colorModel == QColorSpace::ColorModel::Rgb) {
+ // Rescale toXyz to new whitepoint
+ QColorMatrix chad = QColorMatrix::chromaticAdaptation(d_ptr->whitePoint);
+ QColorMatrix rawToXyz = chad.inverted() * d_ptr->toXyz;
+ QColorVector whiteScale = rawToXyz.inverted().map(wXyz);
+ rawToXyz = rawToXyz * QColorMatrix::fromScale(whiteScale);
+ d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz) * rawToXyz;
+ } else if (d_ptr->colorModel == QColorSpace::ColorModel::Gray) {
+ 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 +1008,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 +1112,13 @@ bool QColorSpacePrivate::isValid() const noexcept
return !mAB.isEmpty();
if (!toXyz.isValid())
return false;
- if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
- return false;
+ if (colorModel == QColorSpace::ColorModel::Gray) {
+ if (!trc[0].isValid())
+ return false;
+ } else {
+ if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
+ return false;
+ }
return true;
}
diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h
index 848bc0898e..cbb1435c56 100644
--- a/src/gui/painting/qcolorspace.h
+++ b/src/gui/painting/qcolorspace.h
@@ -50,9 +50,17 @@ public:
ElementListProcessing,
};
Q_ENUM(TransformModel)
+ enum class ColorModel : uint8_t {
+ Undefined = 0,
+ Rgb = 1,
+ Gray = 2,
+ };
+ 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 +112,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..ac09825a69 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,6 +103,7 @@ 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;
diff --git a/src/gui/painting/qcolortransfertable_p.h b/src/gui/painting/qcolortransfertable_p.h
index 5b1bd67476..d857e3942e 100644
--- a/src/gui/painting/qcolortransfertable_p.h
+++ b/src/gui/painting/qcolortransfertable_p.h
@@ -97,6 +97,8 @@ 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));
diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp
index 8d578d7af3..884e338304 100644
--- a/src/gui/painting/qcolortransform.cpp
+++ b/src/gui/painting/qcolortransform.cpp
@@ -346,6 +346,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 +395,25 @@ inline int getAlpha<QRgb>(const QRgb &p)
template<>
inline int getAlpha<QRgba64>(const QRgba64 &p)
{ return p.alpha(); }
+
#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 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);
@@ -768,15 +817,16 @@ 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 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
- constexpr bool isARGB = isArgb<T>();
+ constexpr bool isARGB = isArgb<D>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]);
__m128 vf = _mm_loadu_ps(&buffer[i].x);
__m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
__m128 va = _mm_mul_ps(_mm_set1_ps(a), iFF00);
@@ -789,21 +839,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 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);
@@ -850,14 +900,15 @@ 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>();
+ constexpr bool isARGB = isArgb<D>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]);
__m128 vf = _mm_loadu_ps(&buffer[i].x);
__m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
const int ridx = _mm_extract_epi16(v, 0);
@@ -867,21 +918,21 @@ 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 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);
@@ -907,10 +958,9 @@ 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);
constexpr bool isARGB = isArgb<T>();
for (qsizetype i = 0; i < len; ++i) {
@@ -928,11 +978,10 @@ static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const
}
template<>
-void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
+void storeOpaque<QRgbaFloat32>(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 vZero = _mm_set1_ps(0.0f);
const __m128 vOne = _mm_set1_ps(1.0f);
@@ -976,14 +1025,15 @@ 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>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]);
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)));
const int ridx = vgetq_lane_u32(v, 0);
@@ -998,7 +1048,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,13 +1070,14 @@ 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>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[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))));
const int ridx = vget_lane_u16(v, 0);
@@ -1035,15 +1086,14 @@ static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffe
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);
@@ -1082,10 +1132,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,11 +1143,12 @@ 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)];
@@ -1107,21 +1157,22 @@ static void storePremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVec
}
}
-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 +1182,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 +1195,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 +1208,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 +1219,35 @@ 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)
+{
+ 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;
+ }
+}
+
+static void loadGray(QColorVector *buffer, const quint16 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
+{
+ 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;
+ }
+}
+
+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;
@@ -1255,7 +1322,19 @@ void loadPremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const q
}
}
-static void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
+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);
+ }
+}
+
+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 +1344,60 @@ static void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVect
}
}
-static void storeUnpremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
+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 *, const T *, const QColorVector *, const qsizetype);
+
+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 +1408,23 @@ 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(QRgba64 *, const T *, const QColorVector *, const qsizetype);
+
+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 +1435,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;
@@ -1372,6 +1497,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 +1508,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 +1527,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;
}
@@ -1438,8 +1570,8 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const
return c;
}
-template<typename T>
-void QColorTransformPrivate::applyConvertIn(const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
+template<typename S>
+void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
{
if (colorSpaceIn->isThreeComponentMatrix()) {
if (flags & InputPremultiplied)
@@ -1455,20 +1587,24 @@ void QColorTransformPrivate::applyConvertIn(const T *src, QColorVector *buffer,
else
loadUnpremultipliedLUT(buffer, src, len);
+ if constexpr (std::is_same_v<S, QRgbaFloat16> || std::is_same_v<S, QRgbaFloat32>)
+ clampIfNeeded<DoClamp>(buffer, 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
+template<typename D, typename S>
+void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
{
+ constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp;
if (colorSpaceOut->isThreeComponentMatrix()) {
- applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
+ applyMatrix<doClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
if (flags & InputOpaque)
- storeOpaque(dst, src, buffer, len, this);
+ storeOpaque(dst, buffer, len, this);
else if (flags & OutputPremultiplied)
storePremultiplied(dst, src, buffer, len, this);
else
@@ -1478,11 +1614,7 @@ void QColorTransformPrivate::applyConvertOut(T *dst, const T *src, QColorVector
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);
@@ -1491,8 +1623,8 @@ void QColorTransformPrivate::applyConvertOut(T *dst, const T *src, QColorVector
}
}
-template<typename T>
-void QColorTransformPrivate::applyElementListTransform(T *dst, const T *src, qsizetype count, TransformFlags flags) const
+template<typename D, typename S>
+void QColorTransformPrivate::applyElementListTransform(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{
Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix());
@@ -1526,8 +1658,8 @@ 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
+template<typename D, typename S>
+void QColorTransformPrivate::applyThreeComponentMatrix(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{
Q_ASSERT(colorSpaceIn->isThreeComponentMatrix() && colorSpaceOut->isThreeComponentMatrix());
@@ -1538,7 +1670,7 @@ void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsi
updateLutsOut();
bool doApplyMatrix = !colorMatrix.isIdentity();
- constexpr ApplyMatrixForm doClamp = (std::is_same_v<T, QRgbaFloat16> || std::is_same_v<T, QRgbaFloat32>) ? DoNotClamp : DoClamp;
+ constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp;
QUninitialized<QColorVector, WorkBlockSize> buffer;
qsizetype i = 0;
@@ -1551,9 +1683,11 @@ void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsi
if (doApplyMatrix)
applyMatrix<doClamp>(buffer, len, colorMatrix);
+ else
+ clampIfNeeded<doClamp>(buffer, len);
if (flags & InputOpaque)
- storeOpaque(dst + i, src + i, buffer, len, this);
+ storeOpaque(dst + i, buffer, len, this);
else if (flags & OutputPremultiplied)
storePremultiplied(dst + i, src + i, buffer, len, this);
else
@@ -1563,13 +1697,22 @@ void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsi
}
}
-template<typename T>
-void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const
+/*!
+ \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() && colorSpaceOut->isThreeComponentMatrix())
- applyThreeComponentMatrix<T>(dst, src, count, flags);
+ if (isThreeComponentMatrix())
+ applyThreeComponentMatrix<D, S>(dst, src, count, flags);
else
- applyElementListTransform<T>(dst, src, count, flags);
+ applyElementListTransform<D, S>(dst, src, count, flags);
}
/*!
@@ -1580,6 +1723,7 @@ void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, Transf
template<typename D, typename S>
void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{
+ Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
if (!colorSpaceIn->isThreeComponentMatrix()) {
QUninitialized<QColorVector, WorkBlockSize> buffer;
@@ -1595,7 +1739,8 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
for (auto &&element : colorSpaceIn->mAB)
std::visit([&](auto &&elm) { visitElement(elm, buffer, len); }, element);
- storeGray(dst + i, src + i, buffer, len, this);
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
+ storeOpaque(dst + i, buffer, len, this);
i += len;
}
@@ -1620,7 +1765,7 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
applyMatrix<DoClamp>(buffer, len, colorMatrix);
- storeGray(dst + i, src + i, buffer, len, this);
+ storeOpaque(dst + i, buffer, len, this);
i += len;
}
@@ -1628,84 +1773,99 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
/*!
\internal
- \enum QColorTransformPrivate::TransformFlag
+*/
+template<typename D, typename S>
+void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const
+{
+ Q_ASSERT(colorSpaceIn->isThreeComponentMatrix());
+ if constexpr (std::is_same_v<D, QRgb> || std::is_same_v<D, QRgba64> || std::is_same_v<D, QRgbaFloat32>) {
+ if (!colorSpaceOut->isThreeComponentMatrix()) {
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
- Defines how the transform is to be applied.
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
+ loadGray(buffer, src + i, len, this);
- \value Unpremultiplied The input and output should both be unpremultiplied.
- \value InputOpaque The input is guaranteed to be opaque.
- \value InputPremultiplied The input is premultiplied.
- \value OutputPremultiplied The output should be premultiplied.
- \value Premultiplied Both input and output should both be premultiplied.
-*/
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
-/*!
- \internal
- Prepares a color transformation for fast application. You do not need to
- call this explicitly as it will be called implicitly on the first transforms, but
- if you want predictable performance on the first transforms, you can perform it
- in advance.
+ // Do element based conversion
+ for (auto &&element : colorSpaceOut->mBA)
+ std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
+
+ clampIfNeeded<DoClamp>(buffer, len);
+
+ storeUnpremultipliedLUT(dst, src, buffer, len); // input is always opaque
+
+ i += len;
+ }
+ return;
+ }
+ }
+ Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
+ if (!colorMatrix.isValid())
+ return;
- \sa QColorTransform::map(), apply()
-*/
-void QColorTransformPrivate::prepare()
-{
updateLutsIn();
updateLutsOut();
-}
-/*!
- \internal
- Applies the color transformation on \a count QRgb pixels starting from
- \a src and stores the result in \a dst.
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
- Thread-safe if prepare() has been called first.
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
+ loadGray(buffer, src + i, len, this);
- Assumes unpremultiplied data by default. Set \a flags to change defaults.
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
- \sa prepare()
-*/
-void QColorTransformPrivate::apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const
-{
- apply<QRgb>(dst, src, count, flags);
+ storeOpaque(dst + i, buffer, len, this);
+ i += len;
+ }
}
/*!
\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.
+ \enum QColorTransformPrivate::TransformFlag
- Assumes unpremultiplied data by default. Set \a flags to change defaults.
+ Defines how the transform should handle alpha values.
- \sa prepare()
+ \value Unpremultiplied The input and output should both be unpremultiplied.
+ \value InputOpaque The input is guaranteed to be opaque.
+ \value InputPremultiplied The input is premultiplied.
+ \value OutputPremultiplied The output should be premultiplied.
+ \value Premultiplied Both input and output should both be premultiplied.
*/
-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.
+ Prepares a color transformation for fast application. You do not need to
+ call this explicitly as it will be called implicitly on the first transforms, but
+ if you want predictable performance on the first transforms, you can perform it
+ in advance.
- \sa prepare()
+ \sa QColorTransform::map(), apply()
*/
-void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count,
- TransformFlags flags) const
+void QColorTransformPrivate::prepare()
{
- apply<QRgbaFloat32>(dst, src, count, flags);
+ updateLutsIn();
+ updateLutsOut();
}
-
+// Only allow versions increasing precision
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;
+template void QColorTransformPrivate::applyGray<quint8, quint8>(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<quint16, quint8>(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<quint16, quint16>(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<QRgb, quint8>(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<QRgba64, quint16>(QRgba64 *dst, const quint16 *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<QRgba64, QRgb>(QRgba64 *dst, const QRgb *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, 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;
bool QColorTransformPrivate::isThreeComponentMatrix() const
{
diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h
index 1d54aced1b..92b8b3cc20 100644
--- a/src/gui/painting/qcolortransform_p.h
+++ b/src/gui/painting/qcolortransform_p.h
@@ -51,26 +51,22 @@ 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 apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+ template<typename D, typename S>
+ void applyGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
template<typename D, typename S>
void applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+private:
+ 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;
+ template<typename D, typename S>
+ void applyElementListTransform(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+ template<typename D, typename S>
+ void applyThreeComponentMatrix(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
};
QT_END_NAMESPACE
diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp
index dcebdfd1b0..316ac352ee 100644
--- a/src/gui/painting/qicc.cpp
+++ b/src/gui/painting/qicc.cpp
@@ -782,7 +782,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
@@ -792,7 +792,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;
@@ -964,7 +964,7 @@ 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)
@@ -983,6 +983,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;
@@ -1269,9 +1271,11 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
if (!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();
}
@@ -1285,6 +1289,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
} else {
colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
// Only parse the default perceptual transform for now
if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true))
@@ -1313,6 +1318,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorspaceDPtr->iccProfile = data;
+ Q_ASSERT(colorspaceDPtr->isValid());
return true;
}