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 <stdlib.h>
#include <limits.h> #include <limits.h>
#include <qpa/qplatformpixmap.h> #include <qpa/qplatformpixmap.h>
#include <private/qcolorspace_p.h>
#include <private/qcolortransform_p.h> #include <private/qcolortransform_p.h>
#include <private/qmemrotate_p.h> #include <private/qmemrotate_p.h>
#include <private/qimagescale_p.h> #include <private/qimagescale_p.h>
@ -45,6 +46,7 @@
#include <memory> #include <memory>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QCmyk32;
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
@ -2635,6 +2637,9 @@ void QImage::setPixel(int x, int y, uint index_or_rgb)
case Format_A2RGB30_Premultiplied: case Format_A2RGB30_Premultiplied:
((uint *)s)[x] = qConvertArgb32ToA2rgb30<PixelOrderRGB>(index_or_rgb); ((uint *)s)[x] = qConvertArgb32ToA2rgb30<PixelOrderRGB>(index_or_rgb);
return; return;
case Format_RGBX64:
((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb | 0xff000000);
return;
case Format_RGBA64: case Format_RGBA64:
case Format_RGBA64_Premultiplied: case Format_RGBA64_Premultiplied:
((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb); ((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb);
@ -5035,6 +5040,8 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace)
return; return;
} }
applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace)); applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace));
if (d->ref.loadRelaxed() != 1)
detachMetadata(false);
d->colorSpace = colorSpace; d->colorSpace = colorSpace;
} }
@ -5151,6 +5158,13 @@ void QImage::applyColorTransform(const QColorTransform &transform)
{ {
if (transform.isIdentity()) if (transform.isIdentity())
return; 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(); detach();
if (!d) if (!d)
return; return;
@ -5169,7 +5183,7 @@ void QImage::applyColorTransform(const QColorTransform &transform)
&& oldFormat != QImage::Format_RGBA64_Premultiplied) && oldFormat != QImage::Format_RGBA64_Premultiplied)
convertTo(QImage::Format_RGBA64); convertTo(QImage::Format_RGBA64);
} else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32 } 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) { && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
if (hasAlphaChannel()) if (hasAlphaChannel())
convertTo(QImage::Format_ARGB32); convertTo(QImage::Format_ARGB32);
@ -5187,6 +5201,7 @@ void QImage::applyColorTransform(const QColorTransform &transform)
case Format_Grayscale8: case Format_Grayscale8:
case Format_Grayscale16: case Format_Grayscale16:
case Format_RGB32: case Format_RGB32:
case Format_CMYK8888:
case Format_RGBX64: case Format_RGBX64:
case Format_RGBX32FPx4: case Format_RGBX32FPx4:
flags = QColorTransformPrivate::InputOpaque; flags = QColorTransformPrivate::InputOpaque;
@ -5229,6 +5244,13 @@ void QImage::applyColorTransform(const QColorTransform &transform)
QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags); 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 { } else {
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { 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); return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
case QColorSpace::ColorModel::Gray: case QColorSpace::ColorModel::Gray:
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8); 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: case QColorSpace::ColorModel::Undefined:
break; break;
} }
@ -5410,6 +5434,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
case QImage::Format_RGBA64_Premultiplied: case QImage::Format_RGBA64_Premultiplied:
case QImage::Format_Grayscale8: case QImage::Format_Grayscale8:
case QImage::Format_Grayscale16: case QImage::Format_Grayscale16:
case QImage::Format_CMYK8888:
// can be output natively // can be output natively
break; break;
case QImage::Format_RGB16: case QImage::Format_RGB16:
@ -5419,7 +5444,6 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
case QImage::Format_RGB888: case QImage::Format_RGB888:
case QImage::Format_BGR888: case QImage::Format_BGR888:
case QImage::Format_RGBX8888: case QImage::Format_RGBX8888:
case QImage::Format_CMYK8888:
tmpFormat = QImage::Format_RGB32; tmpFormat = QImage::Format_RGB32;
break; break;
case QImage::Format_Mono: case QImage::Format_Mono:
@ -5486,7 +5510,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { for (int y = yStart; y < yEnd; ++y) {
const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line); 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); 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) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { for (int y = yStart; y < yEnd; ++y) {
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line); 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); 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); fromImage.convertTo(QImage::Format_RGB32);
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { 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());
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)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); 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); fromImage.convertTo(QImage::Format_RGBX64);
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { 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());
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)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque); 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 { } else {
Q_UNREACHABLE(); Q_UNREACHABLE();
} }
@ -5541,7 +5663,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
&& oldFormat != QImage::Format_RGBA64_Premultiplied && oldFormat != QImage::Format_Grayscale16) && oldFormat != QImage::Format_RGBA64_Premultiplied && oldFormat != QImage::Format_Grayscale16)
fromImage.convertTo(QImage::Format_RGBA64); fromImage.convertTo(QImage::Format_RGBA64);
} else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32 } 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) { && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
if (hasAlphaChannel()) if (hasAlphaChannel())
fromImage.convertTo(QImage::Format_ARGB32); fromImage.convertTo(QImage::Format_ARGB32);
@ -5557,13 +5679,13 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
if (fromImage.format() == Format_Grayscale8) { if (fromImage.format() == Format_Grayscale8) {
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { 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) { 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); QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
} else { } else {
Q_ASSERT(tmpFormat == Format_Grayscale16); 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); 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) { } else if (fromImage.format() == Format_Grayscale16) {
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { for (int y = yStart; y < yEnd; ++y) {
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.bits() + y * fromImage.bytesPerLine()); const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
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); 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) { transformSegment = [&](int yStart, int yEnd) {
Q_ASSERT(isRgb32fpx4Data(tmpFormat));
for (int y = yStart; y < yEnd; ++y) { for (int y = yStart; y < yEnd; ++y) {
const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.bits() + y * fromImage.bytesPerLine()); const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.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); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
} }
}; };
} else if (isRgb64Data(fromImage.format())) { } else if (isRgb64Data(fromImage.format())) {
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { 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)) { 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); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
} else { } else {
Q_ASSERT(isRgb64Data(tmpFormat)); 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); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
} }
} }
@ -5602,16 +5733,16 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
} else { } else {
transformSegment = [&](int yStart, int yEnd) { transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) { 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)) { 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); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
} else if (isRgb64Data(tmpFormat)) { } 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); QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
} else { } else {
Q_ASSERT(isRgb32Data(tmpFormat)); 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); 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); return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
case QColorSpace::ColorModel::Gray: case QColorSpace::ColorModel::Gray:
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8); 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: case QColorSpace::ColorModel::Undefined:
break; break;
} }

View File

@ -448,6 +448,8 @@ inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format)
return QColorSpace::ColorModel::Undefined; // No valid colors return QColorSpace::ColorModel::Undefined; // No valid colors
case QPixelFormat::ColorModel::Grayscale: case QPixelFormat::ColorModel::Grayscale:
return QColorSpace::ColorModel::Gray; return QColorSpace::ColorModel::Gray;
case QPixelFormat::ColorModel::CMYK:
return QColorSpace::ColorModel::Cmyk;
default: default:
break; break;
} }
@ -467,9 +469,8 @@ inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::
if (dataCs == cs) if (dataCs == cs)
return true; // Matching color models return true; // Matching color models
if (cs == QColorSpace::ColorModel::Rgb) if (dataCs == QColorSpace::ColorModel::Gray)
// Can apply RGB CS to Gray data return true; // Can apply any CS with white point to Gray data
return dataCs == QColorSpace::ColorModel::Gray;
return false; return false;
} }

View File

@ -20,7 +20,7 @@
QT_BEGIN_NAMESPACE 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 class QColorCLUT
{ {
inline static QColorVector interpolate(const QColorVector &a, const QColorVector &b, float t) inline static QColorVector interpolate(const QColorVector &a, const QColorVector &b, float t)
@ -32,45 +32,92 @@ class QColorCLUT
a += (b - a) * t; a += (b - a) * t;
} }
public: public:
qsizetype gridPointsX = 0; uint32_t gridPointsX = 0;
qsizetype gridPointsY = 0; uint32_t gridPointsY = 0;
qsizetype gridPointsZ = 0; uint32_t gridPointsZ = 0;
uint32_t gridPointsW = 1;
QList<QColorVector> table; QList<QColorVector> table;
bool isEmpty() const { return table.isEmpty(); } bool isEmpty() const { return table.isEmpty(); }
QColorVector apply(const QColorVector &v) const 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 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 y = std::clamp(v.y, 0.0f, 1.0f) * (gridPointsY - 1);
const float z = std::clamp(v.z, 0.0f, 1.0f) * (gridPointsZ - 1); const float z = std::clamp(v.z, 0.0f, 1.0f) * (gridPointsZ - 1);
// Variables for trilinear interpolation const float w = std::clamp(v.w, 0.0f, 1.0f) * (gridPointsW - 1);
const qsizetype lox = static_cast<qsizetype>(std::floor(x)); const uint32_t lox = static_cast<uint32_t>(std::floor(x));
const qsizetype hix = std::min(lox + 1, gridPointsX - 1); const uint32_t hix = std::min(lox + 1, gridPointsX - 1);
const qsizetype loy = static_cast<qsizetype>(std::floor(y)); const uint32_t loy = static_cast<uint32_t>(std::floor(y));
const qsizetype hiy = std::min(loy + 1, gridPointsY - 1); const uint32_t hiy = std::min(loy + 1, gridPointsY - 1);
const qsizetype loz = static_cast<qsizetype>(std::floor(z)); const uint32_t loz = static_cast<uint32_t>(std::floor(z));
const qsizetype hiz = std::min(loz + 1, gridPointsZ - 1); const uint32_t hiz = std::min(loz + 1, gridPointsZ - 1);
const float fracx = x - static_cast<float>(lox); const uint32_t low = static_cast<uint32_t>(std::floor(w));
const float fracy = y - static_cast<float>(loy); const uint32_t hiw = std::min(low + 1, gridPointsW - 1);
const float fracz = z - static_cast<float>(loz); frac.x = x - static_cast<float>(lox);
QColorVector tmp[4]; frac.y = y - static_cast<float>(loy);
auto index = [&](qsizetype x, qsizetype y, qsizetype z) { return x * gridPointsZ * gridPointsY + y * gridPointsZ + z; }; 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 // interpolate over z
tmp[0] = interpolate(table[index(lox, loy, loz)], for (int i = 0; i < 4; ++i)
table[index(lox, loy, hiz)], fracz); interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z);
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);
// interpolate over y // interpolate over y
interpolateIn(tmp[0], tmp[1], fracy); for (int i = 0; i < 2; ++i)
interpolateIn(tmp[2], tmp[3], fracy); interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y);
// interpolate over x // 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]; return tmp[0];
} }
}; };

View File

@ -28,20 +28,20 @@ class QColorVector
{ {
public: public:
QColorVector() = default; 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 explicit constexpr QColorVector(const QPointF &chr) // from XY chromaticity
: x(chr.x() / chr.y()) : x(chr.x() / chr.y())
, y(1.0f) , y(1.0f)
, z((1.0f - chr.x() - chr.y()) / chr.y()) , z((1.0f - chr.x() - chr.y()) / chr.y())
{ } { }
float x = 0.0f; // X, x, L, or red float x = 0.0f; // X, x, L, or red/cyan
float y = 0.0f; // Y, y, a, or green float y = 0.0f; // Y, y, a, or green/magenta
float z = 0.0f; // Z, Y, b, or blue float z = 0.0f; // Z, Y, b, or blue/yellow
float _unused = 0.0f; float w = 0.0f; // unused, or black
constexpr bool isNull() const noexcept constexpr bool isNull() const noexcept
{ {
return !x && !y && !z; return !x && !y && !z && !w;
} }
bool isValid() const noexcept bool isValid() const noexcept
{ {
@ -59,10 +59,10 @@ public:
return true; return true;
} }
constexpr QColorVector operator*(float f) const { return QColorVector(x * f, y * f, z * f); } 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); } 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); } 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; } void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; w += v.w; }
QPointF toChromaticity() const 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)) return (std::abs(v1.x - v2.x) < (1.0f / 2048.0f))
&& (std::abs(v1.y - v2.y) < (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. // A matrix mapping 3 value colors.

View File

@ -468,6 +468,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
Q_ASSERT(transferFunction == QColorSpace::TransferFunction::Custom); Q_ASSERT(transferFunction == QColorSpace::TransferFunction::Custom);
transformModel = QColorSpace::TransformModel::ThreeComponentMatrix; transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
colorModel = QColorSpace::ColorModel::Rgb;
isPcsLab = false; isPcsLab = false;
mAB.clear(); mAB.clear();
mBA.clear(); mBA.clear();
@ -576,6 +577,8 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\value Undefined No color model \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 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 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] return element.trc[0] == other.trc[0]
&& element.trc[1] == other.trc[1] && 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, static bool compareElement(const QColorMatrix &element,
@ -1175,6 +1179,8 @@ static bool compareElement(const QColorCLUT &element,
return false; return false;
if (element.gridPointsZ != other.gridPointsZ) if (element.gridPointsZ != other.gridPointsZ)
return false; return false;
if (element.gridPointsW != other.gridPointsW)
return false;
if (element.table.size() != other.table.size()) if (element.table.size() != other.table.size())
return false; return false;
for (qsizetype i = 0; i < element.table.size(); ++i) { for (qsizetype i = 0; i < element.table.size(); ++i) {
@ -1231,6 +1237,8 @@ bool QColorSpacePrivate::equals(const QColorSpacePrivate *other) const
if (!isThreeComponentMatrix()) { if (!isThreeComponentMatrix()) {
if (isPcsLab != other->isPcsLab) if (isPcsLab != other->isPcsLab)
return false; return false;
if (colorModel != other->colorModel)
return false;
if (mAB.count() != other->mAB.count()) if (mAB.count() != other->mAB.count())
return false; return false;
if (mBA.count() != other->mBA.count()) if (mBA.count() != other->mBA.count())

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@
#include "qcolortransform.h" #include "qcolortransform.h"
#include "qcolortransform_p.h" #include "qcolortransform_p.h"
#include "qcmyk_p.h"
#include "qcolorclut_p.h" #include "qcolorclut_p.h"
#include "qcolormatrix_p.h" #include "qcolormatrix_p.h"
#include "qcolorspace_p.h" #include "qcolorspace_p.h"
@ -244,38 +245,31 @@ QColor QColorTransform::map(const QColor &color) const
if (!d) if (!d)
return color; return color;
QColor clr = 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(); 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; 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()); out.setRgbF(c.x, c.y, c.z, color.alphaF());
}
return out; return out;
} }
@ -404,6 +398,10 @@ template<> float getAlphaF(const QRgb &r)
{ {
return qAlpha(r) * (1.f / 255.f); return qAlpha(r) * (1.f / 255.f);
} }
template<> float getAlphaF(const QCmyk32 &)
{
return 1.f;
}
template<> float getAlphaF(const QRgba64 &r) template<> float getAlphaF(const QRgba64 &r)
{ {
return r.alpha() * (1.f / 65535.f); return r.alpha() * (1.f / 65535.f);
@ -978,8 +976,7 @@ static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len,
} }
template<> template<>
void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len,
const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr) const QColorTransformPrivate *d_ptr)
{ {
const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution)); 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) void loadUnpremultipliedLUT(QColorVector *buffer, const QRgba64 *src, const qsizetype len)
{ {
const float f = 1.0f / 65535.f; 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) void loadPremultipliedLUT(QColorVector *buffer, const QRgba64 *src, const qsizetype len)
{ {
for (qsizetype i = 0; i < len; ++i) { 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; buffer[i].z = src[i].b * f;
} }
} }
template<typename T> template<typename T>
static void storeUnpremultipliedLUT(QRgb *dst, const T *, const QColorVector *buffer, const qsizetype len) 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> template<typename T>
static void storeUnpremultipliedLUT(QRgba64 *dst, const T *, static void storeUnpremultipliedLUT(QRgba64 *dst, const T *,
const QColorVector *buffer, const qsizetype len) 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> template<typename T>
static void storePremultipliedLUT(QRgba64 *, const T *, const QColorVector *, const qsizetype); 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<> template<>
void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len) 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) 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) { for (qsizetype i = 0; i < len; ++i) {
buffer[i].x = element.trc[0].apply(buffer[i].x); buffer[i].x = element.trc[0].apply(buffer[i].x);
buffer[i].y = element.trc[1].apply(buffer[i].y); buffer[i].y = element.trc[1].apply(buffer[i].y);
buffer[i].z = element.trc[2].apply(buffer[i].z); 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> template<typename S>
void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const 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 (colorSpaceIn->isThreeComponentMatrix()) {
if (flags & InputPremultiplied) if (flags & InputPremultiplied)
loadPremultiplied(buffer, src, len, this); loadPremultiplied(buffer, src, len, this);
@ -1581,7 +1641,10 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer,
if (!colorSpaceOut->isThreeComponentMatrix()) if (!colorSpaceOut->isThreeComponentMatrix())
applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the first half only. applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the first half only.
} else { return;
}
}
if (!colorSpaceIn->isThreeComponentMatrix()) {
if (flags & InputPremultiplied) if (flags & InputPremultiplied)
loadPremultipliedLUT(buffer, src, len); loadPremultipliedLUT(buffer, src, len);
else 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 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; 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()) { 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 constexpr (std::is_same_v<S, QCmyk32>) {
storeOpaque(dst, buffer, len, this);
} else {
if (flags & InputOpaque) if (flags & InputOpaque)
storeOpaque(dst, buffer, len, this); storeOpaque(dst, buffer, len, this);
else if (flags & OutputPremultiplied) else if (flags & OutputPremultiplied)
storePremultiplied(dst, src, buffer, len, this); storePremultiplied(dst, src, buffer, len, this);
else else
storeUnpremultiplied(dst, src, buffer, len, this); storeUnpremultiplied(dst, src, buffer, len, this);
} else { }
return;
}
}
if (!colorSpaceOut->isThreeComponentMatrix()) {
// Do element based conversion // Do element based conversion
for (auto &&element : colorSpaceOut->mBA) for (auto &&element : colorSpaceOut->mBA)
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); 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> template<typename D, typename S>
void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const 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()) if (isThreeComponentMatrix())
applyThreeComponentMatrix<D, S>(dst, src, count, flags); return applyThreeComponentMatrix<D, S>(dst, src, count, flags);
else }
applyElementListTransform<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 void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{ {
Q_ASSERT(colorSpaceOut->isThreeComponentMatrix()); Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
updateLutsOut();
if (!colorSpaceIn->isThreeComponentMatrix()) { if (!colorSpaceIn->isThreeComponentMatrix()) {
QUninitialized<QColorVector, WorkBlockSize> buffer; QUninitialized<QColorVector, WorkBlockSize> buffer;
qsizetype i = 0; qsizetype i = 0;
while (i < count) { while (i < count) {
const qsizetype len = qMin(count - i, WorkBlockSize); 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 applyConvertIn(src, buffer, len, flags);
for (auto &&element : colorSpaceIn->mAB)
std::visit([&](auto &&elm) { visitElement(elm, buffer, len); }, element); // 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); applyMatrix<DoClamp>(buffer, len, colorMatrix);
storeOpaque(dst + i, buffer, len, this); storeOpaque(dst + i, buffer, len, this);
@ -1746,12 +1823,11 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
} }
return; return;
} }
if constexpr (!std::is_same_v<S, QCmyk32>) {
if (!colorMatrix.isValid()) if (!colorMatrix.isValid())
return; return;
updateLutsIn(); updateLutsIn();
updateLutsOut();
QUninitialized<QColorVector, WorkBlockSize> buffer; QUninitialized<QColorVector, WorkBlockSize> buffer;
@ -1769,6 +1845,9 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
i += len; 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 void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const
{ {
Q_ASSERT(colorSpaceIn->isThreeComponentMatrix()); 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()) { if (!colorSpaceOut->isThreeComponentMatrix()) {
QUninitialized<QColorVector, WorkBlockSize> buffer; QUninitialized<QColorVector, WorkBlockSize> buffer;
@ -1789,6 +1869,15 @@ void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, Tr
applyMatrix<DoClamp>(buffer, len, colorMatrix); 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 // Do element based conversion
for (auto &&element : colorSpaceOut->mBA) for (auto &&element : colorSpaceOut->mBA)
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element); 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()); Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
if constexpr (!std::is_same_v<D, QCmyk32>) {
if (!colorMatrix.isValid()) if (!colorMatrix.isValid())
return; return;
updateLutsIn();
updateLutsOut(); updateLutsOut();
QUninitialized<QColorVector, WorkBlockSize> buffer; 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); storeOpaque(dst + i, buffer, len, this);
i += len; i += len;
} }
} else {
Q_UNREACHABLE();
}
} }
/*! /*!
@ -1853,17 +1945,28 @@ void QColorTransformPrivate::prepare()
// Only allow versions increasing precision // 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, 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::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<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, 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<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<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::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, 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, 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<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, 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, 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; 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> #include <QtGui/qrgbafloat.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QCmyk32;
class QColorTransformPrivate : public QSharedData class QColorTransformPrivate : public QSharedData
{ {

View File

@ -297,12 +297,13 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
return false; return false;
} }
if (header.inputColorSpace != uint(ColorSpaceType::Rgb) 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)); qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace));
return false; return false;
} }
if (header.pcs != uint(Tag::XYZ_) && header.pcs != uint(Tag::Lab_)) { 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; return false;
} }
@ -679,13 +680,23 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
} }
template<typename T> 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) { for (qsizetype index = 0; index < clut->table.size(); ++index) {
QColorVector v(tableData[index * 3 + 0] * f, QColorVector v(tableData[index * 3 + 0] * f,
tableData[index * 3 + 1] * f, tableData[index * 3 + 1] * f,
tableData[index * 3 + 2] * f); tableData[index * 3 + 2] * f);
clut->table[index] = v; 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)); Q_ASSERT(lut.type == quint32(Tag::mft2));
inputTableEntries = lut.inputTableEntries; inputTableEntries = lut.inputTableEntries;
outputTableEntries = lut.outputTableEntries; outputTableEntries = lut.outputTableEntries;
if (inputTableEntries < 2 || inputTableEntries > 4096)
return false;
if (outputTableEntries < 2 || outputTableEntries > 4096)
return false;
precision = 2; precision = 2;
} }
@ -746,12 +761,12 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false; 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; qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
return false; 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; qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
return false; return false;
} }
@ -782,15 +797,18 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
clutElement.table.resize(clutTableSize); clutElement.table.resize(clutTableSize);
clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints; clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
if (lut.inputChannels == 4)
clutElement.gridPointsW = lut.clutGridPoints;
if constexpr (std::is_same_v<T, Lut8TagData>) { 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 { } else {
float f = 1.0f / 65535.f; float f = 1.0f / 65535.f;
if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
f = 1.0f / 65280.f; f = 1.0f / 65280.f;
QList<S> clutTable(clutTableSize * lut.outputChannels); QList<S> clutTable(clutTableSize * lut.outputChannels);
qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data()); qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
parseCLUT(clutTable.constData(), f, &clutElement); parseCLUT(clutTable.constData(), f, &clutElement, lut.outputChannels);
} }
tableData += clutTableSize * lut.outputChannels * precision; tableData += clutTableSize * lut.outputChannels * precision;
@ -846,12 +864,12 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false; 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; qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
return false; 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; qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
return false; return false;
} }
@ -901,7 +919,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true; bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
// B Curves // 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"; qCWarning(lcIcc) << "Invalid B curves";
return false; return false;
} else { } else {
@ -910,7 +928,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
// A Curves // A Curves
if (mab.aCurvesOffset) { 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"; qCWarning(lcIcc) << "Invalid A curves";
return false; return false;
} else { } else {
@ -951,9 +969,10 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
// CLUT // CLUT
if (mab.clutOffset) { if (mab.clutOffset) {
clutElement.gridPointsX = data[tagEntry.offset + mab.clutOffset]; clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab.clutOffset]);
clutElement.gridPointsY = data[tagEntry.offset + mab.clutOffset + 1]; clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab.clutOffset + 1]);
clutElement.gridPointsZ = data[tagEntry.offset + mab.clutOffset + 2]; 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]; const uchar precision = data[tagEntry.offset + mab.clutOffset + 16];
if (precision > 2 || precision < 1) { if (precision > 2 || precision < 1) {
qCWarning(lcIcc) << "Invalid mAB/mBA element CLUT precision"; 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"; qCWarning(lcIcc) << "Empty CLUT";
return false; 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) { if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) {
qCWarning(lcIcc) << "CLUT oversized for tag"; qCWarning(lcIcc) << "CLUT oversized for tag";
return false; return false;
@ -973,10 +992,10 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
if (precision == 2) { if (precision == 2) {
QList<uint16_t> clutTable(clutTableSize * mab.outputChannels); QList<uint16_t> clutTable(clutTableSize * mab.outputChannels);
qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data()); 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 { } else {
const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20); 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()) if (!clutElement.isEmpty())
colorSpacePrivate->mAB.append(std::move(clutElement)); colorSpacePrivate->mAB.append(std::move(clutElement));
} }
if (mab.mCurvesOffset) { if (mab.mCurvesOffset && mab.outputChannels == 3) {
if (!mCurvesAreLinear) if (!mCurvesAreLinear)
colorSpacePrivate->mAB.append(std::move(mTableElement)); colorSpacePrivate->mAB.append(std::move(mTableElement));
if (!matrixElement.isIdentity()) if (!matrixElement.isIdentity())
@ -1000,7 +1019,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
} else { } else {
if (!bCurvesAreLinear) if (!bCurvesAreLinear)
colorSpacePrivate->mBA.append(std::move(bTableElement)); colorSpacePrivate->mBA.append(std::move(bTableElement));
if (mab.mCurvesOffset) { if (mab.mCurvesOffset && mab.inputChannels == 3) {
if (!matrixElement.isIdentity()) if (!matrixElement.isIdentity())
colorSpacePrivate->mBA.append(std::move(matrixElement)); colorSpacePrivate->mBA.append(std::move(matrixElement));
if (!offsetElement.isNull()) if (!offsetElement.isNull())
@ -1285,11 +1304,11 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
// Check the profile is three-component matrix based: // Check the profile is three-component matrix based:
if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) || 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::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
!tagIndex.contains(Tag::wtpt)) { !tagIndex.contains(Tag::wtpt) || header.pcs == uint(Tag::Lab_)) {
threeComponentMatrix = false; threeComponentMatrix = false;
// Check if the profile is valid n-LUT based: // Check if the profile is valid n-LUT based:
if (!tagIndex.contains(Tag::A2B0)) { 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; return false;
} }
} }
@ -1298,6 +1317,12 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based"; qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
return false; 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 { } else {
Q_UNREACHABLE(); Q_UNREACHABLE();
} }
@ -1338,6 +1363,9 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
} else { } else {
colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_)); colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing; colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
else
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb; colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
// Only parse the default perceptual transform for now // Only parse the default perceptual transform for now

View File

@ -171,6 +171,8 @@ private slots:
void colorSpaceRgbConversion_data(); void colorSpaceRgbConversion_data();
void colorSpaceRgbConversion(); void colorSpaceRgbConversion();
void colorSpaceCmykConversion_data();
void colorSpaceCmykConversion();
void deepCopyWhenPaintingActive(); void deepCopyWhenPaintingActive();
void scaled_QTBUG19157(); 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() void tst_QImage::deepCopyWhenPaintingActive()
{ {
QImage image(64, 64, QImage::Format_ARGB32_Premultiplied); 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<QString>("testProfile");
QTest::addColumn<QColorSpace::NamedColorSpace>("namedColorSpace"); QTest::addColumn<QColorSpace::NamedColorSpace>("namedColorSpace");
QTest::addColumn<QColorSpace::TransferFunction>("transferFunction"); QTest::addColumn<QColorSpace::TransferFunction>("transferFunction");
QTest::addColumn<QColorSpace::TransformModel>("transformModel");
QTest::addColumn<QColorSpace::ColorModel>("colorModel");
QTest::addColumn<QString>("description"); QTest::addColumn<QString>("description");
QString prefix = QFINDTESTDATA("resources/"); QString prefix = QFINDTESTDATA("resources/");
// Read the official sRGB ICCv2 profile: // Read the official sRGB ICCv2 profile:
QTest::newRow("sRGB2014 (ICCv2)") << prefix + "sRGB2014.icc" << QColorSpace::SRgb 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: // My monitor's profile:
QTest::newRow("HP ZR30w (ICCv4)") << prefix + "HP_ZR30w.icc" << QColorSpace::NamedColorSpace(0) 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 // A profile to HD TV
QTest::newRow("VideoHD") << prefix + "VideoHD.icc" << QColorSpace::NamedColorSpace(0) 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 // sRGB on PCSLab format
QTest::newRow("sRGB ICCv4 Appearance") << prefix + "sRGB_ICC_v4_Appearance.icc" << QColorSpace::NamedColorSpace(0) 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() void tst_QColorSpace::fromIccProfile()
@ -210,6 +230,8 @@ void tst_QColorSpace::fromIccProfile()
QFETCH(QString, testProfile); QFETCH(QString, testProfile);
QFETCH(QColorSpace::NamedColorSpace, namedColorSpace); QFETCH(QColorSpace::NamedColorSpace, namedColorSpace);
QFETCH(QColorSpace::TransferFunction, transferFunction); QFETCH(QColorSpace::TransferFunction, transferFunction);
QFETCH(QColorSpace::TransformModel, transformModel);
QFETCH(QColorSpace::ColorModel, colorModel);
QFETCH(QString, description); QFETCH(QString, description);
QFile file(testProfile); QFile file(testProfile);
@ -222,6 +244,8 @@ void tst_QColorSpace::fromIccProfile()
QCOMPARE(fileColorSpace, namedColorSpace); QCOMPARE(fileColorSpace, namedColorSpace);
QCOMPARE(fileColorSpace.transferFunction(), transferFunction); QCOMPARE(fileColorSpace.transferFunction(), transferFunction);
QCOMPARE(fileColorSpace.transformModel(), transformModel);
QCOMPARE(fileColorSpace.colorModel(), colorModel);
QCOMPARE(fileColorSpace.description(), description); QCOMPARE(fileColorSpace.description(), description);
QByteArray iccProfile2 = fileColorSpace.iccProfile(); QByteArray iccProfile2 = fileColorSpace.iccProfile();

View File

@ -4,9 +4,25 @@
#include <cstdlib> #include <cstdlib>
#include <QGuiApplication> #include <QGuiApplication>
#include <QColor>
#include <QColorSpace> #include <QColorSpace>
#include <QImage> #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) { extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
// to reduce noise and increase speed // to reduce noise and increase speed
static char quiet[] = "QT_LOGGING_RULES=qt.gui.icc=false"; 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"); cs2.setDescription("Hello");
bool b = (cs == cs2); bool b = (cs == cs2);
Q_UNUSED(b); Q_UNUSED(b);
QRgb color = 0xfaf8fa00; QColor color(0xfaf8fa00);
color = trans1.map(color); 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); img.setColorSpace(cs);
QImage img2 = img.convertedToColorSpace(QColorSpace::SRgb); QImage img2 = img.convertedToColorSpace(QColorSpace::SRgb);
if (cs.isValidTarget()) { if (cs.isValidTarget()) {

View File

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