Add color space model, making gray color spaces explicit
This also adds image conversion of both format and color space, which will also be required later for conversions to CMYK formats and color spaces. Change-Id: I578c0a010ffcdb4df4cf9080c0621fac8bc342bf Reviewed-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
This commit is contained in:
parent
a786404036
commit
05b8467304
@ -5000,6 +5000,9 @@ void QImage::setColorSpace(const QColorSpace &colorSpace)
|
||||
return;
|
||||
if (d->colorSpace == colorSpace)
|
||||
return;
|
||||
if (colorSpace.isValid() && !qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel()))
|
||||
return;
|
||||
|
||||
detachMetadata(false);
|
||||
if (d)
|
||||
d->colorSpace = colorSpace;
|
||||
@ -5012,13 +5015,14 @@ void QImage::setColorSpace(const QColorSpace &colorSpace)
|
||||
|
||||
If the image has no valid color space, the method does nothing.
|
||||
|
||||
\note If \a colorSpace is not compatible with the current format, the image
|
||||
will be converted to one that is.
|
||||
|
||||
\sa convertedToColorSpace(), setColorSpace()
|
||||
*/
|
||||
void QImage::convertToColorSpace(const QColorSpace &colorSpace)
|
||||
{
|
||||
if (!d)
|
||||
return;
|
||||
if (!d->colorSpace.isValid())
|
||||
if (!d || !d->colorSpace.isValid())
|
||||
return;
|
||||
if (!colorSpace.isValidTarget()) {
|
||||
qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
|
||||
@ -5026,10 +5030,46 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace)
|
||||
}
|
||||
if (d->colorSpace == colorSpace)
|
||||
return;
|
||||
if (!qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) {
|
||||
*this = convertedToColorSpace(colorSpace);
|
||||
return;
|
||||
}
|
||||
applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace));
|
||||
d->colorSpace = colorSpace;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.8
|
||||
|
||||
Converts the image to \a colorSpace and \a format.
|
||||
|
||||
If the image has no valid color space, the method does nothing,
|
||||
nor if the color space is not compatible with with the format.
|
||||
|
||||
The specified image conversion \a flags control how the image data
|
||||
is handled during the format conversion process.
|
||||
|
||||
\sa convertedToColorSpace(), setColorSpace()
|
||||
*/
|
||||
void QImage::convertToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags)
|
||||
{
|
||||
if (!d || !d->colorSpace.isValid())
|
||||
return;
|
||||
if (!colorSpace.isValidTarget()) {
|
||||
qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
|
||||
return;
|
||||
}
|
||||
if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
|
||||
qWarning() << "QImage::convertToColorSpace: Color space is not compatible with format";
|
||||
return;
|
||||
}
|
||||
|
||||
if (d->colorSpace == colorSpace)
|
||||
return convertTo(format, flags);
|
||||
applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace), format, flags);
|
||||
d->colorSpace = colorSpace;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.14
|
||||
|
||||
@ -5037,13 +5077,16 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace)
|
||||
|
||||
If the image has no valid color space, a null QImage is returned.
|
||||
|
||||
\sa convertToColorSpace()
|
||||
\note If \a colorSpace is not compatible with the current format,
|
||||
the returned image will also be converted to a format this is.
|
||||
For more control over returned image format, see the three argument
|
||||
overload of this method.
|
||||
|
||||
\sa convertToColorSpace(), colorTransformed()
|
||||
*/
|
||||
QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const
|
||||
{
|
||||
if (!d)
|
||||
return QImage();
|
||||
if (!d->colorSpace.isValid())
|
||||
if (!d || !d->colorSpace.isValid())
|
||||
return QImage();
|
||||
if (!colorSpace.isValidTarget()) {
|
||||
qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid";
|
||||
@ -5051,8 +5094,39 @@ QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const
|
||||
}
|
||||
if (d->colorSpace == colorSpace)
|
||||
return *this;
|
||||
QImage image = copy();
|
||||
image.convertToColorSpace(colorSpace);
|
||||
QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace));
|
||||
image.setColorSpace(colorSpace);
|
||||
return image;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.8
|
||||
|
||||
Returns the image converted to \a colorSpace and \a format.
|
||||
|
||||
If the image has no valid color space, a null QImage is returned.
|
||||
|
||||
The specified image conversion \a flags control how the image data
|
||||
is handled during the format conversion process.
|
||||
|
||||
\sa colorTransformed()
|
||||
*/
|
||||
QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags) const
|
||||
{
|
||||
if (!d || !d->colorSpace.isValid())
|
||||
return QImage();
|
||||
if (!colorSpace.isValidTarget()) {
|
||||
qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid";
|
||||
return QImage();
|
||||
}
|
||||
if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
|
||||
qWarning() << "QImage::convertedToColorSpace: Color space is not compatible with format";
|
||||
return QImage();
|
||||
}
|
||||
if (d->colorSpace == colorSpace)
|
||||
return convertedTo(format, flags);
|
||||
QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace), format, flags);
|
||||
image.setColorSpace(colorSpace);
|
||||
return image;
|
||||
}
|
||||
|
||||
@ -5095,7 +5169,8 @@ void QImage::applyColorTransform(const QColorTransform &transform)
|
||||
&& oldFormat != QImage::Format_RGBA64_Premultiplied)
|
||||
convertTo(QImage::Format_RGBA64);
|
||||
} else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32
|
||||
&& oldFormat != QImage::Format_ARGB32_Premultiplied) {
|
||||
&& oldFormat != QImage::Format_ARGB32_Premultiplied
|
||||
&& oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
|
||||
if (hasAlphaChannel())
|
||||
convertTo(QImage::Format_ARGB32);
|
||||
else
|
||||
@ -5109,6 +5184,8 @@ void QImage::applyColorTransform(const QColorTransform &transform)
|
||||
case Format_RGBA32FPx4_Premultiplied:
|
||||
flags = QColorTransformPrivate::Premultiplied;
|
||||
break;
|
||||
case Format_Grayscale8:
|
||||
case Format_Grayscale16:
|
||||
case Format_RGB32:
|
||||
case Format_RGBX64:
|
||||
case Format_RGBX32FPx4:
|
||||
@ -5124,7 +5201,21 @@ void QImage::applyColorTransform(const QColorTransform &transform)
|
||||
|
||||
std::function<void(int,int)> transformSegment;
|
||||
|
||||
if (qt_fpColorPrecision(format())) {
|
||||
if (format() == Format_Grayscale8) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
uint8_t *scanline = reinterpret_cast<uint8_t *>(d->data + y * d->bytes_per_line);
|
||||
QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
|
||||
}
|
||||
};
|
||||
} else if (format() == Format_Grayscale16) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
uint16_t *scanline = reinterpret_cast<uint16_t *>(d->data + y * d->bytes_per_line);
|
||||
QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
|
||||
}
|
||||
};
|
||||
} else if (qt_fpColorPrecision(format())) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
QRgbaFloat32 *scanline = reinterpret_cast<QRgbaFloat32 *>(d->data + y * d->bytes_per_line);
|
||||
@ -5171,24 +5262,389 @@ void QImage::applyColorTransform(const QColorTransform &transform)
|
||||
*this = std::move(*this).convertToFormat(oldFormat);
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.8
|
||||
|
||||
Applies the color transformation \a transform to all pixels in the image, and converts the format of the image to \a toFormat.
|
||||
|
||||
The specified image conversion \a flags control how the image data
|
||||
is handled during the format conversion process.
|
||||
*/
|
||||
void QImage::applyColorTransform(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags)
|
||||
{
|
||||
if (!d)
|
||||
return;
|
||||
if (transform.isIdentity())
|
||||
return convertTo(toFormat, flags);
|
||||
|
||||
*this = colorTransformed(transform, toFormat, flags);
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.4
|
||||
|
||||
Returns the image color transformed using \a transform on all pixels in the image.
|
||||
|
||||
\note If \a transform has a source color space which is incompatible with the format of this image,
|
||||
returns a null QImage. If \a transform has a target color space which is incompatible with the format
|
||||
of this image, the image will also be converted to a compatible format. For more control about the
|
||||
choice of the target pixel format, see the three argument overload of this method.
|
||||
|
||||
\sa applyColorTransform()
|
||||
*/
|
||||
QImage QImage::colorTransformed(const QColorTransform &transform) const &
|
||||
{
|
||||
if (!d || !d->colorSpace.isValid())
|
||||
if (!d)
|
||||
return QImage();
|
||||
if (transform.isIdentity())
|
||||
return *this;
|
||||
|
||||
QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
|
||||
QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
|
||||
if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
|
||||
qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
|
||||
return QImage();
|
||||
}
|
||||
if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
|
||||
// All model switching transforms are opaque in at least one end.
|
||||
switch (outColorModel) {
|
||||
case QColorSpace::ColorModel::Rgb:
|
||||
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
|
||||
case QColorSpace::ColorModel::Gray:
|
||||
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
|
||||
case QColorSpace::ColorModel::Undefined:
|
||||
break;
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
QImage image = copy();
|
||||
image.applyColorTransform(transform);
|
||||
return image;
|
||||
}
|
||||
|
||||
static bool isRgb32Data(QImage::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case QImage::Format_RGB32:
|
||||
case QImage::Format_ARGB32:
|
||||
case QImage::Format_ARGB32_Premultiplied:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isRgb64Data(QImage::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case QImage::Format_RGBX64:
|
||||
case QImage::Format_RGBA64:
|
||||
case QImage::Format_RGBA64_Premultiplied:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isRgb32fpx4Data(QImage::Format f)
|
||||
{
|
||||
switch (f) {
|
||||
case QImage::Format_RGBX32FPx4:
|
||||
case QImage::Format_RGBA32FPx4:
|
||||
case QImage::Format_RGBA32FPx4_Premultiplied:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.8
|
||||
|
||||
Returns the image color transformed using \a transform on all pixels in the image, returning an image of format \a toFormat.
|
||||
|
||||
The specified image conversion \a flags control how the image data
|
||||
is handled during the format conversion process.
|
||||
|
||||
\note If \a transform has a source color space which is incompatible with the format of this image,
|
||||
or a target color space that is incompatible with \a toFormat, returns a null QImage.
|
||||
|
||||
\sa applyColorTransform()
|
||||
*/
|
||||
QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags) const &
|
||||
{
|
||||
if (!d)
|
||||
return QImage();
|
||||
if (toFormat == QImage::Format_Invalid)
|
||||
toFormat = format();
|
||||
if (transform.isIdentity())
|
||||
return convertedTo(toFormat, flags);
|
||||
|
||||
QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
|
||||
QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
|
||||
if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
|
||||
qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
|
||||
return QImage();
|
||||
}
|
||||
if (!qt_compatibleColorModel(toPixelFormat(toFormat).colorModel(), outColorModel)) {
|
||||
qWarning() << "QImage::colorTransformed: Invalid output color space for transform";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
QImage fromImage = *this;
|
||||
|
||||
QImage::Format tmpFormat = toFormat;
|
||||
switch (toFormat) {
|
||||
case QImage::Format_RGB32:
|
||||
case QImage::Format_ARGB32:
|
||||
case QImage::Format_ARGB32_Premultiplied:
|
||||
case QImage::Format_RGBX32FPx4:
|
||||
case QImage::Format_RGBA32FPx4:
|
||||
case QImage::Format_RGBA32FPx4_Premultiplied:
|
||||
case QImage::Format_RGBX64:
|
||||
case QImage::Format_RGBA64:
|
||||
case QImage::Format_RGBA64_Premultiplied:
|
||||
case QImage::Format_Grayscale8:
|
||||
case QImage::Format_Grayscale16:
|
||||
// can be output natively
|
||||
break;
|
||||
case QImage::Format_RGB16:
|
||||
case QImage::Format_RGB444:
|
||||
case QImage::Format_RGB555:
|
||||
case QImage::Format_RGB666:
|
||||
case QImage::Format_RGB888:
|
||||
case QImage::Format_BGR888:
|
||||
case QImage::Format_RGBX8888:
|
||||
case QImage::Format_CMYK8888:
|
||||
tmpFormat = QImage::Format_RGB32;
|
||||
break;
|
||||
case QImage::Format_Mono:
|
||||
case QImage::Format_MonoLSB:
|
||||
case QImage::Format_Indexed8:
|
||||
case QImage::Format_ARGB8565_Premultiplied:
|
||||
case QImage::Format_ARGB6666_Premultiplied:
|
||||
case QImage::Format_ARGB8555_Premultiplied:
|
||||
case QImage::Format_ARGB4444_Premultiplied:
|
||||
case QImage::Format_RGBA8888:
|
||||
case QImage::Format_RGBA8888_Premultiplied:
|
||||
tmpFormat = QImage::Format_ARGB32;
|
||||
break;
|
||||
case QImage::Format_BGR30:
|
||||
case QImage::Format_RGB30:
|
||||
tmpFormat = QImage::Format_RGBX64;
|
||||
break;
|
||||
case QImage::Format_A2BGR30_Premultiplied:
|
||||
case QImage::Format_A2RGB30_Premultiplied:
|
||||
tmpFormat = QImage::Format_RGBA64;
|
||||
break;
|
||||
case QImage::Format_RGBX16FPx4:
|
||||
case QImage::Format_RGBA16FPx4:
|
||||
case QImage::Format_RGBA16FPx4_Premultiplied:
|
||||
tmpFormat = QImage::Format_RGBA32FPx4;
|
||||
break;
|
||||
case QImage::Format_Alpha8:
|
||||
return convertedTo(QImage::Format_Alpha8);
|
||||
case QImage::Format_Invalid:
|
||||
case QImage::NImageFormats:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
QColorSpace::ColorModel inColorData = qt_csColorData(pixelFormat().colorModel());
|
||||
QColorSpace::ColorModel outColorData = qt_csColorData(toPixelFormat(toFormat).colorModel());
|
||||
// Ensure only precision increasing transforms
|
||||
if (inColorData != outColorData) {
|
||||
if (fromImage.format() == QImage::Format_Grayscale8 && outColorData == QColorSpace::ColorModel::Rgb)
|
||||
tmpFormat = QImage::Format_RGB32;
|
||||
else if (tmpFormat == QImage::Format_Grayscale8 && qt_highColorPrecision(fromImage.format()))
|
||||
tmpFormat = QImage::Format_Grayscale16;
|
||||
else if (fromImage.format() == QImage::Format_Grayscale16 && outColorData == QColorSpace::ColorModel::Rgb)
|
||||
tmpFormat = QImage::Format_RGBX64;
|
||||
} else {
|
||||
if (tmpFormat == QImage::Format_Grayscale8 && fromImage.format() == QImage::Format_Grayscale16)
|
||||
tmpFormat = QImage::Format_Grayscale16;
|
||||
else if (qt_fpColorPrecision(fromImage.format()) && !qt_fpColorPrecision(tmpFormat))
|
||||
tmpFormat = QImage::Format_RGBA32FPx4;
|
||||
else if (isRgb32Data(tmpFormat) && qt_highColorPrecision(fromImage.format(), true))
|
||||
tmpFormat = QImage::Format_RGBA64;
|
||||
}
|
||||
|
||||
QImage toImage(size(), tmpFormat);
|
||||
copyMetadata(&toImage, *this);
|
||||
|
||||
std::function<void(int, int)> transformSegment;
|
||||
QColorTransformPrivate::TransformFlags transFlags = QColorTransformPrivate::Unpremultiplied;
|
||||
|
||||
if (inColorData != outColorData) {
|
||||
// Needs color model switching transform
|
||||
if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Rgb) {
|
||||
// Gray -> RGB
|
||||
if (format() == QImage::Format_Grayscale8) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line);
|
||||
QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line);
|
||||
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Gray) {
|
||||
// RGB -> Gray
|
||||
if (tmpFormat == QImage::Format_Grayscale8) {
|
||||
fromImage.convertTo(QImage::Format_RGB32);
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
fromImage.convertTo(QImage::Format_RGBX64);
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
} else {
|
||||
// Conversion on same color model
|
||||
if (pixelFormat().colorModel() == QPixelFormat::Indexed) {
|
||||
for (int i = 0; i < d->colortable.size(); ++i)
|
||||
fromImage.d->colortable[i] = transform.map(d->colortable[i]);
|
||||
return fromImage.convertedTo(toFormat, flags);
|
||||
}
|
||||
|
||||
QImage::Format oldFormat = format();
|
||||
if (qt_fpColorPrecision(oldFormat)) {
|
||||
if (oldFormat != QImage::Format_RGBX32FPx4 && oldFormat != QImage::Format_RGBA32FPx4
|
||||
&& oldFormat != QImage::Format_RGBA32FPx4_Premultiplied)
|
||||
fromImage.convertTo(QImage::Format_RGBA32FPx4);
|
||||
} else if (qt_highColorPrecision(oldFormat, true)) {
|
||||
if (oldFormat != QImage::Format_RGBX64 && oldFormat != QImage::Format_RGBA64
|
||||
&& oldFormat != QImage::Format_RGBA64_Premultiplied && oldFormat != QImage::Format_Grayscale16)
|
||||
fromImage.convertTo(QImage::Format_RGBA64);
|
||||
} else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32
|
||||
&& oldFormat != QImage::Format_ARGB32_Premultiplied
|
||||
&& oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
|
||||
if (hasAlphaChannel())
|
||||
fromImage.convertTo(QImage::Format_ARGB32);
|
||||
else
|
||||
fromImage.convertTo(QImage::Format_RGB32);
|
||||
}
|
||||
|
||||
if (!fromImage.hasAlphaChannel())
|
||||
transFlags = QColorTransformPrivate::InputOpaque;
|
||||
else if (qPixelLayouts[fromImage.format()].premultiplied)
|
||||
transFlags = QColorTransformPrivate::Premultiplied;
|
||||
|
||||
if (fromImage.format() == Format_Grayscale8) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const quint8 *in_scanline = reinterpret_cast<const quint8 *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
if (tmpFormat == Format_Grayscale8) {
|
||||
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
|
||||
} else {
|
||||
Q_ASSERT(tmpFormat == Format_Grayscale16);
|
||||
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if (fromImage.format() == Format_Grayscale16) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
|
||||
}
|
||||
};
|
||||
} else if (isRgb32fpx4Data(fromImage.format())) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
Q_ASSERT(isRgb32fpx4Data(tmpFormat));
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
|
||||
}
|
||||
};
|
||||
} else if (isRgb64Data(fromImage.format())) {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
if (isRgb32fpx4Data(tmpFormat)) {
|
||||
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
|
||||
} else {
|
||||
Q_ASSERT(isRgb64Data(tmpFormat));
|
||||
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
transformSegment = [&](int yStart, int yEnd) {
|
||||
for (int y = yStart; y < yEnd; ++y) {
|
||||
const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.bits() + y * fromImage.bytesPerLine());
|
||||
if (isRgb32fpx4Data(tmpFormat)) {
|
||||
QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
|
||||
} else if (isRgb64Data(tmpFormat)) {
|
||||
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
|
||||
} else {
|
||||
Q_ASSERT(isRgb32Data(tmpFormat));
|
||||
QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.bits() + y * toImage.bytesPerLine());
|
||||
QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
|
||||
int segments = (qsizetype(width()) * height()) >> 16;
|
||||
segments = std::min(segments, height());
|
||||
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
|
||||
if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
|
||||
QSemaphore semaphore;
|
||||
int y = 0;
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
int yn = (height() - y) / (segments - i);
|
||||
threadPool->start([&, y, yn]() {
|
||||
transformSegment(y, y + yn);
|
||||
semaphore.release(1);
|
||||
});
|
||||
y += yn;
|
||||
}
|
||||
semaphore.acquire(segments);
|
||||
} else
|
||||
#endif
|
||||
transformSegment(0, height());
|
||||
|
||||
if (tmpFormat != toFormat)
|
||||
toImage.convertTo(toFormat);
|
||||
|
||||
return toImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.4
|
||||
\overload
|
||||
@ -5199,12 +5655,46 @@ QImage QImage::colorTransformed(const QColorTransform &transform) const &
|
||||
*/
|
||||
QImage QImage::colorTransformed(const QColorTransform &transform) &&
|
||||
{
|
||||
if (!d || !d->colorSpace.isValid())
|
||||
if (!d)
|
||||
return QImage();
|
||||
|
||||
QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
|
||||
QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
|
||||
if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
|
||||
qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
|
||||
return QImage();
|
||||
}
|
||||
if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
|
||||
// There is currently no inplace conversion of both colorspace and format, so just use the normal version.
|
||||
switch (outColorModel) {
|
||||
case QColorSpace::ColorModel::Rgb:
|
||||
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
|
||||
case QColorSpace::ColorModel::Gray:
|
||||
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
|
||||
case QColorSpace::ColorModel::Undefined:
|
||||
break;
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
applyColorTransform(transform);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.8
|
||||
\overload
|
||||
|
||||
Returns the image color transformed using \a transform on all pixels in the image.
|
||||
|
||||
\sa applyColorTransform()
|
||||
*/
|
||||
QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags) &&
|
||||
{
|
||||
// There is currently no inplace conversion of both colorspace and format, so just use the normal version.
|
||||
return colorTransformed(transform, format, flags);
|
||||
}
|
||||
|
||||
bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags)
|
||||
{
|
||||
if (format == newFormat)
|
||||
|
@ -231,13 +231,18 @@ public:
|
||||
void invertPixels(InvertMode = InvertRgb);
|
||||
|
||||
QColorSpace colorSpace() const;
|
||||
[[nodiscard]] QImage convertedToColorSpace(const QColorSpace &) const;
|
||||
void convertToColorSpace(const QColorSpace &);
|
||||
void setColorSpace(const QColorSpace &);
|
||||
[[nodiscard]] QImage convertedToColorSpace(const QColorSpace &colorSpace) const;
|
||||
[[nodiscard]] QImage convertedToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) const;
|
||||
void convertToColorSpace(const QColorSpace &colorSpace);
|
||||
void convertToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor);
|
||||
void setColorSpace(const QColorSpace &colorSpace);
|
||||
|
||||
QImage colorTransformed(const QColorTransform &transform) const &;
|
||||
QImage colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) const &;
|
||||
QImage colorTransformed(const QColorTransform &transform) &&;
|
||||
QImage colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) &&;
|
||||
void applyColorTransform(const QColorTransform &transform);
|
||||
void applyColorTransform(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor);
|
||||
|
||||
bool load(QIODevice *device, const char *format);
|
||||
bool load(const QString &fileName, const char *format = nullptr);
|
||||
|
@ -437,6 +437,43 @@ inline bool qt_fpColorPrecision(QImage::Format format)
|
||||
return false;
|
||||
}
|
||||
|
||||
inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format)
|
||||
{
|
||||
switch (format) {
|
||||
case QPixelFormat::ColorModel::RGB:
|
||||
case QPixelFormat::ColorModel::BGR:
|
||||
case QPixelFormat::ColorModel::Indexed:
|
||||
return QColorSpace::ColorModel::Rgb;
|
||||
case QPixelFormat::ColorModel::Alpha:
|
||||
return QColorSpace::ColorModel::Undefined; // No valid colors
|
||||
case QPixelFormat::ColorModel::Grayscale:
|
||||
return QColorSpace::ColorModel::Gray;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QColorSpace::ColorModel::Undefined;
|
||||
}
|
||||
|
||||
inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs)
|
||||
{
|
||||
QColorSpace::ColorModel dataCs = qt_csColorData(data);
|
||||
|
||||
if (data == QPixelFormat::ColorModel::Alpha)
|
||||
return true; // Alpha data has no colors and can be handled by any color space
|
||||
|
||||
if (cs == QColorSpace::ColorModel::Undefined || dataCs == QColorSpace::ColorModel::Undefined)
|
||||
return false;
|
||||
|
||||
if (dataCs == cs)
|
||||
return true; // Matching color models
|
||||
|
||||
if (cs == QColorSpace::ColorModel::Rgb)
|
||||
// Can apply RGB CS to Gray data
|
||||
return dataCs == QColorSpace::ColorModel::Gray;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline QImage::Format qt_maybeDataCompatibleAlphaVersion(QImage::Format format)
|
||||
{
|
||||
switch (format) {
|
||||
|
@ -64,6 +64,14 @@ public:
|
||||
constexpr QColorVector operator-(const QColorVector &v) const { return QColorVector(x - v.x, y - v.y, z - v.z); }
|
||||
void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; }
|
||||
|
||||
QPointF toChromaticity() const
|
||||
{
|
||||
if (isNull())
|
||||
return QPointF();
|
||||
float mag = 1.0f / (x + y + z);
|
||||
return QPointF(x * mag, y * mag);
|
||||
}
|
||||
|
||||
// Common whitepoints:
|
||||
static constexpr QPointF D50Chromaticity() { return QPointF(0.34567, 0.35850); }
|
||||
static constexpr QPointF D65Chromaticity() { return QPointF(0.31271, 0.32902); }
|
||||
@ -279,6 +287,32 @@ public:
|
||||
{ 0.0f, v.y, 0.0f },
|
||||
{ 0.0f, 0.0f, v.z } };
|
||||
}
|
||||
static QColorMatrix chromaticAdaptation(const QColorVector &whitePoint)
|
||||
{
|
||||
constexpr QColorVector whitePointD50 = QColorVector::D50();
|
||||
if (whitePoint != whitePointD50) {
|
||||
// A chromatic adaptation to map a white point to XYZ D50.
|
||||
|
||||
// The Bradford method chromatic adaptation matrix:
|
||||
const QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
|
||||
{ 0.2664f, 1.7135f, -0.0685f },
|
||||
{ -0.1614f, 0.0367f, 1.0296f } };
|
||||
const QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
|
||||
{ -0.1470543f, 0.5183603f, 0.0400428f },
|
||||
{ 0.1599627f, 0.0492912f, 0.9684867f } };
|
||||
|
||||
const QColorVector srcCone = abrad.map(whitePoint);
|
||||
if (srcCone.x && srcCone.y && srcCone.z) {
|
||||
const QColorVector dstCone = abrad.map(whitePointD50);
|
||||
const QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
|
||||
{ 0, dstCone.y / srcCone.y, 0 },
|
||||
{ 0, 0, dstCone.z / srcCone.z } };
|
||||
return abradinv * (wToD50 * abrad);
|
||||
}
|
||||
}
|
||||
return QColorMatrix::identity();
|
||||
}
|
||||
|
||||
// These are used to recognize matrices from ICC profiles:
|
||||
static QColorMatrix toXyzFromSRgb()
|
||||
{
|
||||
|
@ -95,34 +95,7 @@ QColorMatrix QColorSpacePrimaries::toXyzMatrix() const
|
||||
toXyz = toXyz * QColorMatrix::fromScale(whiteScale);
|
||||
|
||||
// But we want a conversion to XYZ relative to D50
|
||||
QColorVector wXyzD50 = QColorVector::D50();
|
||||
|
||||
if (wXyz != wXyzD50) {
|
||||
// Do chromatic adaptation to map our white point to XYZ D50.
|
||||
|
||||
// The Bradford method chromatic adaptation matrix:
|
||||
QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
|
||||
{ 0.2664f, 1.7135f, -0.0685f },
|
||||
{ -0.1614f, 0.0367f, 1.0296f } };
|
||||
QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
|
||||
{ -0.1470543f, 0.5183603f, 0.0400428f },
|
||||
{ 0.1599627f, 0.0492912f, 0.9684867f } };
|
||||
|
||||
QColorVector srcCone = abrad.map(wXyz);
|
||||
QColorVector dstCone = abrad.map(wXyzD50);
|
||||
|
||||
if (srcCone.x && srcCone.y && srcCone.z) {
|
||||
QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
|
||||
{ 0, dstCone.y / srcCone.y, 0 },
|
||||
{ 0, 0, dstCone.z / srcCone.z } };
|
||||
|
||||
|
||||
QColorMatrix chromaticAdaptation = abradinv * (wToD50 * abrad);
|
||||
toXyz = chromaticAdaptation * toXyz;
|
||||
} else {
|
||||
toXyz.r = {0, 0, 0}; // set to invalid value
|
||||
}
|
||||
}
|
||||
toXyz = QColorMatrix::chromaticAdaptation(wXyz) * toXyz;
|
||||
|
||||
return toXyz;
|
||||
}
|
||||
@ -133,6 +106,7 @@ QColorSpacePrivate::QColorSpacePrivate()
|
||||
|
||||
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace)
|
||||
: namedColorSpace(namedColorSpace)
|
||||
, colorModel(QColorSpace::ColorModel::Rgb)
|
||||
{
|
||||
switch (namedColorSpace) {
|
||||
case QColorSpace::SRgb:
|
||||
@ -170,6 +144,7 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSp
|
||||
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma)
|
||||
: primaries(primaries)
|
||||
, transferFunction(transferFunction)
|
||||
, colorModel(QColorSpace::ColorModel::Rgb)
|
||||
, gamma(gamma)
|
||||
{
|
||||
identifyColorSpace();
|
||||
@ -181,18 +156,45 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
|
||||
float gamma)
|
||||
: primaries(QColorSpace::Primaries::Custom)
|
||||
, transferFunction(transferFunction)
|
||||
, colorModel(QColorSpace::ColorModel::Rgb)
|
||||
, gamma(gamma)
|
||||
, whitePoint(primaries.whitePoint)
|
||||
{
|
||||
Q_ASSERT(primaries.areValid());
|
||||
toXyz = primaries.toXyzMatrix();
|
||||
whitePoint = QColorVector(primaries.whitePoint);
|
||||
identifyColorSpace();
|
||||
setTransferFunction();
|
||||
}
|
||||
|
||||
QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint,
|
||||
QColorSpace::TransferFunction transferFunction,
|
||||
float gamma)
|
||||
: primaries(QColorSpace::Primaries::Custom)
|
||||
, transferFunction(transferFunction)
|
||||
, colorModel(QColorSpace::ColorModel::Gray)
|
||||
, gamma(gamma)
|
||||
, whitePoint(whitePoint)
|
||||
{
|
||||
toXyz = QColorMatrix::chromaticAdaptation(this->whitePoint);
|
||||
setTransferFunction();
|
||||
}
|
||||
|
||||
QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable)
|
||||
: primaries(QColorSpace::Primaries::Custom)
|
||||
, transferFunction(QColorSpace::TransferFunction::Custom)
|
||||
, colorModel(QColorSpace::ColorModel::Gray)
|
||||
, gamma(0)
|
||||
, whitePoint(whitePoint)
|
||||
{
|
||||
toXyz = QColorMatrix::chromaticAdaptation(this->whitePoint);
|
||||
setTransferFunctionTable(transferFunctionTable);
|
||||
setTransferFunction();
|
||||
}
|
||||
|
||||
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const QList<uint16_t> &transferFunctionTable)
|
||||
: primaries(primaries)
|
||||
, transferFunction(QColorSpace::TransferFunction::Custom)
|
||||
, colorModel(QColorSpace::ColorModel::Rgb)
|
||||
, gamma(0)
|
||||
{
|
||||
setTransferFunctionTable(transferFunctionTable);
|
||||
@ -203,11 +205,12 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const Q
|
||||
QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, const QList<uint16_t> &transferFunctionTable)
|
||||
: primaries(QColorSpace::Primaries::Custom)
|
||||
, transferFunction(QColorSpace::TransferFunction::Custom)
|
||||
, colorModel(QColorSpace::ColorModel::Rgb)
|
||||
, gamma(0)
|
||||
, whitePoint(primaries.whitePoint)
|
||||
{
|
||||
Q_ASSERT(primaries.areValid());
|
||||
toXyz = primaries.toXyzMatrix();
|
||||
whitePoint = QColorVector(primaries.whitePoint);
|
||||
setTransferFunctionTable(transferFunctionTable);
|
||||
identifyColorSpace();
|
||||
initialize();
|
||||
@ -219,6 +222,7 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
|
||||
const QList<uint16_t> &blueTransferFunctionTable)
|
||||
: primaries(QColorSpace::Primaries::Custom)
|
||||
, transferFunction(QColorSpace::TransferFunction::Custom)
|
||||
, colorModel(QColorSpace::ColorModel::Rgb)
|
||||
, gamma(0)
|
||||
{
|
||||
Q_ASSERT(primaries.areValid());
|
||||
@ -437,8 +441,9 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const
|
||||
transform.d = ptr;
|
||||
ptr->colorSpaceIn = this;
|
||||
ptr->colorSpaceOut = this;
|
||||
// Convert to XYZ relative to our white point, not the regular D50 white point.
|
||||
if (isThreeComponentMatrix())
|
||||
ptr->colorMatrix = toXyz;
|
||||
ptr->colorMatrix = QColorMatrix::chromaticAdaptation(whitePoint).inverted() * toXyz;
|
||||
else
|
||||
ptr->colorMatrix = QColorMatrix::identity();
|
||||
return transform;
|
||||
@ -483,9 +488,10 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
|
||||
A color space can generally speaking be conceived as a combination of set of primary
|
||||
colors and a transfer function. The primaries defines the axes of the color space, and
|
||||
the transfer function how values are mapped on the axes.
|
||||
The primaries are defined by three primary colors that represent exactly how red, green,
|
||||
and blue look in this particular color space, and a white color that represents where
|
||||
and how bright pure white is. The range of colors expressible by the primary colors is
|
||||
The primaries are for ColorModel::Rgb color spaces defined by three primary colors that
|
||||
represent exactly how red, green, and blue look in this particular color space, and a white
|
||||
color that represents where and how bright pure white is. For grayscale color spaces, only
|
||||
a single white primary is needed. The range of colors expressible by the primary colors is
|
||||
called the gamut, and a color space that can represent a wider range of colors is also
|
||||
known as a wide-gamut color space.
|
||||
|
||||
@ -554,6 +560,17 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
|
||||
either primaries or transfer function on a color space on this type it will reset to an empty ThreeComponentMatrix form.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QColorSpace::ColorModel
|
||||
\since 6.8
|
||||
|
||||
Defines the color model used by the color space data.
|
||||
|
||||
\value Undefined No color model
|
||||
\value Rgb An RGB color model with red, green, and blue colors. Can apply to RGB and grayscale data.
|
||||
\value Gray A gray scale color model. Can only apply to grayscale data.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QColorSpace::QColorSpace()
|
||||
|
||||
@ -616,6 +633,28 @@ QColorSpace::QColorSpace(QColorSpace::Primaries gamut, const QList<uint16_t> &tr
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a custom grayscale color space with the white point \a whitePoint, using the transfer function \a transferFunction and
|
||||
optionally \a gamma.
|
||||
|
||||
\since 6.8
|
||||
*/
|
||||
QColorSpace::QColorSpace(const QPointF &whitePoint, TransferFunction transferFunction, float gamma)
|
||||
: d_ptr(new QColorSpacePrivate(whitePoint, transferFunction, gamma))
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a custom grayscale color space with white point \a whitePoint, and using the custom transfer function described by
|
||||
\a transferFunctionTable.
|
||||
|
||||
\since 6.8
|
||||
*/
|
||||
QColorSpace::QColorSpace(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable)
|
||||
: d_ptr(new QColorSpacePrivate(whitePoint, transferFunctionTable))
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a custom colorspace with a primaries based on the chromaticities of the primary colors \a whitePoint,
|
||||
\a redPoint, \a greenPoint and \a bluePoint, and using the transfer function \a transferFunction and optionally \a gamma.
|
||||
@ -869,6 +908,7 @@ void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId)
|
||||
d_ptr->iccProfile = {};
|
||||
d_ptr->description = QString();
|
||||
d_ptr->primaries = primariesId;
|
||||
d_ptr->colorModel = QColorSpace::ColorModel::Rgb;
|
||||
d_ptr->identifyColorSpace();
|
||||
d_ptr->setToXyzMatrix();
|
||||
}
|
||||
@ -898,14 +938,67 @@ void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoin
|
||||
d_ptr->iccProfile = {};
|
||||
d_ptr->description = QString();
|
||||
d_ptr->primaries = QColorSpace::Primaries::Custom;
|
||||
d_ptr->colorModel = QColorSpace::ColorModel::Rgb;
|
||||
d_ptr->toXyz = toXyz;
|
||||
d_ptr->whitePoint = QColorVector(primaries.whitePoint);
|
||||
d_ptr->identifyColorSpace();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the white point used for this color space. Returns a null QPointF if not defined.
|
||||
|
||||
\since 6.8
|
||||
*/
|
||||
QPointF QColorSpace::whitePoint() const
|
||||
{
|
||||
if (Q_UNLIKELY(!d_ptr))
|
||||
return QPointF();
|
||||
return d_ptr->whitePoint.toChromaticity();
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the white point to used for this color space to \a whitePoint.
|
||||
|
||||
\since 6.8
|
||||
*/
|
||||
void QColorSpace::setWhitePoint(const QPointF &whitePoint)
|
||||
{
|
||||
if (Q_UNLIKELY(!d_ptr)) {
|
||||
d_ptr = new QColorSpacePrivate(whitePoint, TransferFunction::Custom, 0.0f);
|
||||
return;
|
||||
}
|
||||
if (QColorVector(whitePoint) == d_ptr->whitePoint)
|
||||
return;
|
||||
detach();
|
||||
if (d_ptr->transformModel == TransformModel::ElementListProcessing)
|
||||
d_ptr->clearElementListProcessingForEdit();
|
||||
d_ptr->iccProfile = {};
|
||||
d_ptr->description = QString();
|
||||
d_ptr->primaries = QColorSpace::Primaries::Custom;
|
||||
// An RGB color model stays RGB, a gray stays gray, but an undefined one can now be considered gray
|
||||
if (d_ptr->colorModel == QColorSpace::ColorModel::Undefined)
|
||||
d_ptr->colorModel = QColorSpace::ColorModel::Gray;
|
||||
QColorVector wXyz(whitePoint);
|
||||
if (d_ptr->transformModel == QColorSpace::TransformModel::ThreeComponentMatrix) {
|
||||
if (d_ptr->colorModel == QColorSpace::ColorModel::Rgb) {
|
||||
// Rescale toXyz to new whitepoint
|
||||
QColorMatrix chad = QColorMatrix::chromaticAdaptation(d_ptr->whitePoint);
|
||||
QColorMatrix rawToXyz = chad.inverted() * d_ptr->toXyz;
|
||||
QColorVector whiteScale = rawToXyz.inverted().map(wXyz);
|
||||
rawToXyz = rawToXyz * QColorMatrix::fromScale(whiteScale);
|
||||
d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz) * rawToXyz;
|
||||
} else if (d_ptr->colorModel == QColorSpace::ColorModel::Gray) {
|
||||
d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz);
|
||||
}
|
||||
}
|
||||
d_ptr->whitePoint = wXyz;
|
||||
d_ptr->identifyColorSpace();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the transfrom processing model used for this color space.
|
||||
|
||||
\since 6.8
|
||||
*/
|
||||
QColorSpace::TransformModel QColorSpace::transformModel() const noexcept
|
||||
{
|
||||
@ -914,6 +1007,18 @@ QColorSpace::TransformModel QColorSpace::transformModel() const noexcept
|
||||
return d_ptr->transformModel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the color model this color space can represent
|
||||
|
||||
\since 6.8
|
||||
*/
|
||||
QColorSpace::ColorModel QColorSpace::colorModel() const noexcept
|
||||
{
|
||||
if (Q_UNLIKELY(!d_ptr))
|
||||
return QColorSpace::ColorModel::Undefined;
|
||||
return d_ptr->colorModel;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
@ -1007,8 +1112,13 @@ bool QColorSpacePrivate::isValid() const noexcept
|
||||
return !mAB.isEmpty();
|
||||
if (!toXyz.isValid())
|
||||
return false;
|
||||
if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
|
||||
return false;
|
||||
if (colorModel == QColorSpace::ColorModel::Gray) {
|
||||
if (!trc[0].isValid())
|
||||
return false;
|
||||
} else {
|
||||
if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -50,9 +50,17 @@ public:
|
||||
ElementListProcessing,
|
||||
};
|
||||
Q_ENUM(TransformModel)
|
||||
enum class ColorModel : uint8_t {
|
||||
Undefined = 0,
|
||||
Rgb = 1,
|
||||
Gray = 2,
|
||||
};
|
||||
Q_ENUM(ColorModel)
|
||||
|
||||
QColorSpace() noexcept = default;
|
||||
QColorSpace(NamedColorSpace namedColorSpace);
|
||||
QColorSpace(const QPointF &whitePoint, TransferFunction transferFunction, float gamma = 0.0f);
|
||||
QColorSpace(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable);
|
||||
QColorSpace(Primaries primaries, TransferFunction transferFunction, float gamma = 0.0f);
|
||||
QColorSpace(Primaries primaries, float gamma);
|
||||
QColorSpace(Primaries primaries, const QList<uint16_t> &transferFunctionTable);
|
||||
@ -104,8 +112,11 @@ public:
|
||||
void setPrimaries(Primaries primariesId);
|
||||
void setPrimaries(const QPointF &whitePoint, const QPointF &redPoint,
|
||||
const QPointF &greenPoint, const QPointF &bluePoint);
|
||||
void setWhitePoint(const QPointF &whitePoint);
|
||||
QPointF whitePoint() const;
|
||||
|
||||
TransformModel transformModel() const noexcept;
|
||||
ColorModel colorModel() const noexcept;
|
||||
void detach();
|
||||
bool isValid() const noexcept;
|
||||
bool isValidTarget() const noexcept;
|
||||
|
@ -66,6 +66,8 @@ public:
|
||||
const QList<uint16_t> &redTransferFunctionTable,
|
||||
const QList<uint16_t> &greenTransferFunctionTable,
|
||||
const QList<uint16_t> &blueRransferFunctionTable);
|
||||
QColorSpacePrivate(const QPointF &whitePoint, QColorSpace::TransferFunction transferFunction, float gamma);
|
||||
QColorSpacePrivate(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable);
|
||||
QColorSpacePrivate(const QColorSpacePrivate &other) = default;
|
||||
|
||||
static const QColorSpacePrivate *get(const QColorSpace &colorSpace)
|
||||
@ -101,6 +103,7 @@ public:
|
||||
QColorSpace::Primaries primaries = QColorSpace::Primaries::Custom;
|
||||
QColorSpace::TransferFunction transferFunction = QColorSpace::TransferFunction::Custom;
|
||||
QColorSpace::TransformModel transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
|
||||
QColorSpace::ColorModel colorModel = QColorSpace::ColorModel::Undefined;
|
||||
float gamma = 0.0f;
|
||||
QColorVector whitePoint;
|
||||
|
||||
|
@ -97,6 +97,8 @@ public:
|
||||
|
||||
float apply(float x) const
|
||||
{
|
||||
if (isEmpty())
|
||||
return x;
|
||||
x = std::clamp(x, 0.0f, 1.0f);
|
||||
x *= m_tableSize - 1;
|
||||
const uint32_t lo = static_cast<uint32_t>(std::floor(x));
|
||||
|
@ -346,6 +346,39 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM
|
||||
#endif
|
||||
}
|
||||
|
||||
template<ApplyMatrixForm doClamp = DoClamp>
|
||||
static void clampIfNeeded(QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
if constexpr (doClamp != DoClamp)
|
||||
return;
|
||||
#if defined(__SSE2__)
|
||||
const __m128 minV = _mm_set1_ps(0.0f);
|
||||
const __m128 maxV = _mm_set1_ps(1.0f);
|
||||
for (qsizetype j = 0; j < len; ++j) {
|
||||
__m128 c = _mm_loadu_ps(&buffer[j].x);
|
||||
c = _mm_min_ps(c, maxV);
|
||||
c = _mm_max_ps(c, minV);
|
||||
_mm_storeu_ps(&buffer[j].x, c);
|
||||
}
|
||||
#elif defined(__ARM_NEON__)
|
||||
const float32x4_t minV = vdupq_n_f32(0.0f);
|
||||
const float32x4_t maxV = vdupq_n_f32(1.0f);
|
||||
for (qsizetype j = 0; j < len; ++j) {
|
||||
float32x4_t c = vld1q_f32(&buffer[j].x);
|
||||
c = vminq_f32(c, maxV);
|
||||
c = vmaxq_f32(c, minV);
|
||||
vst1q_f32(&buffer[j].x, c);
|
||||
}
|
||||
#else
|
||||
for (qsizetype j = 0; j < len; ++j) {
|
||||
const QColorVector cv = buffer[j];
|
||||
buffer[j].x = std::clamp(cv.x, 0.f, 1.f);
|
||||
buffer[j].y = std::clamp(cv.y, 0.f, 1.f);
|
||||
buffer[j].z = std::clamp(cv.z, 0.f, 1.f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__SSE2__) || defined(__ARM_NEON__)
|
||||
template<typename T>
|
||||
static constexpr inline bool isArgb();
|
||||
@ -362,8 +395,24 @@ inline int getAlpha<QRgb>(const QRgb &p)
|
||||
template<>
|
||||
inline int getAlpha<QRgba64>(const QRgba64 &p)
|
||||
{ return p.alpha(); }
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
static float getAlphaF(const T &);
|
||||
template<> float getAlphaF(const QRgb &r)
|
||||
{
|
||||
return qAlpha(r) * (1.f / 255.f);
|
||||
}
|
||||
template<> float getAlphaF(const QRgba64 &r)
|
||||
{
|
||||
return r.alpha() * (1.f / 65535.f);
|
||||
}
|
||||
template<> float getAlphaF(const QRgbaFloat32 &r)
|
||||
{
|
||||
return r.a;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr);
|
||||
template<typename T>
|
||||
@ -768,15 +817,16 @@ inline void storeP<QRgba64>(QRgba64 &p, __m128i &v, int a)
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
|
||||
template<typename D, typename S,
|
||||
typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
|
||||
static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
const __m128 v4080 = _mm_set1_ps(4080.f);
|
||||
const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
|
||||
constexpr bool isARGB = isArgb<T>();
|
||||
constexpr bool isARGB = isArgb<D>();
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = getAlpha<T>(src[i]);
|
||||
const int a = getAlpha<S>(src[i]);
|
||||
__m128 vf = _mm_loadu_ps(&buffer[i].x);
|
||||
__m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
|
||||
__m128 va = _mm_mul_ps(_mm_set1_ps(a), iFF00);
|
||||
@ -789,21 +839,21 @@ static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer,
|
||||
vf = _mm_cvtepi32_ps(v);
|
||||
vf = _mm_mul_ps(vf, va);
|
||||
v = _mm_cvtps_epi32(vf);
|
||||
storeP<T>(dst[i], v, a);
|
||||
storeP<D>(dst[i], v, a);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void storePremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
|
||||
const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
template<typename S>
|
||||
static void storePremultiplied(QRgbaFloat32 *dst, const S *src,
|
||||
const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
const __m128 v4080 = _mm_set1_ps(4080.f);
|
||||
const __m128 vZero = _mm_set1_ps(0.0f);
|
||||
const __m128 vOne = _mm_set1_ps(1.0f);
|
||||
const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256));
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float a = src[i].a;
|
||||
const float a = getAlphaF<S>(src[i]);
|
||||
__m128 va = _mm_set1_ps(a);
|
||||
__m128 vf = _mm_loadu_ps(&buffer[i].x);
|
||||
const __m128 under = _mm_cmplt_ps(vf, vZero);
|
||||
@ -850,14 +900,15 @@ inline void storePU<QRgba64>(QRgba64 &p, __m128i &v, int a)
|
||||
_mm_storel_epi64((__m128i *)&p, v);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
|
||||
template<typename D, typename S,
|
||||
typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
|
||||
static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
const __m128 v4080 = _mm_set1_ps(4080.f);
|
||||
constexpr bool isARGB = isArgb<T>();
|
||||
constexpr bool isARGB = isArgb<D>();
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = getAlpha<T>(src[i]);
|
||||
const int a = getAlpha<S>(src[i]);
|
||||
__m128 vf = _mm_loadu_ps(&buffer[i].x);
|
||||
__m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
|
||||
const int ridx = _mm_extract_epi16(v, 0);
|
||||
@ -867,21 +918,21 @@ static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffe
|
||||
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], isARGB ? 2 : 0);
|
||||
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], 1);
|
||||
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], isARGB ? 0 : 2);
|
||||
storePU<T>(dst[i], v, a);
|
||||
storePU<D>(dst[i], v, a);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void storeUnpremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
|
||||
const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
template<typename S>
|
||||
void storeUnpremultiplied(QRgbaFloat32 *dst, const S *src,
|
||||
const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
const __m128 v4080 = _mm_set1_ps(4080.f);
|
||||
const __m128 vZero = _mm_set1_ps(0.0f);
|
||||
const __m128 vOne = _mm_set1_ps(1.0f);
|
||||
const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256));
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float a = src[i].a;
|
||||
const float a = getAlphaF<S>(src[i]);
|
||||
__m128 vf = _mm_loadu_ps(&buffer[i].x);
|
||||
const __m128 under = _mm_cmplt_ps(vf, vZero);
|
||||
const __m128 over = _mm_cmpgt_ps(vf, vOne);
|
||||
@ -907,10 +958,9 @@ void storeUnpremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *s
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
|
||||
static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
const __m128 v4080 = _mm_set1_ps(4080.f);
|
||||
constexpr bool isARGB = isArgb<T>();
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
@ -928,11 +978,10 @@ static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const
|
||||
}
|
||||
|
||||
template<>
|
||||
void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
|
||||
void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst,
|
||||
const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
const __m128 v4080 = _mm_set1_ps(4080.f);
|
||||
const __m128 vZero = _mm_set1_ps(0.0f);
|
||||
const __m128 vOne = _mm_set1_ps(1.0f);
|
||||
@ -976,14 +1025,15 @@ inline void storeP<QRgba64>(QRgba64 &p, const uint16x4_t &v)
|
||||
vst1_u16((uint16_t *)&p, v);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
|
||||
template<typename D, typename S,
|
||||
typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
|
||||
static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
const float iFF00 = 1.0f / (255 * 256);
|
||||
constexpr bool isARGB = isArgb<T>();
|
||||
constexpr bool isARGB = isArgb<D>();
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = getAlpha<T>(src[i]);
|
||||
const int a = getAlpha<S>(src[i]);
|
||||
float32x4_t vf = vld1q_f32(&buffer[i].x);
|
||||
uint32x4_t v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f)));
|
||||
const int ridx = vgetq_lane_u32(v, 0);
|
||||
@ -998,7 +1048,7 @@ static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer,
|
||||
v = vcvtq_u32_f32(vf);
|
||||
uint16x4_t v16 = vmovn_u32(v);
|
||||
v16 = vset_lane_u16(a, v16, 3);
|
||||
storeP<T>(dst[i], v16);
|
||||
storeP<D>(dst[i], v16);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1020,13 +1070,14 @@ inline void storePU<QRgba64>(QRgba64 &p, uint16x4_t &v, int a)
|
||||
vst1_u16((uint16_t *)&p, v);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
|
||||
template<typename D, typename S,
|
||||
typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
|
||||
static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
constexpr bool isARGB = isArgb<T>();
|
||||
constexpr bool isARGB = isArgb<D>();
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = getAlpha<T>(src[i]);
|
||||
const int a = getAlpha<S>(src[i]);
|
||||
float32x4_t vf = vld1q_f32(&buffer[i].x);
|
||||
uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f))));
|
||||
const int ridx = vget_lane_u16(v, 0);
|
||||
@ -1035,15 +1086,14 @@ static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffe
|
||||
v = vset_lane_u16(d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], v, isARGB ? 2 : 0);
|
||||
v = vset_lane_u16(d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], v, 1);
|
||||
v = vset_lane_u16(d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], v, isARGB ? 0 : 2);
|
||||
storePU<T>(dst[i], v, a);
|
||||
storePU<D>(dst[i], v, a);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
|
||||
static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
constexpr bool isARGB = isArgb<T>();
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
float32x4_t vf = vld1q_f32(&buffer[i].x);
|
||||
@ -1082,10 +1132,9 @@ static void storeUnpremultiplied(QRgb *dst, const QRgb *src, const QColorVector
|
||||
}
|
||||
}
|
||||
|
||||
static void storeOpaque(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len,
|
||||
static void storeOpaque(QRgb *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int r = d_ptr->colorSpaceOut->lut[0]->u8FromLinearF32(buffer[i].x);
|
||||
const int g = d_ptr->colorSpaceOut->lut[1]->u8FromLinearF32(buffer[i].y);
|
||||
@ -1094,11 +1143,12 @@ static void storeOpaque(QRgb *dst, const QRgb *src, const QColorVector *buffer,
|
||||
}
|
||||
}
|
||||
|
||||
static void storePremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
|
||||
template<typename S>
|
||||
static void storePremultiplied(QRgba64 *dst, const S *src, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = src[i].alpha();
|
||||
const int a = getAlphaF(src[i]) * 65535.f;
|
||||
const float fa = a / (255.0f * 256.0f);
|
||||
const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * 4080.0f + 0.5f)];
|
||||
const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * 4080.0f + 0.5f)];
|
||||
@ -1107,21 +1157,22 @@ static void storePremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVec
|
||||
}
|
||||
}
|
||||
|
||||
static void storeUnpremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
|
||||
template<typename S>
|
||||
static void storeUnpremultiplied(QRgba64 *dst, const S *src, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = getAlphaF(src[i]) * 65535.f;
|
||||
const int r = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].x);
|
||||
const int g = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y);
|
||||
const int b = d_ptr->colorSpaceOut->lut[2]->u16FromLinearF32(buffer[i].z);
|
||||
dst[i] = qRgba64(r, g, b, src[i].alpha());
|
||||
dst[i] = qRgba64(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
static void storeOpaque(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
|
||||
static void storeOpaque(QRgba64 *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int r = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].x);
|
||||
const int g = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y);
|
||||
@ -1131,11 +1182,12 @@ static void storeOpaque(QRgba64 *dst, const QRgba64 *src, const QColorVector *bu
|
||||
}
|
||||
#endif
|
||||
#if !defined(__SSE2__)
|
||||
static void storePremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer,
|
||||
template<typename S>
|
||||
static void storePremultiplied(QRgbaFloat32 *dst, const S *src, const QColorVector *buffer,
|
||||
const qsizetype len, const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float a = src[i].a;
|
||||
const float a = getAlphaF(src[i]);
|
||||
dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x) * a;
|
||||
dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y) * a;
|
||||
dst[i].b = d_ptr->colorSpaceOut->trc[2].applyInverseExtended(buffer[i].z) * a;
|
||||
@ -1143,11 +1195,12 @@ static void storePremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const
|
||||
}
|
||||
}
|
||||
|
||||
static void storeUnpremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer,
|
||||
template<typename S>
|
||||
static void storeUnpremultiplied(QRgbaFloat32 *dst, const S *src, const QColorVector *buffer,
|
||||
const qsizetype len, const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float a = src[i].a;
|
||||
const float a = getAlphaF(src[i]);
|
||||
dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x);
|
||||
dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y);
|
||||
dst[i].b = d_ptr->colorSpaceOut->trc[2].applyInverseExtended(buffer[i].z);
|
||||
@ -1155,10 +1208,9 @@ static void storeUnpremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, con
|
||||
}
|
||||
}
|
||||
|
||||
static void storeOpaque(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer, const qsizetype len,
|
||||
static void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x);
|
||||
dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y);
|
||||
@ -1167,20 +1219,35 @@ static void storeOpaque(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColor
|
||||
}
|
||||
}
|
||||
#endif
|
||||
static void storeGray(quint8 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
|
||||
static void loadGray(QColorVector *buffer, const quint8 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
for (qsizetype i = 0; i < len; ++i)
|
||||
dst[i] = d_ptr->colorSpaceOut->lut[1]->u8FromLinearF32(buffer[i].y);
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float y = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]);
|
||||
buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
|
||||
}
|
||||
}
|
||||
|
||||
static void storeGray(quint16 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
|
||||
static void loadGray(QColorVector *buffer, const quint16 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float y = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]);
|
||||
buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
|
||||
}
|
||||
}
|
||||
|
||||
static void storeOpaque(quint8 *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
for (qsizetype i = 0; i < len; ++i)
|
||||
dst[i] = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y);
|
||||
dst[i] = d_ptr->colorSpaceOut->lut[0]->u8FromLinearF32(buffer[i].y);
|
||||
}
|
||||
|
||||
static void storeOpaque(quint16 *dst, const QColorVector *buffer, const qsizetype len,
|
||||
const QColorTransformPrivate *d_ptr)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i)
|
||||
dst[i] = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].y);
|
||||
}
|
||||
|
||||
static constexpr qsizetype WorkBlockSize = 256;
|
||||
@ -1255,7 +1322,19 @@ void loadPremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const q
|
||||
}
|
||||
}
|
||||
|
||||
static void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
|
||||
template<typename T>
|
||||
static void storeUnpremultipliedLUT(QRgb *dst, const T *, const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int r = buffer[i].x * 255.f;
|
||||
const int g = buffer[i].y * 255.f;
|
||||
const int b = buffer[i].z * 255.f;
|
||||
dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int r = buffer[i].x * 255.f;
|
||||
@ -1265,8 +1344,34 @@ static void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVect
|
||||
}
|
||||
}
|
||||
|
||||
static void storeUnpremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
|
||||
template<typename T>
|
||||
static void storeUnpremultipliedLUT(QRgba64 *dst, const T *,
|
||||
const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int r = buffer[i].x * 65535.f;
|
||||
const int g = buffer[i].y * 65535.f;
|
||||
const int b = buffer[i].z * 65535.f;
|
||||
dst[i] = qRgba64(r, g, b, 65535);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void storeUnpremultipliedLUT(QRgba64 *dst, const QRgb *src,
|
||||
const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = qAlpha(src[i]) * 257;
|
||||
const int r = buffer[i].x * 65535.f;
|
||||
const int g = buffer[i].y * 65535.f;
|
||||
const int b = buffer[i].z * 65535.f;
|
||||
dst[i] = qRgba64(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void storeUnpremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
|
||||
const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int r = buffer[i].x * 65535.f;
|
||||
@ -1276,18 +1381,23 @@ static void storeUnpremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
|
||||
}
|
||||
}
|
||||
|
||||
static void storeUnpremultipliedLUT(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
|
||||
template<typename T>
|
||||
static void storeUnpremultipliedLUT(QRgbaFloat32 *dst, const T *src,
|
||||
const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float r = buffer[i].x;
|
||||
const float g = buffer[i].y;
|
||||
const float b = buffer[i].z;
|
||||
dst[i] = QRgbaFloat32{r, g, b, src[i].a};
|
||||
dst[i] = QRgbaFloat32{r, g, b, getAlphaF(src[i])};
|
||||
}
|
||||
}
|
||||
|
||||
static void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
|
||||
template<typename T>
|
||||
static void storePremultipliedLUT(QRgb *, const T *, const QColorVector *, const qsizetype);
|
||||
|
||||
template<>
|
||||
void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = qAlpha(src[i]);
|
||||
@ -1298,8 +1408,23 @@ static void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector
|
||||
}
|
||||
}
|
||||
|
||||
static void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
|
||||
const QColorVector *buffer, const qsizetype len)
|
||||
template<typename T>
|
||||
static void storePremultipliedLUT(QRgba64 *, const T *, const QColorVector *, const qsizetype);
|
||||
|
||||
template<>
|
||||
void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = qAlpha(src[i]) * 257;
|
||||
const int r = buffer[i].x * a;
|
||||
const int g = buffer[i].y * a;
|
||||
const int b = buffer[i].z * a;
|
||||
dst[i] = qRgba64(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const int a = src[i].alpha();
|
||||
@ -1310,11 +1435,11 @@ static void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
|
||||
}
|
||||
}
|
||||
|
||||
static void storePremultipliedLUT(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
|
||||
const QColorVector *buffer, const qsizetype len)
|
||||
template<typename T>
|
||||
static void storePremultipliedLUT(QRgbaFloat32 *dst, const T *src, const QColorVector *buffer, const qsizetype len)
|
||||
{
|
||||
for (qsizetype i = 0; i < len; ++i) {
|
||||
const float a = src[i].a;
|
||||
const float a = getAlphaF(src[i]);
|
||||
const float r = buffer[i].x * a;
|
||||
const float g = buffer[i].y * a;
|
||||
const float b = buffer[i].z * a;
|
||||
@ -1372,6 +1497,9 @@ QColorVector QColorTransformPrivate::map(QColorVector c) const
|
||||
for (auto &&element : colorSpaceIn->mAB)
|
||||
std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element);
|
||||
}
|
||||
c.x = std::clamp(c.x, 0.0f, 1.0f);
|
||||
c.y = std::clamp(c.y, 0.0f, 1.0f);
|
||||
c.z = std::clamp(c.z, 0.0f, 1.0f);
|
||||
|
||||
// Match Profile Connection Spaces (PCS):
|
||||
if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab)
|
||||
@ -1380,11 +1508,12 @@ QColorVector QColorTransformPrivate::map(QColorVector c) const
|
||||
c = c.labToXyz();
|
||||
|
||||
if (colorSpaceOut->isThreeComponentMatrix()) {
|
||||
if (!colorSpaceIn->isThreeComponentMatrix())
|
||||
if (!colorSpaceIn->isThreeComponentMatrix()) {
|
||||
c = colorMatrix.map(c);
|
||||
c.x = std::clamp(c.x, 0.0f, 1.0f);
|
||||
c.y = std::clamp(c.y, 0.0f, 1.0f);
|
||||
c.z = std::clamp(c.z, 0.0f, 1.0f);
|
||||
c.x = std::clamp(c.x, 0.0f, 1.0f);
|
||||
c.y = std::clamp(c.y, 0.0f, 1.0f);
|
||||
c.z = std::clamp(c.z, 0.0f, 1.0f);
|
||||
}
|
||||
if (colorSpaceOut->lut.generated.loadAcquire()) {
|
||||
c.x = colorSpaceOut->lut[0]->fromLinear(c.x);
|
||||
c.y = colorSpaceOut->lut[1]->fromLinear(c.y);
|
||||
@ -1398,6 +1527,9 @@ QColorVector QColorTransformPrivate::map(QColorVector c) const
|
||||
// Do element based conversion
|
||||
for (auto &&element : colorSpaceOut->mBA)
|
||||
std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element);
|
||||
c.x = std::clamp(c.x, 0.0f, 1.0f);
|
||||
c.y = std::clamp(c.y, 0.0f, 1.0f);
|
||||
c.z = std::clamp(c.z, 0.0f, 1.0f);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@ -1438,8 +1570,8 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const
|
||||
return c;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void QColorTransformPrivate::applyConvertIn(const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
|
||||
template<typename S>
|
||||
void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
|
||||
{
|
||||
if (colorSpaceIn->isThreeComponentMatrix()) {
|
||||
if (flags & InputPremultiplied)
|
||||
@ -1455,20 +1587,24 @@ void QColorTransformPrivate::applyConvertIn(const T *src, QColorVector *buffer,
|
||||
else
|
||||
loadUnpremultipliedLUT(buffer, src, len);
|
||||
|
||||
if constexpr (std::is_same_v<S, QRgbaFloat16> || std::is_same_v<S, QRgbaFloat32>)
|
||||
clampIfNeeded<DoClamp>(buffer, len);
|
||||
|
||||
// Do element based conversion
|
||||
for (auto &&element : colorSpaceIn->mAB)
|
||||
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void QColorTransformPrivate::applyConvertOut(T *dst, const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
|
||||
template<typename D, typename S>
|
||||
void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
|
||||
{
|
||||
constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp;
|
||||
if (colorSpaceOut->isThreeComponentMatrix()) {
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
|
||||
applyMatrix<doClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
|
||||
|
||||
if (flags & InputOpaque)
|
||||
storeOpaque(dst, src, buffer, len, this);
|
||||
storeOpaque(dst, buffer, len, this);
|
||||
else if (flags & OutputPremultiplied)
|
||||
storePremultiplied(dst, src, buffer, len, this);
|
||||
else
|
||||
@ -1478,11 +1614,7 @@ void QColorTransformPrivate::applyConvertOut(T *dst, const T *src, QColorVector
|
||||
for (auto &&element : colorSpaceOut->mBA)
|
||||
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
|
||||
|
||||
for (qsizetype j = 0; j < len; ++j) {
|
||||
buffer[j].x = std::clamp(buffer[j].x, 0.f, 1.f);
|
||||
buffer[j].y = std::clamp(buffer[j].y, 0.f, 1.f);
|
||||
buffer[j].z = std::clamp(buffer[j].z, 0.f, 1.f);
|
||||
}
|
||||
clampIfNeeded<doClamp>(buffer, len);
|
||||
|
||||
if (flags & OutputPremultiplied)
|
||||
storePremultipliedLUT(dst, src, buffer, len);
|
||||
@ -1491,8 +1623,8 @@ void QColorTransformPrivate::applyConvertOut(T *dst, const T *src, QColorVector
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void QColorTransformPrivate::applyElementListTransform(T *dst, const T *src, qsizetype count, TransformFlags flags) const
|
||||
template<typename D, typename S>
|
||||
void QColorTransformPrivate::applyElementListTransform(D *dst, const S *src, qsizetype count, TransformFlags flags) const
|
||||
{
|
||||
Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix());
|
||||
|
||||
@ -1526,8 +1658,8 @@ void QColorTransformPrivate::applyElementListTransform(T *dst, const T *src, qsi
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsizetype count, TransformFlags flags) const
|
||||
template<typename D, typename S>
|
||||
void QColorTransformPrivate::applyThreeComponentMatrix(D *dst, const S *src, qsizetype count, TransformFlags flags) const
|
||||
{
|
||||
Q_ASSERT(colorSpaceIn->isThreeComponentMatrix() && colorSpaceOut->isThreeComponentMatrix());
|
||||
|
||||
@ -1538,7 +1670,7 @@ void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsi
|
||||
updateLutsOut();
|
||||
|
||||
bool doApplyMatrix = !colorMatrix.isIdentity();
|
||||
constexpr ApplyMatrixForm doClamp = (std::is_same_v<T, QRgbaFloat16> || std::is_same_v<T, QRgbaFloat32>) ? DoNotClamp : DoClamp;
|
||||
constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp;
|
||||
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
qsizetype i = 0;
|
||||
@ -1551,9 +1683,11 @@ void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsi
|
||||
|
||||
if (doApplyMatrix)
|
||||
applyMatrix<doClamp>(buffer, len, colorMatrix);
|
||||
else
|
||||
clampIfNeeded<doClamp>(buffer, len);
|
||||
|
||||
if (flags & InputOpaque)
|
||||
storeOpaque(dst + i, src + i, buffer, len, this);
|
||||
storeOpaque(dst + i, buffer, len, this);
|
||||
else if (flags & OutputPremultiplied)
|
||||
storePremultiplied(dst + i, src + i, buffer, len, this);
|
||||
else
|
||||
@ -1563,13 +1697,22 @@ void QColorTransformPrivate::applyThreeComponentMatrix(T *dst, const T *src, qsi
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const
|
||||
/*!
|
||||
\internal
|
||||
Applies the color transformation on \a count S pixels starting from
|
||||
\a src and stores the result in \a dst as D pixels .
|
||||
|
||||
Assumes unpremultiplied data by default. Set \a flags to change defaults.
|
||||
|
||||
\sa prepare()
|
||||
*/
|
||||
template<typename D, typename S>
|
||||
void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const
|
||||
{
|
||||
if (colorSpaceIn->isThreeComponentMatrix() && colorSpaceOut->isThreeComponentMatrix())
|
||||
applyThreeComponentMatrix<T>(dst, src, count, flags);
|
||||
if (isThreeComponentMatrix())
|
||||
applyThreeComponentMatrix<D, S>(dst, src, count, flags);
|
||||
else
|
||||
applyElementListTransform<T>(dst, src, count, flags);
|
||||
applyElementListTransform<D, S>(dst, src, count, flags);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1580,6 +1723,7 @@ void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, Transf
|
||||
template<typename D, typename S>
|
||||
void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
|
||||
{
|
||||
Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
|
||||
if (!colorSpaceIn->isThreeComponentMatrix()) {
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
|
||||
@ -1595,7 +1739,8 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
|
||||
for (auto &&element : colorSpaceIn->mAB)
|
||||
std::visit([&](auto &&elm) { visitElement(elm, buffer, len); }, element);
|
||||
|
||||
storeGray(dst + i, src + i, buffer, len, this);
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
storeOpaque(dst + i, buffer, len, this);
|
||||
|
||||
i += len;
|
||||
}
|
||||
@ -1620,17 +1765,69 @@ void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype cou
|
||||
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
|
||||
storeGray(dst + i, src + i, buffer, len, this);
|
||||
storeOpaque(dst + i, buffer, len, this);
|
||||
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
template<typename D, typename S>
|
||||
void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const
|
||||
{
|
||||
Q_ASSERT(colorSpaceIn->isThreeComponentMatrix());
|
||||
if constexpr (std::is_same_v<D, QRgb> || std::is_same_v<D, QRgba64> || std::is_same_v<D, QRgbaFloat32>) {
|
||||
if (!colorSpaceOut->isThreeComponentMatrix()) {
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
|
||||
qsizetype i = 0;
|
||||
while (i < count) {
|
||||
const qsizetype len = qMin(count - i, WorkBlockSize);
|
||||
loadGray(buffer, src + i, len, this);
|
||||
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
|
||||
// Do element based conversion
|
||||
for (auto &&element : colorSpaceOut->mBA)
|
||||
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
|
||||
|
||||
clampIfNeeded<DoClamp>(buffer, len);
|
||||
|
||||
storeUnpremultipliedLUT(dst, src, buffer, len); // input is always opaque
|
||||
|
||||
i += len;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
|
||||
if (!colorMatrix.isValid())
|
||||
return;
|
||||
|
||||
updateLutsIn();
|
||||
updateLutsOut();
|
||||
|
||||
QUninitialized<QColorVector, WorkBlockSize> buffer;
|
||||
|
||||
qsizetype i = 0;
|
||||
while (i < count) {
|
||||
const qsizetype len = qMin(count - i, WorkBlockSize);
|
||||
loadGray(buffer, src + i, len, this);
|
||||
|
||||
applyMatrix<DoClamp>(buffer, len, colorMatrix);
|
||||
|
||||
storeOpaque(dst + i, buffer, len, this);
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
\enum QColorTransformPrivate::TransformFlag
|
||||
|
||||
Defines how the transform is to be applied.
|
||||
Defines how the transform should handle alpha values.
|
||||
|
||||
\value Unpremultiplied The input and output should both be unpremultiplied.
|
||||
\value InputOpaque The input is guaranteed to be opaque.
|
||||
@ -1654,58 +1851,21 @@ void QColorTransformPrivate::prepare()
|
||||
updateLutsOut();
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Applies the color transformation on \a count QRgb pixels starting from
|
||||
\a src and stores the result in \a dst.
|
||||
|
||||
Thread-safe if prepare() has been called first.
|
||||
|
||||
Assumes unpremultiplied data by default. Set \a flags to change defaults.
|
||||
|
||||
\sa prepare()
|
||||
*/
|
||||
void QColorTransformPrivate::apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const
|
||||
{
|
||||
apply<QRgb>(dst, src, count, flags);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Applies the color transformation on \a count QRgba64 pixels starting from
|
||||
\a src and stores the result in \a dst.
|
||||
|
||||
Thread-safe if prepare() has been called first.
|
||||
|
||||
Assumes unpremultiplied data by default. Set \a flags to change defaults.
|
||||
|
||||
\sa prepare()
|
||||
*/
|
||||
void QColorTransformPrivate::apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const
|
||||
{
|
||||
apply<QRgba64>(dst, src, count, flags);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Applies the color transformation on \a count QRgbaFloat32 pixels starting from
|
||||
\a src and stores the result in \a dst.
|
||||
|
||||
Thread-safe if prepare() has been called first.
|
||||
|
||||
Assumes unpremultiplied data by default. Set \a flags to change defaults.
|
||||
|
||||
\sa prepare()
|
||||
*/
|
||||
void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count,
|
||||
TransformFlags flags) const
|
||||
{
|
||||
apply<QRgbaFloat32>(dst, src, count, flags);
|
||||
}
|
||||
|
||||
|
||||
// Only allow versions increasing precision
|
||||
template void QColorTransformPrivate::applyReturnGray<quint8, QRgb>(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::applyReturnGray<quint16, QRgba64>(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::applyGray<quint8, quint8>(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::applyGray<quint16, quint8>(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::applyGray<quint16, quint16>(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::applyGray<QRgb, quint8>(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::applyGray<QRgba64, quint16>(QRgba64 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
|
||||
|
||||
template void QColorTransformPrivate::apply<QRgb, QRgb>(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::apply<QRgba64, QRgb>(QRgba64 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::apply<QRgba64, QRgba64>(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::apply<QRgbaFloat32, QRgb>(QRgbaFloat32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::apply<QRgbaFloat32, QRgba64>(QRgbaFloat32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
|
||||
template void QColorTransformPrivate::apply<QRgbaFloat32, QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const;
|
||||
|
||||
bool QColorTransformPrivate::isThreeComponentMatrix() const
|
||||
{
|
||||
|
@ -51,26 +51,22 @@ public:
|
||||
QColorVector map(QColorVector color) const;
|
||||
QColorVector mapExtended(QColorVector color) const;
|
||||
|
||||
void apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags = Unpremultiplied) const;
|
||||
void apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags = Unpremultiplied) const;
|
||||
void apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count,
|
||||
TransformFlags flags = Unpremultiplied) const;
|
||||
|
||||
template<typename T>
|
||||
void apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
|
||||
|
||||
template<typename T>
|
||||
void applyConvertIn(const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
|
||||
template<typename T>
|
||||
void applyConvertOut(T *dst, const T *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
|
||||
template<typename T>
|
||||
void applyElementListTransform(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
|
||||
template<typename T>
|
||||
void applyThreeComponentMatrix(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
|
||||
|
||||
template<typename D, typename S>
|
||||
void apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
|
||||
template<typename D, typename S>
|
||||
void applyGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
|
||||
template<typename D, typename S>
|
||||
void applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
|
||||
|
||||
private:
|
||||
template<typename S>
|
||||
void applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
|
||||
template<typename D, typename S>
|
||||
void applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
|
||||
template<typename D, typename S>
|
||||
void applyElementListTransform(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
|
||||
template<typename D, typename S>
|
||||
void applyThreeComponentMatrix(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -782,7 +782,7 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
|
||||
colorSpacePrivate->mAB.append(inTableElement);
|
||||
if (!clutElement.isEmpty())
|
||||
colorSpacePrivate->mAB.append(clutElement);
|
||||
if (!outTableIsLinear)
|
||||
if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
|
||||
colorSpacePrivate->mAB.append(outTableElement);
|
||||
} else {
|
||||
// The matrix is only to be applied if the input color-space is XYZ
|
||||
@ -792,7 +792,7 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
|
||||
colorSpacePrivate->mBA.append(inTableElement);
|
||||
if (!clutElement.isEmpty())
|
||||
colorSpacePrivate->mBA.append(clutElement);
|
||||
if (!outTableIsLinear)
|
||||
if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
|
||||
colorSpacePrivate->mBA.append(outTableElement);
|
||||
}
|
||||
return true;
|
||||
@ -964,7 +964,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
|
||||
if (!offsetElement.isNull())
|
||||
colorSpacePrivate->mAB.append(std::move(offsetElement));
|
||||
}
|
||||
if (!bCurvesAreLinear)
|
||||
if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
|
||||
colorSpacePrivate->mAB.append(std::move(bTableElement));
|
||||
} else {
|
||||
if (!bCurvesAreLinear)
|
||||
@ -983,6 +983,8 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
|
||||
if (!aCurvesAreLinear)
|
||||
colorSpacePrivate->mBA.append(std::move(aTableElement));
|
||||
}
|
||||
if (colorSpacePrivate->mBA.isEmpty()) // Ensure non-empty to indicate valid empty transform
|
||||
colorSpacePrivate->mBA.append(std::move(bTableElement));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1269,9 +1271,11 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
|
||||
if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
|
||||
if (!parseRgbMatrix(data, tagIndex, colorspaceDPtr))
|
||||
return false;
|
||||
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
|
||||
} else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
|
||||
if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
|
||||
return false;
|
||||
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
@ -1285,6 +1289,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
|
||||
} else {
|
||||
colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
|
||||
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
|
||||
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
|
||||
|
||||
// Only parse the default perceptual transform for now
|
||||
if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true))
|
||||
@ -1313,6 +1318,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
|
||||
|
||||
colorspaceDPtr->iccProfile = data;
|
||||
|
||||
Q_ASSERT(colorspaceDPtr->isValid());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -169,6 +169,9 @@ private slots:
|
||||
void largeInplaceRgbConversion_data();
|
||||
void largeInplaceRgbConversion();
|
||||
|
||||
void colorSpaceRgbConversion_data();
|
||||
void colorSpaceRgbConversion();
|
||||
|
||||
void deepCopyWhenPaintingActive();
|
||||
void scaled_QTBUG19157();
|
||||
|
||||
@ -3244,6 +3247,81 @@ void tst_QImage::largeInplaceRgbConversion()
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QImage::colorSpaceRgbConversion_data()
|
||||
{
|
||||
QTest::addColumn<QImage::Format>("fromFormat");
|
||||
QTest::addColumn<QImage::Format>("toFormat");
|
||||
|
||||
// The various possible code paths for color space conversions compatible with RGB color spaces:
|
||||
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 fromFormat : formats) {
|
||||
const QLatin1String formatI = formatToString(fromFormat);
|
||||
for (auto toFormat : formats) {
|
||||
QTest::addRow("%s -> %s", formatI.data(), formatToString(toFormat).data())
|
||||
<< fromFormat << toFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QImage::colorSpaceRgbConversion()
|
||||
{
|
||||
// Test that all color space conversions work
|
||||
QFETCH(QImage::Format, fromFormat);
|
||||
QFETCH(QImage::Format, toFormat);
|
||||
|
||||
bool srcGrayscale = fromFormat == QImage::Format_Grayscale8 || fromFormat == QImage::Format_Grayscale16;
|
||||
bool dstGrayscale = toFormat == QImage::Format_Grayscale8 || toFormat == QImage::Format_Grayscale16;
|
||||
|
||||
QImage image(16, 16, fromFormat);
|
||||
image.setColorSpace(QColorSpace::SRgb);
|
||||
|
||||
for (int i = 0; i < image.height(); ++i) {
|
||||
for (int j = 0; j < image.width(); ++j) {
|
||||
if (srcGrayscale || 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::DisplayP3, 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;
|
||||
int blue = 0;
|
||||
for (int x = 0; x < image.width(); ++x) {
|
||||
int newRed = qRed(imageConverted.pixel(x, 5));
|
||||
int newBlue = qBlue(imageConverted.pixel(x, 7));
|
||||
QCOMPARE_GE(newBlue, blue);
|
||||
QCOMPARE_GE(newRed, red);
|
||||
blue = newBlue;
|
||||
red = newRed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void tst_QImage::deepCopyWhenPaintingActive()
|
||||
{
|
||||
QImage image(64, 64, QImage::Format_ARGB32_Premultiplied);
|
||||
|
@ -42,9 +42,12 @@ private slots:
|
||||
void imageConversionOverLargerGamut();
|
||||
void imageConversionOverLargerGamut2_data();
|
||||
void imageConversionOverLargerGamut2();
|
||||
void imageConversionOverAnyGamutFP_data();
|
||||
void imageConversionOverAnyGamutFP();
|
||||
void imageConversionOverAnyGamutFP2_data();
|
||||
void imageConversionOverAnyGamutFP2();
|
||||
void imageConversionOverNonThreeComponentMatrix_data();
|
||||
void imageConversionOverNonThreeComponentMatrix();
|
||||
|
||||
void loadImage();
|
||||
|
||||
void primaries();
|
||||
@ -63,6 +66,11 @@ private slots:
|
||||
void transferFunctionTable();
|
||||
|
||||
void description();
|
||||
void whitePoint_data();
|
||||
void whitePoint();
|
||||
void setWhitePoint();
|
||||
void grayColorSpace();
|
||||
void grayColorSpaceEffectivelySRgb();
|
||||
};
|
||||
|
||||
tst_QColorSpace::tst_QColorSpace()
|
||||
@ -530,6 +538,80 @@ void tst_QColorSpace::imageConversionOverLargerGamut2()
|
||||
QVERIFY(resultImage.pixelColor(0, 255).greenF() > 1.0f);
|
||||
}
|
||||
|
||||
void tst_QColorSpace::imageConversionOverAnyGamutFP_data()
|
||||
{
|
||||
QTest::addColumn<QColorSpace::NamedColorSpace>("fromColorSpace");
|
||||
QTest::addColumn<QColorSpace::NamedColorSpace>("toColorSpace");
|
||||
|
||||
QTest::newRow("sRGB -> Display-P3") << QColorSpace::SRgb << QColorSpace::DisplayP3;
|
||||
QTest::newRow("sRGB -> Adobe RGB") << QColorSpace::SRgb << QColorSpace::AdobeRgb;
|
||||
QTest::newRow("sRGB -> ProPhoto RGB") << QColorSpace::SRgb << QColorSpace::ProPhotoRgb;
|
||||
QTest::newRow("Adobe RGB -> sRGB") << QColorSpace::AdobeRgb << QColorSpace::SRgb;
|
||||
QTest::newRow("Adobe RGB -> Display-P3") << QColorSpace::AdobeRgb << QColorSpace::DisplayP3;
|
||||
QTest::newRow("Adobe RGB -> ProPhoto RGB") << QColorSpace::AdobeRgb << QColorSpace::ProPhotoRgb;
|
||||
QTest::newRow("Display-P3 -> sRGB") << QColorSpace::DisplayP3 << QColorSpace::SRgb;
|
||||
QTest::newRow("Display-P3 -> Adobe RGB") << QColorSpace::DisplayP3 << QColorSpace::AdobeRgb;
|
||||
QTest::newRow("Display-P3 -> ProPhoto RGB") << QColorSpace::DisplayP3 << QColorSpace::ProPhotoRgb;
|
||||
}
|
||||
|
||||
void tst_QColorSpace::imageConversionOverAnyGamutFP()
|
||||
{
|
||||
QFETCH(QColorSpace::NamedColorSpace, fromColorSpace);
|
||||
QFETCH(QColorSpace::NamedColorSpace, toColorSpace);
|
||||
|
||||
QColorSpace csfrom(fromColorSpace);
|
||||
QColorSpace csto(toColorSpace);
|
||||
csfrom.setTransferFunction(QColorSpace::TransferFunction::Linear);
|
||||
csto.setTransferFunction(QColorSpace::TransferFunction::Linear);
|
||||
|
||||
QImage testImage(256, 256, QImage::Format_RGBX32FPx4);
|
||||
testImage.setColorSpace(csfrom);
|
||||
for (int y = 0; y < 256; ++y)
|
||||
for (int x = 0; x < 256; ++x)
|
||||
testImage.setPixel(x, y, qRgb(x, y, 0));
|
||||
|
||||
QImage resultImage = testImage.convertedToColorSpace(csto);
|
||||
resultImage.convertToColorSpace(csfrom);
|
||||
|
||||
for (int y = 0; y < 256; ++y) {
|
||||
for (int x = 0; x < 256; ++x) {
|
||||
QCOMPARE(resultImage.pixel(x, y), testImage.pixel(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QColorSpace::imageConversionOverAnyGamutFP2_data()
|
||||
{
|
||||
imageConversionOverAnyGamutFP_data();
|
||||
}
|
||||
|
||||
void tst_QColorSpace::imageConversionOverAnyGamutFP2()
|
||||
{
|
||||
QFETCH(QColorSpace::NamedColorSpace, fromColorSpace);
|
||||
QFETCH(QColorSpace::NamedColorSpace, toColorSpace);
|
||||
|
||||
// Same as imageConversionOverAnyGamutFP but using format switching transform
|
||||
QColorSpace csfrom(fromColorSpace);
|
||||
QColorSpace csto(toColorSpace);
|
||||
csfrom.setTransferFunction(QColorSpace::TransferFunction::Linear);
|
||||
csto.setTransferFunction(QColorSpace::TransferFunction::Linear);
|
||||
|
||||
QImage testImage(256, 256, QImage::Format_RGB32);
|
||||
testImage.setColorSpace(csfrom);
|
||||
for (int y = 0; y < 256; ++y)
|
||||
for (int x = 0; x < 256; ++x)
|
||||
testImage.setPixel(x, y, qRgb(x, y, 0));
|
||||
|
||||
QImage resultImage = testImage.convertedToColorSpace(csto, QImage::Format_RGBX32FPx4);
|
||||
resultImage.convertToColorSpace(csfrom, QImage::Format_RGB32);
|
||||
|
||||
for (int y = 0; y < 256; ++y) {
|
||||
for (int x = 0; x < 256; ++x) {
|
||||
QCOMPARE(resultImage.pixel(x, y), testImage.pixel(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QColorSpace::imageConversionOverNonThreeComponentMatrix_data()
|
||||
{
|
||||
QTest::addColumn<QColorSpace>("fromColorSpace");
|
||||
@ -569,6 +651,7 @@ void tst_QColorSpace::imageConversionOverNonThreeComponentMatrix()
|
||||
testImage.setPixel(x, y, qRgb(x, y, 0));
|
||||
|
||||
QImage resultImage = testImage.convertedToColorSpace(toColorSpace);
|
||||
QCOMPARE(resultImage.size(), testImage.size());
|
||||
for (int y = 0; y < 256; ++y) {
|
||||
int lastRed = 0;
|
||||
for (int x = 0; x < 256; ++x) {
|
||||
@ -834,5 +917,92 @@ void tst_QColorSpace::description()
|
||||
QCOMPARE(srgb.description(), QLatin1String("Linear sRGB")); // Set to empty returns default behavior
|
||||
}
|
||||
|
||||
void tst_QColorSpace::whitePoint_data()
|
||||
{
|
||||
QTest::addColumn<QColorSpace::NamedColorSpace>("namedColorSpace");
|
||||
QTest::addColumn<QPointF>("whitePoint");
|
||||
|
||||
QTest::newRow("sRGB") << QColorSpace::SRgb << QColorVector::D65Chromaticity();
|
||||
QTest::newRow("Adobe RGB") << QColorSpace::AdobeRgb << QColorVector::D65Chromaticity();
|
||||
QTest::newRow("Display-P3") << QColorSpace::DisplayP3 << QColorVector::D65Chromaticity();
|
||||
QTest::newRow("ProPhoto RGB") << QColorSpace::ProPhotoRgb << QColorVector::D50Chromaticity();
|
||||
}
|
||||
|
||||
void tst_QColorSpace::whitePoint()
|
||||
{
|
||||
QFETCH(QColorSpace::NamedColorSpace, namedColorSpace);
|
||||
QFETCH(QPointF, whitePoint);
|
||||
|
||||
QColorSpace colorSpace(namedColorSpace);
|
||||
QPointF wpt = colorSpace.whitePoint();
|
||||
QCOMPARE_LE(qAbs(wpt.x() - whitePoint.x()), 0.0000001);
|
||||
QCOMPARE_LE(qAbs(wpt.y() - whitePoint.y()), 0.0000001);
|
||||
}
|
||||
|
||||
void tst_QColorSpace::setWhitePoint()
|
||||
{
|
||||
QColorSpace colorSpace(QColorSpace::SRgb);
|
||||
colorSpace.setWhitePoint(QPointF(0.33, 0.33));
|
||||
QCOMPARE_NE(colorSpace, QColorSpace(QColorSpace::SRgb));
|
||||
colorSpace.setWhitePoint(QColorVector::D65Chromaticity());
|
||||
// Check our matrix manipulations returned us to where we came from
|
||||
QCOMPARE(colorSpace, QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
|
||||
void tst_QColorSpace::grayColorSpace()
|
||||
{
|
||||
QColorSpace spc;
|
||||
QCOMPARE(spc.colorModel(), QColorSpace::ColorModel::Undefined);
|
||||
QVERIFY(!spc.isValid());
|
||||
spc.setWhitePoint(QColorVector::D65Chromaticity());
|
||||
spc.setTransferFunction(QColorSpace::TransferFunction::SRgb);
|
||||
QVERIFY(spc.isValid());
|
||||
QCOMPARE(spc.colorModel(), QColorSpace::ColorModel::Gray);
|
||||
|
||||
QColorSpace spc2(QColorVector::D65Chromaticity(), QColorSpace::TransferFunction::SRgb);
|
||||
QVERIFY(spc2.isValid());
|
||||
QCOMPARE(spc2.colorModel(), QColorSpace::ColorModel::Gray);
|
||||
QCOMPARE(spc, spc2);
|
||||
|
||||
QImage rgbImage(1, 8, QImage::Format_RGB32);
|
||||
QImage grayImage(1, 255, QImage::Format_Grayscale8);
|
||||
// RGB images can not have gray color space
|
||||
rgbImage.setColorSpace(spc2);
|
||||
grayImage.setColorSpace(spc2);
|
||||
QCOMPARE_NE(rgbImage.colorSpace(), spc2);
|
||||
QCOMPARE(grayImage.colorSpace(), spc2);
|
||||
// But gray images can have RGB color space
|
||||
rgbImage.setColorSpace(QColorSpace::SRgb);
|
||||
grayImage.setColorSpace(QColorSpace::SRgb);
|
||||
QCOMPARE(rgbImage.colorSpace(), QColorSpace(QColorSpace::SRgb));
|
||||
QCOMPARE(grayImage.colorSpace(), QColorSpace(QColorSpace::SRgb));
|
||||
|
||||
// While we can not set a grayscale color space on rgb image, we can convert to one
|
||||
QImage grayImage2 = rgbImage.convertedToColorSpace(spc2);
|
||||
QCOMPARE(grayImage2.colorSpace(), spc2);
|
||||
QCOMPARE(grayImage2.format(), QImage::Format_Grayscale8);
|
||||
}
|
||||
|
||||
void tst_QColorSpace::grayColorSpaceEffectivelySRgb()
|
||||
{
|
||||
// Test grayscale colorspace conversion by making a gray color space that should act like sRGB on gray values.
|
||||
QColorSpace sRgb(QColorSpace::SRgb);
|
||||
QColorSpace sRgbGray(QColorVector::D65Chromaticity(), QColorSpace::TransferFunction::SRgb);
|
||||
|
||||
QImage grayImage1(256, 1, QImage::Format_Grayscale8);
|
||||
QImage grayImage2(256, 1, QImage::Format_Grayscale8);
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
grayImage1.bits()[i] = i;
|
||||
grayImage2.bits()[i] = i;
|
||||
}
|
||||
grayImage1.setColorSpace(sRgb);
|
||||
grayImage2.setColorSpace(sRgbGray);
|
||||
|
||||
QImage rgbImage1 = grayImage1.convertedTo(QImage::Format_RGB32);
|
||||
QImage rgbImage2 = grayImage2.convertedToColorSpace(sRgb, QImage::Format_RGB32);
|
||||
|
||||
QCOMPARE(rgbImage1, rgbImage2);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QColorSpace)
|
||||
#include "tst_qcolorspace.moc"
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QColorSpace>
|
||||
#include <QImage>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
|
||||
// to reduce noise and increase speed
|
||||
@ -28,7 +29,12 @@ extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
|
||||
Q_UNUSED(b);
|
||||
QRgb color = 0xfaf8fa00;
|
||||
color = trans1.map(color);
|
||||
QImage img(16, 2, cs.colorModel() == QColorSpace::ColorModel::Rgb ? QImage::Format_RGB32 : QImage::Format_Grayscale8);
|
||||
img.setColorSpace(cs);
|
||||
QImage img2 = img.convertedToColorSpace(QColorSpace::SRgb);
|
||||
if (cs.isValidTarget()) {
|
||||
QImage img3 = img2.convertedToColorSpace(cs);
|
||||
|
||||
QColorTransform trans2 = QColorSpace(QColorSpace::SRgb).transformationToColorSpace(cs);
|
||||
bool a = (trans1 == trans2);
|
||||
Q_UNUSED(a);
|
||||
|
Loading…
x
Reference in New Issue
Block a user