Add CMYK support to QColorSpace

[ChangeLog][QtGui][QColorSpace] Support for CMYK color spaces has been
added.

Change-Id: I2c684dbeee8b97fc90ca4e2a892349a7fa465d06
Reviewed-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
This commit is contained in:
Allan Sandfeld Jensen 2024-02-22 14:45:22 +01:00
parent e794894ece
commit 25c96d547b
18 changed files with 628 additions and 199 deletions

View File

@ -21,6 +21,7 @@
#include <stdlib.h>
#include <limits.h>
#include <qpa/qplatformpixmap.h>
#include <private/qcolorspace_p.h>
#include <private/qcolortransform_p.h>
#include <private/qmemrotate_p.h>
#include <private/qimagescale_p.h>
@ -45,6 +46,7 @@
#include <memory>
QT_BEGIN_NAMESPACE
class QCmyk32;
using namespace Qt::StringLiterals;
@ -2635,6 +2637,9 @@ void QImage::setPixel(int x, int y, uint index_or_rgb)
case Format_A2RGB30_Premultiplied:
((uint *)s)[x] = qConvertArgb32ToA2rgb30<PixelOrderRGB>(index_or_rgb);
return;
case Format_RGBX64:
((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb | 0xff000000);
return;
case Format_RGBA64:
case Format_RGBA64_Premultiplied:
((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb);
@ -5035,6 +5040,8 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace)
return;
}
applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace));
if (d->ref.loadRelaxed() != 1)
detachMetadata(false);
d->colorSpace = colorSpace;
}
@ -5151,6 +5158,13 @@ void QImage::applyColorTransform(const QColorTransform &transform)
{
if (transform.isIdentity())
return;
if (!qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel) ||
!qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel)) {
qWarning() << "QImage::applyColorTransform can not apply format switching transform without switching format";
return;
}
detach();
if (!d)
return;
@ -5169,7 +5183,7 @@ 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_CMYK8888
&& oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
if (hasAlphaChannel())
convertTo(QImage::Format_ARGB32);
@ -5187,6 +5201,7 @@ void QImage::applyColorTransform(const QColorTransform &transform)
case Format_Grayscale8:
case Format_Grayscale16:
case Format_RGB32:
case Format_CMYK8888:
case Format_RGBX64:
case Format_RGBX32FPx4:
flags = QColorTransformPrivate::InputOpaque;
@ -5229,6 +5244,13 @@ void QImage::applyColorTransform(const QColorTransform &transform)
QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags);
}
};
} else if (oldFormat == QImage::Format_CMYK8888) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
QCmyk32 *scanline = reinterpret_cast<QCmyk32 *>(d->data + y * d->bytes_per_line);
QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags);
}
};
} else {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
@ -5312,6 +5334,8 @@ QImage QImage::colorTransformed(const QColorTransform &transform) const &
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::Cmyk:
return colorTransformed(transform, QImage::Format_CMYK8888);
case QColorSpace::ColorModel::Undefined:
break;
}
@ -5410,6 +5434,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
case QImage::Format_RGBA64_Premultiplied:
case QImage::Format_Grayscale8:
case QImage::Format_Grayscale16:
case QImage::Format_CMYK8888:
// can be output natively
break;
case QImage::Format_RGB16:
@ -5419,7 +5444,6 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
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:
@ -5486,7 +5510,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
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());
QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
@ -5494,7 +5518,26 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
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());
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
} else if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Cmyk) {
// Gray -> CMYK
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);
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + 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);
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
@ -5505,8 +5548,8 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
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());
const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
@ -5514,12 +5557,91 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
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());
const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
} else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Gray) {
// CMYK -> Gray
if (tmpFormat == QImage::Format_Grayscale8) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
} else {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
} else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Rgb) {
// CMYK -> RGB
if (isRgb32Data(tmpFormat) ) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
} else if (isRgb64Data(tmpFormat)) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
} else {
Q_ASSERT(isRgb32fpx4Data(tmpFormat));
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
} else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Cmyk) {
// RGB -> CMYK
if (!fromImage.hasAlphaChannel())
transFlags = QColorTransformPrivate::InputOpaque;
else if (qPixelLayouts[fromImage.format()].premultiplied)
transFlags = QColorTransformPrivate::Premultiplied;
if (isRgb32Data(fromImage.format()) ) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + 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.constBits() + y * fromImage.bytesPerLine());
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
}
};
} else {
Q_ASSERT(isRgb32fpx4Data(fromImage.format()));
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
}
};
}
} else {
Q_UNREACHABLE();
}
@ -5541,7 +5663,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
&& 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_ARGB32_Premultiplied && oldFormat != QImage::Format_CMYK8888
&& oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
if (hasAlphaChannel())
fromImage.convertTo(QImage::Format_ARGB32);
@ -5557,13 +5679,13 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
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());
const quint8 *in_scanline = reinterpret_cast<const quint8 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
if (tmpFormat == Format_Grayscale8) {
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.bits() + y * toImage.bytesPerLine());
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + 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());
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
}
}
@ -5571,30 +5693,39 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
} 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());
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
}
};
} else if (isRgb32fpx4Data(fromImage.format())) {
} else if (fromImage.format() == Format_CMYK8888) {
Q_ASSERT(tmpFormat == Format_CMYK8888);
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());
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
}
};
} else if (isRgb32fpx4Data(fromImage.format())) {
Q_ASSERT(isRgb32fpx4Data(tmpFormat));
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + 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());
const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
if (isRgb32fpx4Data(tmpFormat)) {
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + 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());
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
}
}
@ -5602,16 +5733,16 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
} 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());
const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine());
if (isRgb32fpx4Data(tmpFormat)) {
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + 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());
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + 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());
QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine());
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
}
}
@ -5671,6 +5802,8 @@ QImage QImage::colorTransformed(const QColorTransform &transform) &&
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::Cmyk:
return colorTransformed(transform, QImage::Format_CMYK8888);
case QColorSpace::ColorModel::Undefined:
break;
}

View File

@ -448,6 +448,8 @@ inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format)
return QColorSpace::ColorModel::Undefined; // No valid colors
case QPixelFormat::ColorModel::Grayscale:
return QColorSpace::ColorModel::Gray;
case QPixelFormat::ColorModel::CMYK:
return QColorSpace::ColorModel::Cmyk;
default:
break;
}
@ -467,9 +469,8 @@ inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::
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;
if (dataCs == QColorSpace::ColorModel::Gray)
return true; // Can apply any CS with white point to Gray data
return false;
}

View File

@ -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() == 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
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];
}
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
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];
}
};

View File

@ -28,20 +28,20 @@ class QColorVector
{
public:
QColorVector() = default;
constexpr QColorVector(float x, float y, float z) : x(x), y(y), z(z) { }
constexpr QColorVector(float x, float y, float z, float w = 0.0f) noexcept : x(x), y(y), z(z), w(w) { }
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;
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,10 +59,10 @@ 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
{
@ -198,7 +198,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.

View File

@ -468,6 +468,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
Q_ASSERT(transferFunction == QColorSpace::TransferFunction::Custom);
transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
colorModel = QColorSpace::ColorModel::Rgb;
isPcsLab = false;
mAB.clear();
mBA.clear();
@ -576,6 +577,8 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\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.
*/
/*!
@ -1151,7 +1154,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,
@ -1175,6 +1179,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) {
@ -1231,6 +1237,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())

View File

@ -54,6 +54,7 @@ public:
Undefined = 0,
Rgb = 1,
Gray = 2,
Cmyk = 3,
};
Q_ENUM(ColorModel)

View File

@ -114,7 +114,7 @@ public:
// Element list processing data:
struct TransferElement {
QColorTrc trc[3];
QColorTrc trc[4];
};
using Element = std::variant<TransferElement, QColorMatrix, QColorVector, QColorCLUT>;
bool isPcsLab = false;

View File

@ -45,7 +45,7 @@ public:
Q_ASSERT(qsizetype(size) <= table.size());
}
bool isEmpty() const
bool isEmpty() const noexcept
{
return m_tableSize == 0;
}

View File

@ -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"
@ -244,38 +245,31 @@ QColor QColorTransform::map(const QColor &color) const
if (!d)
return color;
QColor clr = color;
if (color.spec() != QColor::ExtendedRgb || color.spec() != QColor::Rgb)
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);
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);
}
QColor out;
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;
}
@ -404,6 +398,10 @@ 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);
@ -978,8 +976,7 @@ static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len,
}
template<>
void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst,
const QColorVector *buffer, const qsizetype len,
void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
@ -1272,6 +1269,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;
@ -1302,6 +1311,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) {
@ -1321,7 +1335,6 @@ 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)
{
@ -1344,6 +1357,19 @@ void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buf
}
}
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)
@ -1408,6 +1434,24 @@ void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffe
}
}
template<>
void storePremultipliedLUT(QRgb *dst, const QCmyk32 *, 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<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 *, const T *, const QColorVector *, const qsizetype);
@ -1423,6 +1467,17 @@ void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *bu
}
}
template<>
void storePremultipliedLUT(QRgba64 *dst, const QCmyk32 *, 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 QRgba64 *src, const QColorVector *buffer, const qsizetype len)
{
@ -1449,10 +1504,13 @@ static void storePremultipliedLUT(QRgbaFloat32 *dst, const T *src, const QColorV
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);
}
}
@ -1573,6 +1631,8 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const
template<typename S>
void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
{
// Avoid compiling this part for S=QCmyk32:
if constexpr (!std::is_same_v<S, QCmyk32>) {
if (colorSpaceIn->isThreeComponentMatrix()) {
if (flags & InputPremultiplied)
loadPremultiplied(buffer, src, len, this);
@ -1581,7 +1641,10 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer,
if (!colorSpaceOut->isThreeComponentMatrix())
applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the first half only.
} else {
return;
}
}
if (!colorSpaceIn->isThreeComponentMatrix()) {
if (flags & InputPremultiplied)
loadPremultipliedLUT(buffer, src, len);
else
@ -1600,16 +1663,25 @@ 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;
// Avoid compiling this part for D=QCmyk32:
if constexpr (!std::is_same_v<D, QCmyk32>) {
if (colorSpaceOut->isThreeComponentMatrix()) {
applyMatrix<doClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
if constexpr (std::is_same_v<S, QCmyk32>) {
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);
} else {
}
return;
}
}
if (!colorSpaceOut->isThreeComponentMatrix()) {
// Do element based conversion
for (auto &&element : colorSpaceOut->mBA)
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
@ -1709,9 +1781,10 @@ void QColorTransformPrivate::applyThreeComponentMatrix(D *dst, const S *src, qsi
template<typename D, typename S>
void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{
if constexpr (!std::is_same_v<D, QCmyk32> && !std::is_same_v<S, QCmyk32>) {
if (isThreeComponentMatrix())
applyThreeComponentMatrix<D, S>(dst, src, count, flags);
else
return applyThreeComponentMatrix<D, S>(dst, src, count, flags);
}
applyElementListTransform<D, S>(dst, src, count, flags);
}
@ -1724,20 +1797,24 @@ template<typename D, typename S>
void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{
Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
updateLutsOut();
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);
applyConvertIn(src, 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();
}
applyMatrix<DoClamp>(buffer, len, colorMatrix);
storeOpaque(dst + i, buffer, len, this);
@ -1746,12 +1823,11 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
}
return;
}
if constexpr (!std::is_same_v<S, QCmyk32>) {
if (!colorMatrix.isValid())
return;
updateLutsIn();
updateLutsOut();
QUninitialized<QColorVector, WorkBlockSize> buffer;
@ -1769,6 +1845,9 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
i += len;
}
} else {
Q_UNREACHABLE();
}
}
/*!
@ -1778,7 +1857,8 @@ 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>) {
updateLutsIn();
if constexpr (std::is_same_v<D, QRgb> || std::is_same_v<D, QRgba64> || std::is_same_v<D, QRgbaFloat32> || std::is_same_v<D, QCmyk32>) {
if (!colorSpaceOut->isThreeComponentMatrix()) {
QUninitialized<QColorVector, WorkBlockSize> buffer;
@ -1789,6 +1869,15 @@ void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, Tr
applyMatrix<DoClamp>(buffer, len, colorMatrix);
// 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();
}
// Do element based conversion
for (auto &&element : colorSpaceOut->mBA)
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
@ -1803,10 +1892,10 @@ void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, Tr
}
}
Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
if constexpr (!std::is_same_v<D, QCmyk32>) {
if (!colorMatrix.isValid())
return;
updateLutsIn();
updateLutsOut();
QUninitialized<QColorVector, WorkBlockSize> buffer;
@ -1821,6 +1910,9 @@ void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, Tr
storeOpaque(dst + i, buffer, len, this);
i += len;
}
} else {
Q_UNREACHABLE();
}
}
/*!
@ -1853,17 +1945,28 @@ void QColorTransformPrivate::prepare()
// 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<quint8, QCmyk32>(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::applyReturnGray<quint16, QCmyk32>(quint16 *dst, const QCmyk32 *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<QCmyk32, quint8>(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::applyGray<QCmyk32, quint16>(QCmyk32 *dst, const quint16 *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<QRgb, QCmyk32>(QRgb *dst, const QCmyk32 *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, 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;

View File

@ -22,6 +22,7 @@
#include <QtGui/qrgbafloat.h>
QT_BEGIN_NAMESPACE
class QCmyk32;
class QColorTransformPrivate : public QSharedData
{

View File

@ -297,12 +297,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;
}
@ -679,13 +680,23 @@ 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)
{
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;
};
}
}
@ -723,6 +734,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;
}
@ -746,12 +761,12 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
if (lut.inputChannels != 3) {
if (lut.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.inputChannels == 4)) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
return false;
}
if (lut.outputChannels != 3) {
if (lut.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.outputChannels == 4)) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
return false;
}
@ -782,15 +797,18 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
clutElement.table.resize(clutTableSize);
clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
if (lut.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, lut.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);
qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
parseCLUT(clutTable.constData(), f, &clutElement);
parseCLUT(clutTable.constData(), f, &clutElement, lut.outputChannels);
}
tableData += clutTableSize * lut.outputChannels * precision;
@ -846,12 +864,12 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
if (mab.inputChannels != 3) {
if (mab.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.inputChannels == 4)) {
qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
return false;
}
if (mab.outputChannels != 3) {
if (mab.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.outputChannels == 4)) {
qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
return false;
}
@ -901,7 +919,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 ? mab.outputChannels : mab.inputChannels)) {
qCWarning(lcIcc) << "Invalid B curves";
return false;
} else {
@ -910,7 +928,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 ? mab.inputChannels : mab.outputChannels)) {
qCWarning(lcIcc) << "Invalid A curves";
return false;
} else {
@ -951,9 +969,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";
@ -963,7 +982,7 @@ 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;
const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) {
qCWarning(lcIcc) << "CLUT oversized for tag";
return false;
@ -973,10 +992,10 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
if (precision == 2) {
QList<uint16_t> clutTable(clutTableSize * mab.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, mab.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, mab.outputChannels);
}
}
@ -987,7 +1006,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 && mab.outputChannels == 3) {
if (!mCurvesAreLinear)
colorSpacePrivate->mAB.append(std::move(mTableElement));
if (!matrixElement.isIdentity())
@ -1000,7 +1019,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
} else {
if (!bCurvesAreLinear)
colorSpacePrivate->mBA.append(std::move(bTableElement));
if (mab.mCurvesOffset) {
if (mab.mCurvesOffset && mab.inputChannels == 3) {
if (!matrixElement.isIdentity())
colorSpacePrivate->mBA.append(std::move(matrixElement));
if (!offsetElement.isNull())
@ -1285,11 +1304,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;
}
}
@ -1298,6 +1317,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();
}
@ -1338,6 +1363,9 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
} 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

View File

@ -171,6 +171,8 @@ private slots:
void colorSpaceRgbConversion_data();
void colorSpaceRgbConversion();
void colorSpaceCmykConversion_data();
void colorSpaceCmykConversion();
void deepCopyWhenPaintingActive();
void scaled_QTBUG19157();
@ -3322,6 +3324,69 @@ void tst_QImage::colorSpaceRgbConversion()
}
void tst_QImage::colorSpaceCmykConversion_data()
{
QTest::addColumn<QImage::Format>("toFormat");
QImage::Format formats[] = {
QImage::Format_RGB32,
QImage::Format_ARGB32,
QImage::Format_ARGB32_Premultiplied,
QImage::Format_RGBX64,
QImage::Format_RGBA64,
QImage::Format_RGBA64_Premultiplied,
QImage::Format_RGBX32FPx4,
QImage::Format_RGBA32FPx4,
QImage::Format_RGBA32FPx4_Premultiplied,
QImage::Format_Grayscale8,
QImage::Format_Grayscale16,
};
for (auto toFormat : formats)
QTest::addRow("CMYK8888 -> %s", formatToString(toFormat).data()) << toFormat;
}
void tst_QImage::colorSpaceCmykConversion()
{
QFETCH(QImage::Format, toFormat);
bool dstGrayscale = toFormat == QImage::Format_Grayscale8 || toFormat == QImage::Format_Grayscale16;
QImage image(16, 16, QImage::Format_CMYK8888);
QFile iccProfile(m_prefix +"CGATS001Compat-v2-micro.icc");
iccProfile.open(QIODevice::ReadOnly);
image.setColorSpace(QColorSpace::fromIccProfile(iccProfile.readAll()));
QVERIFY(image.colorSpace().isValid());
for (int i = 0; i < image.height(); ++i) {
for (int j = 0; j < image.width(); ++j) {
if (dstGrayscale)
image.setPixel(j, i, qRgb((i + j) * 8, (i + j) * 8, (i + j) * 8));
else
image.setPixel(j, i, qRgb(j * 16, i * 16, (i + j) * 8));
}
}
QImage imageConverted = image.convertedToColorSpace(QColorSpace::SRgb, toFormat);
QCOMPARE(imageConverted.format(), toFormat);
QCOMPARE(imageConverted.size(), image.size());
if (dstGrayscale) {
int gray = 0;
for (int x = 0; x < image.width(); ++x) {
int newGray = qGray(imageConverted.pixel(x, 6));
QCOMPARE_GE(newGray, gray);
gray = newGray;
}
} else {
int red = 0;
for (int x = 0; x < image.width(); ++x) {
int newRed = qRed(imageConverted.pixel(x, 5));
QCOMPARE_GE(newRed, red);
red = newRed;
}
}
}
void tst_QImage::deepCopyWhenPaintingActive()
{
QImage image(64, 64, QImage::Format_ARGB32_Premultiplied);

View File

@ -188,21 +188,41 @@ void tst_QColorSpace::fromIccProfile_data()
QTest::addColumn<QString>("testProfile");
QTest::addColumn<QColorSpace::NamedColorSpace>("namedColorSpace");
QTest::addColumn<QColorSpace::TransferFunction>("transferFunction");
QTest::addColumn<QColorSpace::TransformModel>("transformModel");
QTest::addColumn<QColorSpace::ColorModel>("colorModel");
QTest::addColumn<QString>("description");
QString prefix = QFINDTESTDATA("resources/");
// Read the official sRGB ICCv2 profile:
QTest::newRow("sRGB2014 (ICCv2)") << prefix + "sRGB2014.icc" << QColorSpace::SRgb
<< QColorSpace::TransferFunction::SRgb << QString("sRGB2014");
<< QColorSpace::TransferFunction::SRgb
<< QColorSpace::TransformModel::ThreeComponentMatrix
<< QColorSpace::ColorModel::Rgb << QString("sRGB2014");
// My monitor's profile:
QTest::newRow("HP ZR30w (ICCv4)") << prefix + "HP_ZR30w.icc" << QColorSpace::NamedColorSpace(0)
<< QColorSpace::TransferFunction::Gamma << QString("HP Z30i");
<< QColorSpace::TransferFunction::Gamma
<< QColorSpace::TransformModel::ThreeComponentMatrix
<< QColorSpace::ColorModel::Rgb << QString("HP Z30i");
// A profile to HD TV
QTest::newRow("VideoHD") << prefix + "VideoHD.icc" << QColorSpace::NamedColorSpace(0)
<< QColorSpace::TransferFunction::Custom << QString("HDTV (Rec. 709)");
<< QColorSpace::TransferFunction::Custom
<< QColorSpace::TransformModel::ElementListProcessing
<< QColorSpace::ColorModel::Rgb << QString("HDTV (Rec. 709)");
// sRGB on PCSLab format
QTest::newRow("sRGB ICCv4 Appearance") << prefix + "sRGB_ICC_v4_Appearance.icc" << QColorSpace::NamedColorSpace(0)
<< QColorSpace::TransferFunction::Custom << QString("sRGB_ICC_v4_Appearance.icc");
<< QColorSpace::TransferFunction::Custom
<< QColorSpace::TransformModel::ElementListProcessing
<< QColorSpace::ColorModel::Rgb << QString("sRGB_ICC_v4_Appearance.icc");
// Grayscale profile
QTest::newRow("sGrey-v4") << prefix + "sGrey-v4.icc" << QColorSpace::NamedColorSpace(0)
<< QColorSpace::TransferFunction::SRgb
<< QColorSpace::TransformModel::ThreeComponentMatrix
<< QColorSpace::ColorModel::Gray << QString("sGry");
// CMYK profile
QTest::newRow("CGATS compat") << prefix + "CGATS001Compat-v2-micro.icc" << QColorSpace::NamedColorSpace(0)
<< QColorSpace::TransferFunction::Custom
<< QColorSpace::TransformModel::ElementListProcessing
<< QColorSpace::ColorModel::Cmyk << QString("uCMY");
}
void tst_QColorSpace::fromIccProfile()
@ -210,6 +230,8 @@ void tst_QColorSpace::fromIccProfile()
QFETCH(QString, testProfile);
QFETCH(QColorSpace::NamedColorSpace, namedColorSpace);
QFETCH(QColorSpace::TransferFunction, transferFunction);
QFETCH(QColorSpace::TransformModel, transformModel);
QFETCH(QColorSpace::ColorModel, colorModel);
QFETCH(QString, description);
QFile file(testProfile);
@ -222,6 +244,8 @@ void tst_QColorSpace::fromIccProfile()
QCOMPARE(fileColorSpace, namedColorSpace);
QCOMPARE(fileColorSpace.transferFunction(), transferFunction);
QCOMPARE(fileColorSpace.transformModel(), transformModel);
QCOMPARE(fileColorSpace.colorModel(), colorModel);
QCOMPARE(fileColorSpace.description(), description);
QByteArray iccProfile2 = fileColorSpace.iccProfile();

View File

@ -4,9 +4,25 @@
#include <cstdlib>
#include <QGuiApplication>
#include <QColor>
#include <QColorSpace>
#include <QImage>
static QImage::Format toFormat(QColorSpace::ColorModel model)
{
switch (model) {
case QColorSpace::ColorModel::Rgb:
return QImage::Format_RGB32;
case QColorSpace::ColorModel::Gray:
return QImage::Format_Grayscale16;
case QColorSpace::ColorModel::Cmyk:
return QImage::Format_CMYK8888;
case QColorSpace::ColorModel::Undefined:
break;
}
return QImage::Format_Invalid;
}
extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
// to reduce noise and increase speed
static char quiet[] = "QT_LOGGING_RULES=qt.gui.icc=false";
@ -27,9 +43,9 @@ extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
cs2.setDescription("Hello");
bool b = (cs == cs2);
Q_UNUSED(b);
QRgb color = 0xfaf8fa00;
QColor color(0xfaf8fa00);
color = trans1.map(color);
QImage img(16, 2, cs.colorModel() == QColorSpace::ColorModel::Rgb ? QImage::Format_RGB32 : QImage::Format_Grayscale8);
QImage img(16, 2, toFormat(cs.colorModel()));
img.setColorSpace(cs);
QImage img2 = img.convertedToColorSpace(QColorSpace::SRgb);
if (cs.isValidTarget()) {

View File

@ -79,10 +79,11 @@ bool ImageViewer::loadFile(const QString &fileName)
void ImageViewer::setImage(const QImage &newImage)
{
if (newImage.colorSpace().isValid())
image = newImage.convertedToColorSpace(QColorSpace::SRgb);
else
image = newImage;
if (image.colorSpace().isValid())
image.convertToColorSpace(QColorSpace::SRgb);
imageLabel->setPixmap(QPixmap::fromImage(image));
imageLabel->setPixmap(QPixmap::fromImage(image, Qt::NoFormatConversion));
//! [4]
scaleFactor = 1.0;