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:
parent
e794894ece
commit
25c96d547b
@ -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,19 +5518,38 @@ 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::Rgb && outColorData == QColorSpace::ColorModel::Gray) {
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Gray) {
|
||||
// RGB -> Gray
|
||||
if (tmpFormat == QImage::Format_Grayscale8) {
|
||||
fromImage.convertTo(QImage::Format_RGB32);
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
for (int i = 0; i < 4; ++i)
|
||||
interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z);
|
||||
// interpolate over y
|
||||
for (int i = 0; i < 2; ++i)
|
||||
interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y);
|
||||
// interpolate over x
|
||||
interpolateIn(tmp[0], tmp[4], frac.x);
|
||||
return tmp[0];
|
||||
}
|
||||
auto index = [&](qsizetype x, qsizetype y, qsizetype z) -> qsizetype {
|
||||
return x * gridPointsZ * gridPointsY
|
||||
+ y * gridPointsZ
|
||||
+ z;
|
||||
};
|
||||
QColorVector tmp[8] = {
|
||||
table[index(lox, loy, loz)],
|
||||
table[index(lox, loy, hiz)],
|
||||
table[index(lox, hiy, loz)],
|
||||
table[index(lox, hiy, hiz)],
|
||||
table[index(hix, loy, loz)],
|
||||
table[index(hix, loy, hiz)],
|
||||
table[index(hix, hiy, loz)],
|
||||
table[index(hix, hiy, hiz)]
|
||||
};
|
||||
// interpolate over z
|
||||
tmp[0] = interpolate(table[index(lox, loy, loz)],
|
||||
table[index(lox, loy, hiz)], fracz);
|
||||
tmp[1] = interpolate(table[index(lox, hiy, loz)],
|
||||
table[index(lox, hiy, hiz)], fracz);
|
||||
tmp[2] = interpolate(table[index(hix, loy, loz)],
|
||||
table[index(hix, loy, hiz)], fracz);
|
||||
tmp[3] = interpolate(table[index(hix, hiy, loz)],
|
||||
table[index(hix, hiy, hiz)], fracz);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z);
|
||||
// interpolate over y
|
||||
interpolateIn(tmp[0], tmp[1], fracy);
|
||||
interpolateIn(tmp[2], tmp[3], fracy);
|
||||
for (int i = 0; i < 2; ++i)
|
||||
interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y);
|
||||
// interpolate over x
|
||||
interpolateIn(tmp[0], tmp[2], fracx);
|
||||
interpolateIn(tmp[0], tmp[4], frac.x);
|
||||
return tmp[0];
|
||||
}
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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())
|
||||
|
@ -54,6 +54,7 @@ public:
|
||||
Undefined = 0,
|
||||
Rgb = 1,
|
||||
Gray = 2,
|
||||
Cmyk = 3,
|
||||
};
|
||||
Q_ENUM(ColorModel)
|
||||
|
||||
|
@ -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;
|
||||
|
@ -45,7 +45,7 @@ public:
|
||||
Q_ASSERT(qsizetype(size) <= table.size());
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
bool isEmpty() const noexcept
|
||||
{
|
||||
return m_tableSize == 0;
|
||||
}
|
||||
|
@ -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)
|
||||
clr = clr.toRgb();
|
||||
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;
|
||||
out.setRgbF(c.x, c.y, c.z, color.alphaF());
|
||||
if (d->colorSpaceOut->colorModel == QColorSpace::ColorModel::Cmyk) {
|
||||
c.x = std::clamp(c.x, 0.f, 1.f);
|
||||
c.y = std::clamp(c.y, 0.f, 1.f);
|
||||
c.z = std::clamp(c.z, 0.f, 1.f);
|
||||
c.w = std::clamp(c.w, 0.f, 1.f);
|
||||
out.setCmykF(c.x, c.y, c.z, c.w, color.alphaF());
|
||||
} else {
|
||||
out.setRgbF(c.x, c.y, c.z, color.alphaF());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -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,9 +976,8 @@ static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len,
|
||||
}
|
||||
|
||||
template<>
|
||||
void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst,
|
||||
const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
|
||||
const __m128 vZero = _mm_set1_ps(0.0f);
|
||||
@ -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,15 +1631,20 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const
|
||||
template<typename S>
|
||||
void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
|
||||
{
|
||||
if (colorSpaceIn->isThreeComponentMatrix()) {
|
||||
if (flags & InputPremultiplied)
|
||||
loadPremultiplied(buffer, src, len, this);
|
||||
else
|
||||
loadUnpremultiplied(buffer, src, len, this);
|
||||
// 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);
|
||||
else
|
||||
loadUnpremultiplied(buffer, src, len, this);
|
||||
|
||||
if (!colorSpaceOut->isThreeComponentMatrix())
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the first half only.
|
||||
} else {
|
||||
if (!colorSpaceOut->isThreeComponentMatrix())
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the first half only.
|
||||
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;
|
||||
if (colorSpaceOut->isThreeComponentMatrix()) {
|
||||
applyMatrix<doClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
|
||||
// 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 (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 {
|
||||
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);
|
||||
}
|
||||
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,10 +1781,11 @@ 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 (isThreeComponentMatrix())
|
||||
applyThreeComponentMatrix<D, S>(dst, src, count, flags);
|
||||
else
|
||||
applyElementListTransform<D, S>(dst, src, count, flags);
|
||||
if constexpr (!std::is_same_v<D, QCmyk32> && !std::is_same_v<S, QCmyk32>) {
|
||||
if (isThreeComponentMatrix())
|
||||
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,28 +1823,30 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
|
||||
}
|
||||
return;
|
||||
}
|
||||
if constexpr (!std::is_same_v<S, QCmyk32>) {
|
||||
if (!colorMatrix.isValid())
|
||||
return;
|
||||
|
||||
if (!colorMatrix.isValid())
|
||||
return;
|
||||
updateLutsIn();
|
||||
|
||||
updateLutsIn();
|
||||
updateLutsOut();
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
qsizetype i = 0;
|
||||
while (i < count) {
|
||||
const qsizetype len = qMin(count - i, WorkBlockSize);
|
||||
if (flags & InputPremultiplied)
|
||||
loadPremultiplied(buffer, src + i, len, this);
|
||||
else
|
||||
loadUnpremultiplied(buffer, src + i, len, this);
|
||||
|
||||
qsizetype i = 0;
|
||||
while (i < count) {
|
||||
const qsizetype len = qMin(count - i, WorkBlockSize);
|
||||
if (flags & InputPremultiplied)
|
||||
loadPremultiplied(buffer, src + i, len, this);
|
||||
else
|
||||
loadUnpremultiplied(buffer, src + i, len, this);
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
storeOpaque(dst + i, buffer, len, this);
|
||||
|
||||
storeOpaque(dst + i, buffer, len, this);
|
||||
|
||||
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
|
||||
{
|
||||
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,23 +1892,26 @@ void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, Tr
|
||||
}
|
||||
}
|
||||
Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
|
||||
if (!colorMatrix.isValid())
|
||||
return;
|
||||
if constexpr (!std::is_same_v<D, QCmyk32>) {
|
||||
if (!colorMatrix.isValid())
|
||||
return;
|
||||
|
||||
updateLutsIn();
|
||||
updateLutsOut();
|
||||
updateLutsOut();
|
||||
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
|
||||
qsizetype i = 0;
|
||||
while (i < count) {
|
||||
const qsizetype len = qMin(count - i, WorkBlockSize);
|
||||
loadGray(buffer, src + i, len, this);
|
||||
qsizetype i = 0;
|
||||
while (i < count) {
|
||||
const qsizetype len = qMin(count - i, WorkBlockSize);
|
||||
loadGray(buffer, src + i, len, this);
|
||||
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
|
||||
storeOpaque(dst + i, buffer, len, this);
|
||||
i += len;
|
||||
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;
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <QtGui/qrgbafloat.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QCmyk32;
|
||||
|
||||
class QColorTransformPrivate : public QSharedData
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
for (qsizetype index = 0; index < clut->table.size(); ++index) {
|
||||
QColorVector v(tableData[index * 3 + 0] * f,
|
||||
tableData[index * 3 + 1] * f,
|
||||
tableData[index * 3 + 2] * f);
|
||||
clut->table[index] = v;
|
||||
if (outputChannels == 4) {
|
||||
for (qsizetype index = 0; index < clut->table.size(); ++index) {
|
||||
QColorVector v(tableData[index * 4 + 0] * f,
|
||||
tableData[index * 4 + 1] * f,
|
||||
tableData[index * 4 + 2] * f,
|
||||
tableData[index * 4 + 3] * f);
|
||||
clut->table[index] = v;
|
||||
};
|
||||
} else {
|
||||
for (qsizetype index = 0; index < clut->table.size(); ++index) {
|
||||
QColorVector v(tableData[index * 3 + 0] * f,
|
||||
tableData[index * 3 + 1] * f,
|
||||
tableData[index * 3 + 2] * f);
|
||||
clut->table[index] = v;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,7 +1363,10 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
|
||||
} else {
|
||||
colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
|
||||
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
|
||||
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
|
||||
if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
|
||||
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
|
||||
else
|
||||
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
|
||||
|
||||
// Only parse the default perceptual transform for now
|
||||
if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true))
|
||||
|
BIN
tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc
Normal file
BIN
tests/auto/gui/image/qimage/images/CGATS001Compat-v2-micro.icc
Normal file
Binary file not shown.
@ -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);
|
||||
|
Binary file not shown.
BIN
tests/auto/gui/painting/qcolorspace/resources/sGrey-v4.icc
Normal file
BIN
tests/auto/gui/painting/qcolorspace/resources/sGrey-v4.icc
Normal file
Binary file not shown.
@ -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();
|
||||
|
@ -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()) {
|
||||
|
@ -79,10 +79,11 @@ bool ImageViewer::loadFile(const QString &fileName)
|
||||
|
||||
void ImageViewer::setImage(const QImage &newImage)
|
||||
{
|
||||
image = newImage;
|
||||
if (image.colorSpace().isValid())
|
||||
image.convertToColorSpace(QColorSpace::SRgb);
|
||||
imageLabel->setPixmap(QPixmap::fromImage(image));
|
||||
if (newImage.colorSpace().isValid())
|
||||
image = newImage.convertedToColorSpace(QColorSpace::SRgb);
|
||||
else
|
||||
image = newImage;
|
||||
imageLabel->setPixmap(QPixmap::fromImage(image, Qt::NoFormatConversion));
|
||||
//! [4]
|
||||
scaleFactor = 1.0;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user