Long live QColorSpace and friends

Adds QColorSpace and QColorTransform classes,
and parsing of a common subset of ICC profiles
found in images, and also parses the ICC profiles
in PNG and JPEGs.

For backwards compatibility no automatic color
handling is done by this patch.

[ChangeLog][QtGui] A QColorSpace class has been added,
and color spaces are now parsed from PNG and JPEG images.
No automatic color space conversion is done however, and
applications must request it.

Change-Id: Ic09935f84640a716467fa3a9ed1e73c02daf3675
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2018-06-26 17:12:02 +02:00
parent 17512d497d
commit 90a8de656f
32 changed files with 3863 additions and 92 deletions

View File

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

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
@ -38,8 +38,10 @@
****************************************************************************/
#include "qimage.h"
#include "qdatastream.h"
#include "qbuffer.h"
#include "qdatastream.h"
#include "qcolortransform.h"
#include "qmap.h"
#include "qmatrix.h"
#include "qtransform.h"
@ -54,6 +56,7 @@
#include <stdlib.h>
#include <limits.h>
#include <qpa/qplatformpixmap.h>
#include <private/qcolortransform_p.h>
#include <private/qdrawhelper_p.h>
#include <private/qmemrotate_p.h>
#include <private/qimagescale_p.h>
@ -1098,6 +1101,7 @@ static void copyMetadata(QImageData *dst, const QImageData *src)
dst->dpmy = src->dpmy;
dst->devicePixelRatio = src->devicePixelRatio;
dst->text = src->text;
dst->colorSpace = src->colorSpace;
}
/*!
@ -4920,6 +4924,132 @@ QTransform QImage::trueMatrix(const QTransform &matrix, int w, int h)
return matrix * QTransform().translate(-delta.x(), -delta.y());
}
/*!
\since 5.14
Sets the image color space to \a colorSpace without performing any conversions on image data.
\sa colorSpace()
*/
void QImage::setColorSpace(const QColorSpace &colorSpace)
{
if (!d)
return;
if (d->colorSpace == colorSpace)
return;
if (!isDetached()) // Detach only if shared, not for read-only data.
detach();
d->colorSpace = colorSpace;
}
/*!
\since 5.14
Converts the image to \a colorSpace.
If the image has no valid color space, the method does nothing.
\sa convertedToColorSpace(), setColorSpace()
*/
void QImage::convertToColorSpace(const QColorSpace &colorSpace)
{
if (!d)
return;
if (!d->colorSpace.isValid())
return;
if (!colorSpace.isValid()) {
qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
return;
}
detach();
applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace));
d->colorSpace = colorSpace;
}
/*!
\since 5.14
Returns the image converted to \a colorSpace.
If the image has no valid color space, a null QImage is returned.
\sa convertToColorSpace()
*/
QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const
{
if (!d || !d->colorSpace.isValid() || !colorSpace.isValid())
return QImage();
QImage image = copy();
image.convertToColorSpace(colorSpace);
return image;
}
/*!
\since 5.14
Returns the color space of the image if a color space is defined.
*/
QColorSpace QImage::colorSpace() const
{
if (!d)
return QColorSpace::Undefined;
return d->colorSpace;
}
/*!
\since 5.14
Applies the color transformation \a transform to all pixels in the image.
*/
void QImage::applyColorTransform(const QColorTransform &transform)
{
QImage::Format oldFormat = format();
if (depth() > 32) {
if (format() != QImage::Format_RGBX64 && format() != QImage::Format_RGBA64
&& format() != QImage::Format_RGBA64_Premultiplied)
*this = std::move(*this).convertToFormat(QImage::Format_RGBA64);
} else if (format() != QImage::Format_ARGB32 && format() != QImage::Format_RGB32
&& format() != QImage::Format_ARGB32_Premultiplied) {
if (hasAlphaChannel())
*this = std::move(*this).convertToFormat(QImage::Format_ARGB32);
else
*this = std::move(*this).convertToFormat(QImage::Format_RGB32);
}
QColorTransformPrivate::TransformFlags flags = QColorTransformPrivate::Unpremultiplied;
switch (format()) {
case Format_ARGB32_Premultiplied:
case Format_RGBA64_Premultiplied:
flags = QColorTransformPrivate::Premultiplied;
break;
case Format_RGB32:
case Format_RGBX64:
flags = QColorTransformPrivate::InputOpaque;
break;
case Format_ARGB32:
case Format_RGBA64:
break;
default:
Q_UNREACHABLE();
}
if (depth() > 32) {
for (int i = 0; i < height(); ++i) {
QRgba64 *scanline = reinterpret_cast<QRgba64 *>(scanLine(i));
transform.d_func()->apply(scanline, scanline, width(), flags);
}
} else {
for (int i = 0; i < height(); ++i) {
QRgb *scanline = reinterpret_cast<QRgb *>(scanLine(i));
transform.d_func()->apply(scanline, scanline, width(), flags);
}
}
if (oldFormat != format())
*this = std::move(*this).convertToFormat(oldFormat);
}
bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags)
{
if (format == newFormat)

View File

@ -61,9 +61,11 @@ Q_FORWARD_DECLARE_MUTABLE_CG_TYPE(CGImage);
QT_BEGIN_NAMESPACE
class QColorSpace;
class QColorTransform;
class QIODevice;
class QStringList;
class QMatrix;
class QStringList;
class QTransform;
class QVariant;
template <class T> class QList;
@ -296,6 +298,12 @@ public:
#endif
void invertPixels(InvertMode = InvertRgb);
QColorSpace colorSpace() const;
QImage convertedToColorSpace(const QColorSpace &) const;
void convertToColorSpace(const QColorSpace &);
void setColorSpace(const QColorSpace &);
void applyColorTransform(const QColorTransform &transform);
bool load(QIODevice *device, const char* format);
bool load(const QString &fileName, const char *format = nullptr);

View File

@ -39,7 +39,7 @@
#include <private/qdrawhelper_p.h>
#include <private/qguiapplication_p.h>
#include <private/qcolorprofile_p.h>
#include <private/qcolortrclut_p.h>
#include <private/qendian_p.h>
#include <private/qsimd_p.h>
#include <private/qimage_p.h>
@ -100,7 +100,7 @@ const uchar *qt_get_bitflip_array()
void qGamma_correct_back_to_linear_cs(QImage *image)
{
const QColorProfile *cp = QGuiApplicationPrivate::instance()->colorProfileForA32Text();
const QColorTrcLut *cp = QGuiApplicationPrivate::instance()->colorProfileForA32Text();
if (!cp)
return;
// gamma correct the pixels back to linear color space...

View File

@ -51,7 +51,9 @@
// We mean it.
//
#include <QtGui/qcolorspace.h>
#include <QtGui/private/qtguiglobal_p.h>
#include <QtGui/qimage.h>
#include <QtCore/private/qnumeric_p.h>
#include <QMap>
@ -106,6 +108,8 @@ struct Q_GUI_EXPORT QImageData { // internal image data
QPaintEngine *paintEngine;
QColorSpace colorSpace;
struct ImageSizeParameters {
qsizetype bytesPerLine;
qsizetype totalSize;

View File

@ -42,6 +42,7 @@
#ifndef QT_NO_IMAGEFORMAT_PNG
#include <qcoreapplication.h>
#include <qdebug.h>
#include <qiodevice.h>
#include <qimage.h>
#include <qlist.h>
@ -50,6 +51,10 @@
#include <private/qimage_p.h> // for qt_getImageText
#include <qcolorspace.h>
#include <private/qcolorspace_p.h>
#include <private/qicc_p.h>
#include <png.h>
#include <pngconf.h>
@ -96,9 +101,16 @@ public:
ReadingEnd,
Error
};
// Defines the order of how the various ways of setting colorspace overrides eachother:
enum ColorSpaceState {
Undefined = 0,
GammaChrm = 1, // gAMA+cHRM chunks
Srgb = 2, // sRGB chunk
Icc = 3 // iCCP chunk
};
QPngHandlerPrivate(QPngHandler *qq)
: gamma(0.0), fileGamma(0.0), quality(50), compression(50), png_ptr(0), info_ptr(0), end_info(0), state(Ready), q(qq)
: gamma(0.0), fileGamma(0.0), quality(50), compression(50), colorSpaceState(Undefined), png_ptr(0), info_ptr(0), end_info(0), state(Ready), q(qq)
{ }
float gamma;
@ -108,6 +120,8 @@ public:
QString description;
QSize scaledSize;
QStringList readTexts;
QColorSpace colorSpace;
ColorSpaceState colorSpaceState;
png_struct *png_ptr;
png_info *info_ptr;
@ -226,11 +240,8 @@ void qpiw_flush_fn(png_structp /* png_ptr */)
}
static
void setup_qt(QImage& image, png_structp png_ptr, png_infop info_ptr, QSize scaledSize, bool *doScaledRead, float screen_gamma=0.0, float file_gamma=0.0)
void setup_qt(QImage& image, png_structp png_ptr, png_infop info_ptr, QSize scaledSize, bool *doScaledRead)
{
if (screen_gamma != 0.0 && file_gamma != 0.0)
png_set_gamma(png_ptr, 1.0f / screen_gamma, file_gamma);
png_uint_32 width;
png_uint_32 height;
int bit_depth;
@ -585,10 +596,45 @@ bool QPngHandlerPrivate::readPngHeader()
readPngTexts(info_ptr);
#ifdef PNG_iCCP_SUPPORTED
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
png_charp name = nullptr;
int compressionType = 0;
#if (PNG_LIBPNG_VER < 10500)
png_charp profileData = nullptr;
#else
png_bytep profileData = nullptr;
#endif
png_uint_32 profLen;
png_get_iCCP(png_ptr, info_ptr, &name, &compressionType, &profileData, &profLen);
if (!QIcc::fromIccProfile(QByteArray::fromRawData((const char *)profileData, profLen), &colorSpace)) {
qWarning() << "QPngHandler: Failed to parse ICC profile";
} else {
colorSpaceState = Icc;
}
}
#endif
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
int rendering_intent = -1;
png_get_sRGB(png_ptr, info_ptr, &rendering_intent);
// We don't actually care about the rendering_intent, just that it is valid
if (rendering_intent >= 0 && rendering_intent <= 3 && colorSpaceState <= Srgb) {
colorSpace = QColorSpace::SRgb;
colorSpaceState = Srgb;
}
}
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA)) {
double file_gamma = 0.0;
png_get_gAMA(png_ptr, info_ptr, &file_gamma);
fileGamma = file_gamma;
if (fileGamma > 0.0f && colorSpaceState <= GammaChrm) {
QColorSpacePrivate *csPrivate = colorSpace.d_func();
csPrivate->gamut = QColorSpace::Gamut::SRgb;
csPrivate->transferFunction = QColorSpace::TransferFunction::Gamma;
csPrivate->gamma = fileGamma;
csPrivate->initialize();
colorSpaceState = GammaChrm;
}
}
state = ReadHeader;
@ -613,8 +659,19 @@ bool QPngHandlerPrivate::readPngImage(QImage *outImage)
return false;
}
if (gamma != 0.0 && fileGamma != 0.0) {
// This configuration forces gamma correction and
// thus changes the output colorspace
png_set_gamma(png_ptr, 1.0f / gamma, fileGamma);
QColorSpacePrivate *csPrivate = colorSpace.d_func();
csPrivate->transferFunction = QColorSpace::TransferFunction::Gamma;
csPrivate->gamma = gamma;
csPrivate->initialize();
colorSpaceState = GammaChrm;
}
bool doScaledRead = false;
setup_qt(*outImage, png_ptr, info_ptr, scaledSize, &doScaledRead, gamma, fileGamma);
setup_qt(*outImage, png_ptr, info_ptr, scaledSize, &doScaledRead);
if (outImage->isNull()) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
@ -683,6 +740,9 @@ bool QPngHandlerPrivate::readPngImage(QImage *outImage)
if (scaledSize.isValid() && outImage->size() != scaledSize)
*outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
if (colorSpaceState > Undefined && colorSpace.isValid())
outImage->setColorSpace(colorSpace);
return true;
}

View File

@ -68,7 +68,7 @@
#include <qpalette.h>
#include <qscreen.h>
#include "qsessionmanager.h"
#include <private/qcolorprofile_p.h>
#include <private/qcolortrclut_p.h>
#include <private/qscreen_p.h>
#include <QtGui/qgenericpluginfactory.h>
@ -1643,8 +1643,6 @@ QGuiApplicationPrivate::~QGuiApplicationPrivate()
platform_theme = 0;
delete platform_integration;
platform_integration = 0;
delete m_a8ColorProfile.load();
delete m_a32ColorProfile.load();
window_list.clear();
screen_list.clear();
@ -4019,32 +4017,26 @@ void QGuiApplicationPrivate::notifyDragStarted(const QDrag *drag)
}
#endif
const QColorProfile *QGuiApplicationPrivate::colorProfileForA8Text()
const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA8Text()
{
#ifdef Q_OS_WIN
QColorProfile *result = m_a8ColorProfile.load();
if (!result){
QColorProfile *cs = QColorProfile::fromGamma(2.31); // This is a hard-coded thing for Windows text rendering
if (!m_a8ColorProfile.testAndSetRelease(0, cs))
delete cs;
result = m_a8ColorProfile.load();
if (!m_a8ColorProfile){
QColorTrcLut *cs = QColorTrcLut::fromGamma(2.31); // This is a hard-coded thing for Windows text rendering
m_a8ColorProfile.reset(cs);
}
return result;
return m_a8ColorProfile.get();
#else
return colorProfileForA32Text();
#endif
}
const QColorProfile *QGuiApplicationPrivate::colorProfileForA32Text()
const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA32Text()
{
QColorProfile *result = m_a32ColorProfile.load();
if (!result){
QColorProfile *cs = QColorProfile::fromGamma(fontSmoothingGamma);
if (!m_a32ColorProfile.testAndSetRelease(0, cs))
delete cs;
result = m_a32ColorProfile.load();
if (!m_a32ColorProfile) {
QColorTrcLut *cs = QColorTrcLut::fromGamma(fontSmoothingGamma);
m_a32ColorProfile.reset(cs);
}
return result;
return m_a32ColorProfile.get();
}
void QGuiApplicationPrivate::_q_updateFocusObject(QObject *object)

View File

@ -55,6 +55,7 @@
#include <QtGui/qguiapplication.h>
#include <QtCore/QPointF>
#include <QtCore/QSharedPointer>
#include <QtCore/private/qcoreapplication_p.h>
#include <QtCore/private/qthread_p.h>
@ -66,7 +67,7 @@
QT_BEGIN_NAMESPACE
class QColorProfile;
class QColorTrcLut;
class QPlatformIntegration;
class QPlatformTheme;
class QPlatformDragQtResponse;
@ -299,8 +300,8 @@ public:
static QInputDeviceManager *inputDeviceManager();
const QColorProfile *colorProfileForA8Text();
const QColorProfile *colorProfileForA32Text();
const QColorTrcLut *colorProfileForA8Text();
const QColorTrcLut *colorProfileForA32Text();
// hook reimplemented in QApplication to apply the QStyle function on the QIcon
virtual QPixmap applyQIconStyleHelper(QIcon::Mode, const QPixmap &basePixmap) const { return basePixmap; }
@ -327,8 +328,8 @@ private:
static QGuiApplicationPrivate *self;
static QTouchDevice *m_fakeTouchDevice;
static int m_fakeMouseSourcePointId;
QAtomicPointer<QColorProfile> m_a8ColorProfile;
QAtomicPointer<QColorProfile> m_a32ColorProfile;
QSharedPointer<QColorTrcLut> m_a8ColorProfile;
QSharedPointer<QColorTrcLut> m_a32ColorProfile;
bool ownGlobalShareContext;

View File

@ -8,7 +8,15 @@ HEADERS += \
painting/qbrush.h \
painting/qcolor.h \
painting/qcolor_p.h \
painting/qcolorprofile_p.h \
painting/qcolormatrix_p.h \
painting/qcolorspace.h \
painting/qcolorspace_p.h \
painting/qcolortransferfunction_p.h \
painting/qcolortransfertable_p.h \
painting/qcolortransform.h \
painting/qcolortransform_p.h \
painting/qcolortrc_p.h \
painting/qcolortrclut_p.h \
painting/qcosmeticstroker_p.h \
painting/qdatabuffer_p.h \
painting/qdrawhelper_p.h \
@ -17,6 +25,7 @@ HEADERS += \
painting/qemulationpaintengine_p.h \
painting/qfixed_p.h \
painting/qgrayraster_p.h \
painting/qicc_p.h \
painting/qmatrix.h \
painting/qmemrotate_p.h \
painting/qoutlinemapper_p.h \
@ -64,12 +73,15 @@ SOURCES += \
painting/qblittable.cpp \
painting/qbrush.cpp \
painting/qcolor.cpp \
painting/qcolorprofile.cpp \
painting/qcolorspace.cpp \
painting/qcolortransform.cpp \
painting/qcolortrclut.cpp \
painting/qcompositionfunctions.cpp \
painting/qcosmeticstroker.cpp \
painting/qdrawhelper.cpp \
painting/qemulationpaintengine.cpp \
painting/qgrayraster.c \
painting/qicc.cpp \
painting/qimagescale.cpp \
painting/qmatrix.cpp \
painting/qmemrotate.cpp \

View File

@ -0,0 +1,214 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORMATRIX_H
#define QCOLORMATRIX_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtGui/qtguiglobal.h>
#include <cmath>
QT_BEGIN_NAMESPACE
// An abstract 3 value color
class QColorVector
{
public:
QColorVector() = default;
constexpr QColorVector(float x, float y, float z) : x(x), y(y), z(z), _unused(0.0f) { }
float x; // X, x or red
float y; // Y, y or green
float z; // Z, Y or blue
float _unused;
friend inline bool operator==(const QColorVector &v1, const QColorVector &v2);
friend inline bool operator!=(const QColorVector &v1, const QColorVector &v2);
static constexpr QColorVector null() { return QColorVector(0.0f, 0.0f, 0.0f); }
// Common whitepoints on normalized XYZ form:
static constexpr QColorVector D50() { return QColorVector(0.96421f, 1.0f, 0.82519f); }
static constexpr QColorVector D65() { return QColorVector(0.95043f, 1.0f, 1.08890f); }
};
inline bool operator==(const QColorVector &v1, const QColorVector &v2)
{
return (std::abs(v1.x - v2.x) < (1.0f / 2048.0f))
&& (std::abs(v1.y - v2.y) < (1.0f / 2048.0f))
&& (std::abs(v1.z - v2.z) < (1.0f / 2048.0f));
}
inline bool operator!=(const QColorVector &v1, const QColorVector &v2)
{
return !(v1 == v2);
}
// A matrix mapping 3 value colors.
// Not using QMatrix because only floats are needed and performance is critical.
class QColorMatrix
{
public:
// We are storing the matrix transposed as that is more convenient:
QColorVector r;
QColorVector g;
QColorVector b;
friend inline bool operator==(const QColorMatrix &m1, const QColorMatrix &m2);
friend inline bool operator!=(const QColorMatrix &m1, const QColorMatrix &m2);
bool isValid() const
{
// A color matrix must be invertible
float det = r.x * (b.z * g.y - g.z * b.y) -
r.y * (b.z * g.x - g.z * b.x) +
r.z * (b.y * g.x - g.y * b.x);
return !qFuzzyIsNull(det);
}
QColorMatrix inverted() const
{
float det = r.x * (b.z * g.y - g.z * b.y) -
r.y * (b.z * g.x - g.z * b.x) +
r.z * (b.y * g.x - g.y * b.x);
det = 1.0f / det;
QColorMatrix inv;
inv.r.x = (g.y * b.z - b.y * g.z) * det;
inv.r.y = (b.y * r.z - r.y * b.z) * det;
inv.r.z = (r.y * g.z - g.y * r.z) * det;
inv.g.x = (b.x * g.z - g.x * b.z) * det;
inv.g.y = (r.x * b.z - b.x * r.z) * det;
inv.g.z = (g.x * r.z - r.x * g.z) * det;
inv.b.x = (g.x * b.y - b.x * g.y) * det;
inv.b.y = (b.x * r.y - r.x * b.y) * det;
inv.b.z = (r.x * g.y - g.x * r.y) * det;
return inv;
}
QColorMatrix operator*(const QColorMatrix &o) const
{
QColorMatrix comb;
comb.r.x = r.x * o.r.x + g.x * o.r.y + b.x * o.r.z;
comb.g.x = r.x * o.g.x + g.x * o.g.y + b.x * o.g.z;
comb.b.x = r.x * o.b.x + g.x * o.b.y + b.x * o.b.z;
comb.r.y = r.y * o.r.x + g.y * o.r.y + b.y * o.r.z;
comb.g.y = r.y * o.g.x + g.y * o.g.y + b.y * o.g.z;
comb.b.y = r.y * o.b.x + g.y * o.b.y + b.y * o.b.z;
comb.r.z = r.z * o.r.x + g.z * o.r.y + b.z * o.r.z;
comb.g.z = r.z * o.g.x + g.z * o.g.y + b.z * o.g.z;
comb.b.z = r.z * o.b.x + g.z * o.b.y + b.z * o.b.z;
return comb;
}
QColorVector map(const QColorVector &c) const
{
return QColorVector { c.x * r.x + c.y * g.x + c.z * b.x,
c.x * r.y + c.y * g.y + c.z * b.y,
c.x * r.z + c.y * g.z + c.z * b.z };
}
QColorMatrix transposed() const
{
return QColorMatrix { { r.x, g.x, b.x },
{ r.y, g.y, b.y },
{ r.z, g.z, b.z } };
}
static QColorMatrix null()
{
return { QColorVector::null(), QColorVector::null(), QColorVector::null() };
}
static QColorMatrix identity()
{
return { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } };
}
static QColorMatrix toXyzFromSRgb()
{
return QColorMatrix { { 0.4360217452f, 0.2224751115f, 0.0139281144f },
{ 0.3851087987f, 0.7169067264f, 0.0971015394f },
{ 0.1430812478f, 0.0606181994f, 0.7141585946f } };
}
static QColorMatrix toXyzFromAdobeRgb()
{
return QColorMatrix { { 0.6097189188f, 0.3111021519f, 0.0194766335f },
{ 0.2052682191f, 0.6256770492f, 0.0608891509f },
{ 0.1492247432f, 0.0632209629f, 0.7448224425f } };
}
static QColorMatrix toXyzFromDciP3D65()
{
return QColorMatrix { { 0.5150973201f, 0.2411795557f, -0.0010491034f },
{ 0.2919696569f, 0.6922441125f, 0.0418830328f },
{ 0.1571449190f, 0.0665764511f, 0.7843542695f } };
}
static QColorMatrix toXyzFromProPhotoRgb()
{
return QColorMatrix { { 0.7976672649f, 0.2880374491f, 0.0000000000f },
{ 0.1351922452f, 0.7118769884f, 0.0000000000f },
{ 0.0313525312f, 0.0000856627f, 0.8251883388f } };
}
static QColorMatrix toXyzFromBt2020()
{
return QColorMatrix { { 0.6506130099f, 0.2695676684f, -0.0018652577f },
{ 0.1865101457f, 0.6840794086f, 0.0172256753f },
{ 0.1270887405f, 0.0463530831f, 0.8098278046f } };
}
};
inline bool operator==(const QColorMatrix &m1, const QColorMatrix &m2)
{
return (m1.r == m2.r) && (m1.g == m2.g) && (m1.b == m2.b);
}
inline bool operator!=(const QColorMatrix &m1, const QColorMatrix &m2)
{
return !(m1 == m2);
}
QT_END_NAMESPACE
#endif // QCOLORMATRIX_P_H

View File

@ -0,0 +1,633 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qcolorspace.h"
#include "qcolorspace_p.h"
#include "qcolortransform.h"
#include "qcolormatrix_p.h"
#include "qcolortransferfunction_p.h"
#include "qcolortransform_p.h"
#include "qicc_p.h"
#include <qmath.h>
#include <qtransform.h>
#include <qdebug.h>
QT_BEGIN_NAMESPACE
QColorSpacePrivate::QColorSpacePrivate()
: id(QColorSpace::Unknown)
, gamut(QColorSpace::Gamut::Custom)
, transferFunction(QColorSpace::TransferFunction::Custom)
, gamma(0.0f)
, whitePoint(QColorVector::null())
, toXyz(QColorMatrix::null())
{
}
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::ColorSpaceId colorSpaceId)
: id(colorSpaceId)
{
switch (colorSpaceId) {
case QColorSpace::Undefined:
gamut = QColorSpace::Gamut::Custom;
transferFunction = QColorSpace::TransferFunction::Custom;
gamma = 0.0f;
description = QStringLiteral("Undefined");
break;
case QColorSpace::SRgb:
gamut = QColorSpace::Gamut::SRgb;
transferFunction = QColorSpace::TransferFunction::SRgb;
gamma = 2.31f; // ?
description = QStringLiteral("sRGB");
break;
case QColorSpace::SRgbLinear:
gamut = QColorSpace::Gamut::SRgb;
transferFunction = QColorSpace::TransferFunction::Linear;
gamma = 1.0f;
description = QStringLiteral("Linear sRGB");
break;
case QColorSpace::AdobeRgb:
gamut = QColorSpace::Gamut::AdobeRgb;
transferFunction = QColorSpace::TransferFunction::Gamma;
gamma = 2.19921875f; // Not quite 2.2, see https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
description = QStringLiteral("Adobe RGB");
break;
case QColorSpace::DisplayP3:
gamut = QColorSpace::Gamut::DciP3D65;
transferFunction = QColorSpace::TransferFunction::SRgb;
gamma = 2.31f; // ?
description = QStringLiteral("Display P3");
break;
case QColorSpace::ProPhotoRgb:
gamut = QColorSpace::Gamut::ProPhotoRgb;
transferFunction = QColorSpace::TransferFunction::ProPhotoRgb;
gamma = 1.8f;
description = QStringLiteral("ProPhoto RGB");
break;
case QColorSpace::Bt2020:
gamut = QColorSpace::Gamut::Bt2020;
transferFunction = QColorSpace::TransferFunction::Bt2020;
gamma = 2.1f; // ?
description = QStringLiteral("BT.2020");
break;
case QColorSpace::Unknown:
qWarning("Can not create an unknown color space");
Q_FALLTHROUGH();
default:
Q_UNREACHABLE();
}
initialize();
}
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Gamut gamut, QColorSpace::TransferFunction fun, float gamma)
: gamut(gamut)
, transferFunction(fun)
, gamma(gamma)
{
if (!identifyColorSpace())
id = QColorSpace::Unknown;
initialize();
}
bool QColorSpacePrivate::identifyColorSpace()
{
switch (gamut) {
case QColorSpace::Gamut::SRgb:
if (transferFunction == QColorSpace::TransferFunction::SRgb) {
id = QColorSpace::SRgb;
description = QStringLiteral("sRGB");
return true;
}
if (transferFunction == QColorSpace::TransferFunction::Linear) {
id = QColorSpace::SRgbLinear;
description = QStringLiteral("Linear sRGB");
return true;
}
break;
case QColorSpace::Gamut::AdobeRgb:
if (transferFunction == QColorSpace::TransferFunction::Gamma) {
if (qAbs(gamma - 2.19921875f) < (1/1024.0f)) {
id = QColorSpace::AdobeRgb;
description = QStringLiteral("Adobe RGB");
return true;
}
}
break;
case QColorSpace::Gamut::DciP3D65:
if (transferFunction == QColorSpace::TransferFunction::SRgb) {
id = QColorSpace::DisplayP3;
description = QStringLiteral("Display P3");
return true;
}
break;
case QColorSpace::Gamut::ProPhotoRgb:
if (transferFunction == QColorSpace::TransferFunction::ProPhotoRgb) {
id = QColorSpace::ProPhotoRgb;
description = QStringLiteral("ProPhoto RGB");
return true;
}
if (transferFunction == QColorSpace::TransferFunction::Gamma) {
// ProPhoto RGB's curve is effectively gamma 1.8 for 8bit precision.
if (qAbs(gamma - 1.8f) < (1/1024.0f)) {
id = QColorSpace::ProPhotoRgb;
description = QStringLiteral("ProPhoto RGB");
return true;
}
}
break;
case QColorSpace::Gamut::Bt2020:
if (transferFunction == QColorSpace::TransferFunction::Bt2020) {
id = QColorSpace::Bt2020;
description = QStringLiteral("BT.2020");
return true;
}
break;
default:
break;
}
return false;
}
void QColorSpacePrivate::initialize()
{
setToXyzMatrix();
setTransferFunction();
}
void QColorSpacePrivate::setToXyzMatrix()
{
switch (gamut) {
case QColorSpace::Gamut::SRgb:
toXyz = QColorMatrix::toXyzFromSRgb();
whitePoint = QColorVector::D65();
return;
case QColorSpace::Gamut::AdobeRgb:
toXyz = QColorMatrix::toXyzFromAdobeRgb();
whitePoint = QColorVector::D65();
return;
case QColorSpace::Gamut::DciP3D65:
toXyz = QColorMatrix::toXyzFromDciP3D65();
whitePoint = QColorVector::D65();
return;
case QColorSpace::Gamut::ProPhotoRgb:
toXyz = QColorMatrix::toXyzFromProPhotoRgb();
whitePoint = QColorVector::D50();
return;
case QColorSpace::Gamut::Bt2020:
toXyz = QColorMatrix::toXyzFromBt2020();
whitePoint = QColorVector::D65();
return;
case QColorSpace::Gamut::Custom:
toXyz = QColorMatrix::null();
whitePoint = QColorVector::D50();
return;
}
Q_UNREACHABLE();
}
void QColorSpacePrivate::setTransferFunction()
{
switch (transferFunction) {
case QColorSpace::TransferFunction::Linear:
trc[0].m_type = QColorTrc::Type::Function;
trc[0].m_fun = QColorTransferFunction();
break;
case QColorSpace::TransferFunction::Gamma:
trc[0].m_type = QColorTrc::Type::Function;
trc[0].m_fun = QColorTransferFunction::fromGamma(gamma);
break;
case QColorSpace::TransferFunction::SRgb:
trc[0].m_type = QColorTrc::Type::Function;
trc[0].m_fun = QColorTransferFunction::fromSRgb();
break;
case QColorSpace::TransferFunction::ProPhotoRgb:
trc[0].m_type = QColorTrc::Type::Function;
trc[0].m_fun = QColorTransferFunction::fromProPhotoRgb();
break;
case QColorSpace::TransferFunction::Bt2020:
trc[0].m_type = QColorTrc::Type::Function;
trc[0].m_fun = QColorTransferFunction::fromBt2020();
break;
case QColorSpace::TransferFunction::Custom:
break;
default:
Q_UNREACHABLE();
break;
}
trc[1] = trc[0];
trc[2] = trc[0];
}
QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpacePrivate *out) const
{
Q_ASSERT(out);
QColorTransform combined;
combined.d_ptr.reset(new QColorTransformPrivate);
combined.d_ptr->colorSpaceIn = this;
combined.d_ptr->colorSpaceOut = out;
combined.d_ptr->colorMatrix = out->toXyz.inverted() * toXyz;
return combined;
}
/*!
\class QColorSpace
\brief The QColorSpace class provides a color space abstraction.
\since 5.14
\ingroup painting
\ingroup appearance
\inmodule QtGui
Color values can be interpreted in different ways, and based on the interpretation
can live in different spaces. We call this \e {color spaces}.
QColorSpace provides access to creating several predefined color spaces and
can generate QColorTransforms for converting colors from one color space to
another.
QColorSpace can also represent color spaces defined by ICC profiles or embedded
in images, that do not otherwise fit the predefined color spaces.
A color space can generally speaking be conceived as a combination of a transfer
function and a gamut. The gamut defines which colors the color space can represent.
A color space that can represent a wider range of colors is also known as a
wide-gamut color space. The gamut is 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 transfer function or gamma curve determines how each component in the
color space is encoded. These are used because human perception does not operate
linearly, and the transfer functions try to ensure that colors will seem evenly
spaced to human eyes.
*/
/*!
\enum QColorSpace::ColorSpaceId
Predefined color spaces.
\value Undefined An empty, invalid or unsupported color space.
\value Unknown A valid color space that doesn't match any of the predefined color spaces.
\value SRgb The sRGB color space, which Qt operates in by default. It is a close approximation
of how most classic monitors operate, and a mode most software and hardware support.
\l{http://www.color.org/chardata/rgb/srgb.xalter}{ICC registration of sRGB}.
\value SRgbLinear The sRGB color space with linear gamma. Useful for gamma-corrected blending.
\value AdobeRgb The Adobe RGB color space is a classic wide-gamut color space, using a gamma of 2.2.
\l{http://www.color.org/chardata/rgb/adobergb.xalter}{ICC registration of Adobe RGB (1998)}
\value DisplayP3 A color-space using the primaries of DCI-P3, but with the whitepoint and transfer
function of sRGB. Common in modern wide-gamut screens.
\l{http://www.color.org/chardata/rgb/DCIP3.xalter}{ICC registration of DCI-P3}
\value ProPhotoRgb The Pro Photo RGB color space, also known as ROMM RGB is a very wide gamut color space.
\l{http://www.color.org/chardata/rgb/rommrgb.xalter}{ICC registration of ROMM RGB}
\value Bt2020 BT.2020 also known as Rec.2020 is the color space of HDR TVs.
\l{http://www.color.org/chardata/rgb/BT2020.xalter}{ICC registration of BT.2020}
*/
/*!
\enum QColorSpace::Gamut
Predefined gamuts, or sets of primary colors.
\value Custom The gamut is undefined or does not match any predefined sets.
\value SRgb The sRGB gamut
\value AdobeRgb The Adobe RGB gamut
\value DciP3D65 The DCI-P3 gamut with the D65 whitepoint
\value ProPhotoRgb The ProPhoto RGB gamut with the D50 whitepoint
\value Bt2020 The BT.2020 gamut
*/
/*!
\enum QColorSpace::TransferFunction
Predefined transfer functions or gamma curves.
\value Custom The custom or null transfer function
\value Linear The linear transfer functions
\value Gamma A transfer function that is a real gamma curve based on the value of gamma()
\value SRgb The sRGB transfer function, composed of linear and gamma parts
\value ProPhotoRgb The ProPhoto RGB transfer function, composed of linear and gamma parts
\value Bt2020 The BT.2020 transfer function, composed of linear and gamma parts
*/
/*!
Creates a new colorspace object that represents \a colorSpaceId.
*/
QColorSpace::QColorSpace(QColorSpace::ColorSpaceId colorSpaceId)
{
static QExplicitlySharedDataPointer<QColorSpacePrivate> predefinedColorspacePrivates[QColorSpace::Bt2020];
if (colorSpaceId <= QColorSpace::Unknown) {
if (!predefinedColorspacePrivates[0])
predefinedColorspacePrivates[0] = new QColorSpacePrivate(QColorSpace::Undefined);
d_ptr = predefinedColorspacePrivates[0]; // unknown and undefined both returns the static undefined colorspace.
} else {
if (!predefinedColorspacePrivates[colorSpaceId - 1])
predefinedColorspacePrivates[colorSpaceId - 1] = new QColorSpacePrivate(colorSpaceId);
d_ptr = predefinedColorspacePrivates[colorSpaceId - 1];
}
Q_ASSERT(colorSpaceId == QColorSpace::Undefined || isValid());
}
/*!
Creates a custom color space with the gamut \a gamut, using the transfer function \a fun and
optionally \a gamma.
*/
QColorSpace::QColorSpace(QColorSpace::Gamut gamut, QColorSpace::TransferFunction fun, float gamma)
: d_ptr(new QColorSpacePrivate(gamut, fun, gamma))
{
}
/*!
Creates a custom color space with the gamut \a gamut, using a gamma transfer function of
\a gamma.
*/
QColorSpace::QColorSpace(QColorSpace::Gamut gamut, float gamma)
: d_ptr(new QColorSpacePrivate(gamut, TransferFunction::Gamma, gamma))
{
}
QColorSpace::~QColorSpace()
{
}
QColorSpace::QColorSpace(QColorSpace &&colorSpace)
: d_ptr(std::move(colorSpace.d_ptr))
{
}
QColorSpace::QColorSpace(const QColorSpace &colorSpace)
: d_ptr(colorSpace.d_ptr)
{
}
QColorSpace &QColorSpace::operator=(QColorSpace &&colorSpace)
{
d_ptr = std::move(colorSpace.d_ptr);
return *this;
}
QColorSpace &QColorSpace::operator=(const QColorSpace &colorSpace)
{
d_ptr = colorSpace.d_ptr;
return *this;
}
/*!
Returns the id of the predefined color space this object
represents or \c Unknown if it doesn't match any of them.
*/
QColorSpace::ColorSpaceId QColorSpace::colorSpaceId() const noexcept
{
return d_ptr->id;
}
/*!
Returns the predefined gamut of the color space
or \c Gamut::Custom if it doesn't match any of them.
*/
QColorSpace::Gamut QColorSpace::gamut() const noexcept
{
return d_ptr->gamut;
}
/*!
Returns the predefined transfer function of the color space
or \c TransferFunction::Custom if it doesn't match any of them.
\sa gamma()
*/
QColorSpace::TransferFunction QColorSpace::transferFunction() const noexcept
{
return d_ptr->transferFunction;
}
/*!
Returns the gamma value of color spaces with \c TransferFunction::Gamma,
an approximate gamma value for other predefined color spaces, or
0.0 if no approximate gamma is known.
\sa transferFunction()
*/
float QColorSpace::gamma() const noexcept
{
return d_ptr->gamma;
}
/*!
Returns an ICC profile representing the color space.
If the color space was generated from an ICC profile, that profile
is returned, otherwise one is generated.
\note Even invalid color spaces may return the ICC profile if they
were generated from one, to allow applications to implement wider
support themselves.
\sa fromIccProfile()
*/
QByteArray QColorSpace::iccProfile() const
{
if (!d_ptr->iccProfile.isEmpty())
return d_ptr->iccProfile;
if (!isValid())
return QByteArray();
return QIcc::toIccProfile(*this);
}
/*!
Creates a QColorSpace from ICC profile \a iccProfile.
\note Not all ICC profiles are supported. QColorSpace only supports
RGB-XYZ ICC profiles that are three-component matrix-based.
If the ICC profile is not supported an invalid QColorSpace is returned
where you can still read the original ICC profile using iccProfile().
\sa iccProfile()
*/
QColorSpace QColorSpace::fromIccProfile(const QByteArray &iccProfile)
{
QColorSpace colorSpace;
if (QIcc::fromIccProfile(iccProfile, &colorSpace))
return colorSpace;
colorSpace.d_ptr->id = QColorSpace::Undefined;
colorSpace.d_ptr->iccProfile = iccProfile;
return colorSpace;
}
/*!
Returns \c true if the color space is valid.
*/
bool QColorSpace::isValid() const noexcept
{
return d_ptr->id != QColorSpace::Undefined && d_ptr->toXyz.isValid()
&& d_ptr->trc[0].isValid() && d_ptr->trc[1].isValid() && d_ptr->trc[2].isValid();
}
/*!
\relates QColorSpace
Returns \c true if colorspace \a colorSpace1 is equal to colorspace \a colorSpace2;
otherwise returns \c false
*/
bool operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2)
{
if (colorSpace1.d_ptr == colorSpace2.d_ptr)
return true;
if (colorSpace1.colorSpaceId() == QColorSpace::Undefined && colorSpace2.colorSpaceId() == QColorSpace::Undefined)
return colorSpace1.d_ptr->iccProfile == colorSpace2.d_ptr->iccProfile;
if (colorSpace1.colorSpaceId() != QColorSpace::Unknown && colorSpace2.colorSpaceId() != QColorSpace::Unknown)
return colorSpace1.colorSpaceId() == colorSpace2.colorSpaceId();
if (colorSpace1.gamut() != QColorSpace::Gamut::Custom && colorSpace2.gamut() != QColorSpace::Gamut::Custom) {
if (colorSpace1.gamut() != colorSpace2.gamut())
return false;
} else {
if (colorSpace1.d_ptr->toXyz != colorSpace2.d_ptr->toXyz)
return false;
}
if (colorSpace1.transferFunction() != QColorSpace::TransferFunction::Custom &&
colorSpace2.transferFunction() != QColorSpace::TransferFunction::Custom) {
if (colorSpace1.transferFunction() != colorSpace2.transferFunction())
return false;
if (colorSpace1.transferFunction() == QColorSpace::TransferFunction::Gamma)
return colorSpace1.gamma() == colorSpace2.gamma();
return true;
}
if (colorSpace1.d_ptr->trc[0] != colorSpace2.d_ptr->trc[0] ||
colorSpace1.d_ptr->trc[1] != colorSpace2.d_ptr->trc[1] ||
colorSpace1.d_ptr->trc[2] != colorSpace2.d_ptr->trc[2])
return false;
return true;
}
/*!
\fn bool operator!=(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2)
\relates QColorSpace
Returns \c true if colorspace \a colorspace1 is not equal to colorspace \a colorspace2;
otherwise returns \c false
*/
/*!
Generates and returns a color space transformation from this color space to
\a colorspace.
*/
QColorTransform QColorSpace::transformationToColorSpace(const QColorSpace &colorspace) const
{
if (!isValid() || !colorspace.isValid())
return QColorTransform();
return d_ptr->transformationToColorSpace(colorspace.d_ptr.constData());
}
/*!
\internal
*/
QColorSpacePrivate *QColorSpace::d_func()
{
d_ptr.detach();
return d_ptr.data();
}
/*!
\fn const QColorSpacePrivate* QColorSpacePrivate::d_func() const
\internal
*/
/*****************************************************************************
QColorSpace stream functions
*****************************************************************************/
#if !defined(QT_NO_DATASTREAM)
/*!
\fn QDataStream &operator<<(QDataStream &stream, const QColorSpace &colorSpace)
\relates QColorSpace
Writes the given \a colorSpace to the given \a stream as an ICC profile.
\sa QColorSpace::iccProfile(), {Serializing Qt Data Types}
*/
QDataStream &operator<<(QDataStream &s, const QColorSpace &image)
{
s << image.iccProfile();
return s;
}
/*!
\fn QDataStream &operator>>(QDataStream &stream, QColorSpace &colorSpace)
\relates QColorSpace
Reads a color space from the given \a stream and stores it in the given
\a colorSpace.
\sa QColorSpace::fromIccProfile(), {Serializing Qt Data Types}
*/
QDataStream &operator>>(QDataStream &s, QColorSpace &colorSpace)
{
QByteArray iccProfile;
s >> iccProfile;
colorSpace = QColorSpace::fromIccProfile(iccProfile);
return s;
}
#endif // QT_NO_DATASTREAM
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace)
{
QDebugStateSaver saver(dbg);
dbg.nospace();
dbg << "QColorSpace(";
dbg << colorSpace.colorSpaceId() << ", " << colorSpace.gamut() << ", " << colorSpace.transferFunction();
dbg << ", gamma=" << colorSpace.gamma();
dbg << ')';
return dbg;
}
#endif
QT_END_NAMESPACE

View File

@ -0,0 +1,136 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORSPACE_H
#define QCOLORSPACE_H
#include <QtGui/qtguiglobal.h>
#include <QtGui/qcolortransform.h>
#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
class QColorSpacePrivate;
class Q_GUI_EXPORT QColorSpace
{
Q_GADGET
public:
enum ColorSpaceId {
Undefined = 0,
Unknown = 1,
SRgb,
SRgbLinear,
AdobeRgb,
DisplayP3,
ProPhotoRgb,
Bt2020,
};
Q_ENUM(ColorSpaceId)
enum class Gamut {
Custom = 0,
SRgb,
AdobeRgb,
DciP3D65,
ProPhotoRgb,
Bt2020,
};
Q_ENUM(Gamut)
enum class TransferFunction {
Custom = 0,
Linear,
Gamma,
SRgb,
ProPhotoRgb,
Bt2020,
};
Q_ENUM(TransferFunction)
QColorSpace(ColorSpaceId colorSpaceId = Undefined);
QColorSpace(Gamut gamut, TransferFunction fun, float gamma = 0.0f);
QColorSpace(Gamut gamut, float gamma);
~QColorSpace();
QColorSpace(QColorSpace &&colorSpace);
QColorSpace(const QColorSpace &colorSpace);
QColorSpace &operator=(QColorSpace &&colorSpace);
QColorSpace &operator=(const QColorSpace &colorSpace);
ColorSpaceId colorSpaceId() const noexcept;
Gamut gamut() const noexcept;
TransferFunction transferFunction() const noexcept;
float gamma() const noexcept;
bool isValid() const noexcept;
friend Q_GUI_EXPORT bool operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2);
friend inline bool operator!=(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2);
static QColorSpace fromIccProfile(const QByteArray &iccProfile);
QByteArray iccProfile() const;
QColorTransform transformationToColorSpace(const QColorSpace &colorspace) const;
QColorSpacePrivate *d_func();
inline const QColorSpacePrivate *d_func() const { return d_ptr.constData(); }
private:
friend class QColorSpacePrivate;
QExplicitlySharedDataPointer<QColorSpacePrivate> d_ptr;
};
bool Q_GUI_EXPORT operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2);
inline bool operator!=(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2)
{
return !(colorSpace1 == colorSpace2);
}
// QColorSpace stream functions
#if !defined(QT_NO_DATASTREAM)
Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QColorSpace &);
Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QColorSpace &);
#endif
#ifndef QT_NO_DEBUG_STREAM
Q_GUI_EXPORT QDebug operator<<(QDebug, const QColorSpace &);
#endif
QT_END_NAMESPACE
#endif // QCOLORSPACE_P_H

View File

@ -0,0 +1,96 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORSPACE_P_H
#define QCOLORSPACE_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qcolorspace.h"
#include "qcolormatrix_p.h"
#include "qcolortrc_p.h"
#include "qcolortrclut_p.h"
#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
class QColorSpacePrivate : public QSharedData
{
public:
QColorSpacePrivate();
QColorSpacePrivate(QColorSpace::ColorSpaceId colorSpaceId);
QColorSpacePrivate(QColorSpace::Gamut gamut, QColorSpace::TransferFunction fun, float gamma);
QColorSpacePrivate(const QColorSpacePrivate &other) = default;
QColorSpacePrivate &operator=(const QColorSpacePrivate &other) = default;
void initialize();
void setToXyzMatrix();
void setTransferFunction();
bool identifyColorSpace();
QColorTransform transformationToColorSpace(const QColorSpacePrivate *out) const;
QColorSpace::ColorSpaceId id;
QColorSpace::Gamut gamut;
QColorSpace::TransferFunction transferFunction;
float gamma;
QColorVector whitePoint;
QColorTrc trc[3];
QColorMatrix toXyz;
QString description;
QByteArray iccProfile;
mutable QSharedPointer<QColorTrcLut> lut[3];
mutable QAtomicInt lutsGenerated;
};
QT_END_NAMESPACE
#endif // QCOLORSPACE_P_H

View File

@ -0,0 +1,207 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORTRANSFERFUNCTION_P_H
#define QCOLORTRANSFERFUNCTION_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtGui/private/qtguiglobal_p.h>
#include <cmath>
QT_BEGIN_NAMESPACE
// Defines a ICC parametric curve type 4
class Q_GUI_EXPORT QColorTransferFunction
{
public:
QColorTransferFunction() noexcept
: m_a(1.0f), m_b(0.0f), m_c(1.0f), m_d(0.0f), m_e(0.0f), m_f(0.0f), m_g(1.0f), m_flags(0)
{ }
QColorTransferFunction(float a, float b, float c, float d, float e, float f, float g) noexcept
: m_a(a), m_b(b), m_c(c), m_d(d), m_e(e), m_f(f), m_g(g), m_flags(0)
{ }
bool isGamma() const
{
updateHints();
return m_flags & quint32(Hints::IsGamma);
}
bool isLinear() const
{
updateHints();
return m_flags & quint32(Hints::IsLinear);
}
bool isSRgb() const
{
updateHints();
return m_flags & quint32(Hints::IsSRgb);
}
float apply(float x) const
{
if (x < m_d)
return m_c * x + m_f;
else
return std::pow(m_a * x + m_b, m_g) + m_e;
}
QColorTransferFunction inverted() const
{
float a, b, c, d, e, f, g;
d = m_c * m_d + m_f;
if (!qFuzzyIsNull(m_c)) {
c = 1.0f / m_c;
f = -m_f / m_c;
} else {
c = 0.0f;
f = 0.0f;
}
if (!qFuzzyIsNull(m_a) && !qFuzzyIsNull(m_g)) {
a = std::pow(1.0f / m_a, m_g);
b = -a * m_e;
e = -m_b / m_a;
g = 1.0f / m_g;
} else {
a = 0.0f;
b = 0.0f;
e = 1.0f;
g = 1.0f;
}
return QColorTransferFunction(a, b, c, d, e, f, g);
}
// A few predefined curves:
static QColorTransferFunction fromGamma(float gamma)
{
return QColorTransferFunction(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, gamma);
}
static QColorTransferFunction fromSRgb()
{
return QColorTransferFunction(1.0f / 1.055f, 0.055f / 1.055f, 1.0f / 12.92f, 0.04045f, 0.0f, 0.0f, 2.4f);
}
static QColorTransferFunction fromBt2020()
{
return QColorTransferFunction(1.0f / 1.0993f, 0.0993f / 1.0993f, 1.0f / 4.5f, 0.08145f, 0.0f, 0.0f, 2.2f);
}
static QColorTransferFunction fromProPhotoRgb()
{
return QColorTransferFunction(1.0f, 0.0f, 1.0f / 16.0f, 16.0f / 512.0f, 0.0f, 0.0f, 1.8f);
}
bool matches(const QColorTransferFunction &o) const
{
return paramCompare(m_a, o.m_a) && paramCompare(m_b, o.m_b)
&& paramCompare(m_c, o.m_c) && paramCompare(m_d, o.m_d)
&& paramCompare(m_e, o.m_e) && paramCompare(m_f, o.m_f)
&& paramCompare(m_g, o.m_g);
}
friend inline bool operator==(const QColorTransferFunction &f1, const QColorTransferFunction &f2);
friend inline bool operator!=(const QColorTransferFunction &f1, const QColorTransferFunction &f2);
float m_a;
float m_b;
float m_c;
float m_d;
float m_e;
float m_f;
float m_g;
private:
static inline bool paramCompare(float p1, float p2)
{
// Much fuzzier than fuzzy compare.
// It tries match parameters that has been passed through a 8.8
// fixed point form.
return (qAbs(p1 - p2) <= (1.0f / 512.0f));
}
void updateHints() const
{
if (m_flags & quint32(Hints::Calculated))
return;
// We do not consider the case with m_d = 1.0f linear or simple,
// since it wouldn't be linear for applyExtended().
bool simple = paramCompare(m_a, 1.0f) && paramCompare(m_b, 0.0f)
&& paramCompare(m_d, 0.0f)
&& paramCompare(m_e, 0.0f);
if (simple) {
m_flags |= quint32(Hints::IsGamma);
if (qFuzzyCompare(m_g, 1.0f))
m_flags |= quint32(Hints::IsLinear);
} else {
if (*this == fromSRgb())
m_flags |= quint32(Hints::IsSRgb);
}
m_flags |= quint32(Hints::Calculated);
}
enum class Hints : quint32 {
Calculated = 1,
IsGamma = 2,
IsLinear = 4,
IsSRgb = 8
};
mutable quint32 m_flags;
};
inline bool operator==(const QColorTransferFunction &f1, const QColorTransferFunction &f2)
{
return f1.matches(f2);
}
inline bool operator!=(const QColorTransferFunction &f1, const QColorTransferFunction &f2)
{
return !f1.matches(f2);
}
QT_END_NAMESPACE
#endif // QCOLORTRANSFERFUNCTION_P_H

View File

@ -0,0 +1,245 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORTRANSFERTABLE_P_H
#define QCOLORTRANSFERTABLE_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtGui/private/qtguiglobal_p.h>
#include "qcolortransferfunction_p.h"
#include <QVector>
#include <cmath>
QT_BEGIN_NAMESPACE
// Defines either an ICC TRC 'curve' or a lut8/lut16 A or B table
class Q_GUI_EXPORT QColorTransferTable
{
public:
QColorTransferTable() noexcept
: m_tableSize(0)
{ }
QColorTransferTable(uint32_t size, const QVector<uint8_t> &table) noexcept
: m_tableSize(size)
, m_table8(table)
{ }
QColorTransferTable(uint32_t size, const QVector<uint16_t> &table) noexcept
: m_tableSize(size)
, m_table16(table)
{ }
bool isValid() const
{
if (m_tableSize < 2)
return false;
#if !defined(QT_NO_DEBUG)
// The table must describe an injective curve:
if (!m_table8.isEmpty()) {
uint8_t val = 0;
for (uint i = 0; i < m_tableSize; ++i) {
Q_ASSERT(m_table8[i] >= val);
val = m_table8[i];
}
}
if (!m_table16.isEmpty()) {
uint16_t val = 0;
for (uint i = 0; i < m_tableSize; ++i) {
Q_ASSERT(m_table16[i] >= val);
val = m_table16[i];
}
}
#endif
return !m_table8.isEmpty() || !m_table16.isEmpty();
}
float apply(float x) const
{
x = std::min(std::max(x, 0.0f), 1.0f);
x *= m_tableSize - 1;
uint32_t lo = (int)std::floor(x);
uint32_t hi = std::min(lo + 1, m_tableSize);
float frac = x - lo;
if (!m_table16.isEmpty())
return (m_table16[lo] * (1.0f - frac) + m_table16[hi] * frac) * (1.0f/65535.0f);
if (!m_table8.isEmpty())
return (m_table8[lo] * (1.0f - frac) + m_table8[hi] * frac) * (1.0f/255.0f);
return x;
}
// Apply inverse, optimized by giving a previous result a value < x.
float applyInverse(float x, float resultLargerThan = 0.0f) const
{
Q_ASSERT(resultLargerThan >= 0.0f && resultLargerThan <= 1.0f);
if (x <= 0.0f)
return 0.0f;
if (x >= 1.0f)
return 1.0f;
if (!m_table16.isEmpty()) {
float v = x * 65535.0f;
uint i = std::floor(resultLargerThan * (m_tableSize - 1)) + 1;
for ( ; i < m_tableSize; ++i) {
if (m_table16[i] > v)
break;
}
if (i >= m_tableSize - 1)
return 1.0f;
float y1 = m_table16[i - 1];
float y2 = m_table16[i];
Q_ASSERT(x >= y1 && x < y2);
float fr = (v - y1) / (y2 - y1);
return (i + fr) * (1.0f / (m_tableSize - 1));
}
if (!m_table8.isEmpty()) {
float v = x * 255.0f;
uint i = std::floor(resultLargerThan * (m_tableSize - 1)) + 1;
for ( ; i < m_tableSize; ++i) {
if (m_table8[i] > v)
break;
}
if (i >= m_tableSize - 1)
return 1.0f;
float y1 = m_table8[i - 1];
float y2 = m_table8[i];
Q_ASSERT(x >= y1 && x < y2);
float fr = (v - y1) / (y2 - y1);
return (i + fr) * (1.0f / (m_tableSize - 1));
}
return x;
}
bool asColorTransferFunction(QColorTransferFunction *transferFn)
{
Q_ASSERT(isValid());
Q_ASSERT(transferFn);
if (!m_table8.isEmpty() && (m_table8[0] != 0 || m_table8[m_tableSize - 1] != 255))
return false;
if (!m_table16.isEmpty() && (m_table16[0] != 0 || m_table16[m_tableSize - 1] != 65535))
return false;
if (m_tableSize == 2) {
*transferFn = QColorTransferFunction(); // Linear
return true;
}
// The following heuristics are based on those from Skia:
if (m_tableSize == 26 && !m_table16.isEmpty()) {
// code.facebook.com/posts/411525055626587/under-the-hood-improving-facebook-photos
if (m_table16[6] != 3062)
return false;
if (m_table16[12] != 12824)
return false;
if (m_table16[18] != 31237)
return false;
*transferFn = QColorTransferFunction::fromSRgb();
return true;
}
if (m_tableSize == 1024 && !m_table16.isEmpty()) {
// HP and Canon sRGB gamma tables:
if (m_table16[257] != 3366)
return false;
if (m_table16[513] != 14116)
return false;
if (m_table16[768] != 34318)
return false;
*transferFn = QColorTransferFunction::fromSRgb();
return true;
}
if (m_tableSize == 4096 && !m_table16.isEmpty()) {
// Nikon, Epson, and lcms2 sRGB gamma tables:
if (m_table16[515] != 960)
return false;
if (m_table16[1025] != 3342)
return false;
if (m_table16[2051] != 14079)
return false;
*transferFn = QColorTransferFunction::fromSRgb();
return true;
}
return false;
}
friend inline bool operator!=(const QColorTransferTable &t1, const QColorTransferTable &t2);
friend inline bool operator==(const QColorTransferTable &t1, const QColorTransferTable &t2);
uint32_t m_tableSize;
QVector<uint8_t> m_table8;
QVector<uint16_t> m_table16;
};
inline bool operator!=(const QColorTransferTable &t1, const QColorTransferTable &t2)
{
if (t1.m_tableSize != t2.m_tableSize)
return true;
if (t1.m_table8.isEmpty() != t2.m_table8.isEmpty())
return true;
if (t1.m_table16.isEmpty() != t2.m_table16.isEmpty())
return true;
if (!t1.m_table8.isEmpty()) {
for (quint32 i = 0; i < t1.m_tableSize; ++i) {
if (t1.m_table8[i] != t2.m_table8[i])
return true;
}
}
if (!t1.m_table16.isEmpty()) {
for (quint32 i = 0; i < t1.m_tableSize; ++i) {
if (t1.m_table16[i] != t2.m_table16[i])
return true;
}
}
return false;
}
inline bool operator==(const QColorTransferTable &t1, const QColorTransferTable &t2)
{
return !(t1 != t2);
}
QT_END_NAMESPACE
#endif // QCOLORTRANSFERTABLE_P_H

View File

@ -0,0 +1,679 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qcolortransform.h"
#include "qcolortransform_p.h"
#include "qcolormatrix_p.h"
#include "qcolorspace_p.h"
#include "qcolortrc_p.h"
#include "qcolortrclut_p.h"
#include <QtCore/qatomic.h>
#include <QtCore/qmath.h>
#include <QtGui/qcolor.h>
#include <QtGui/qtransform.h>
#include <QtCore/private/qsimd_p.h>
#include <qdebug.h>
QT_BEGIN_NAMESPACE
QColorTrcLut *lutFromTrc(const QColorTrc &trc)
{
if (trc.m_type == QColorTrc::Type::Table)
return QColorTrcLut::fromTransferTable(trc.m_table);
if (trc.m_type == QColorTrc::Type::Function)
return QColorTrcLut::fromTransferFunction(trc.m_fun);
qWarning() << "TRC uninitialized";
return nullptr;
}
void QColorTransformPrivate::updateLutsIn() const
{
if (colorSpaceIn->lutsGenerated.loadAcquire())
return;
for (int i = 0; i < 3; ++i) {
if (!colorSpaceIn->trc[i].isValid())
return;
}
if (colorSpaceIn->trc[0] == colorSpaceIn->trc[1] && colorSpaceIn->trc[0] == colorSpaceIn->trc[2]) {
colorSpaceIn->lut[0].reset(lutFromTrc(colorSpaceIn->trc[0]));
colorSpaceIn->lut[1] = colorSpaceIn->lut[0];
colorSpaceIn->lut[2] = colorSpaceIn->lut[0];
} else {
for (int i = 0; i < 3; ++i)
colorSpaceIn->lut[i].reset(lutFromTrc(colorSpaceIn->trc[i]));
}
colorSpaceIn->lutsGenerated.storeRelease(1);
}
void QColorTransformPrivate::updateLutsOut() const
{
if (colorSpaceOut->lutsGenerated.loadAcquire())
return;
for (int i = 0; i < 3; ++i) {
if (!colorSpaceOut->trc[i].isValid())
return;
}
if (colorSpaceOut->trc[0] == colorSpaceOut->trc[1] && colorSpaceOut->trc[0] == colorSpaceOut->trc[2]) {
colorSpaceOut->lut[0].reset(lutFromTrc(colorSpaceOut->trc[0]));
colorSpaceOut->lut[1] = colorSpaceOut->lut[0];
colorSpaceOut->lut[2] = colorSpaceOut->lut[0];
} else {
for (int i = 0; i < 3; ++i)
colorSpaceOut->lut[i].reset(lutFromTrc(colorSpaceOut->trc[i]));
}
colorSpaceOut->lutsGenerated.storeRelease(1);
}
/*!
\class QColorTransform
\brief The QColorTransform class is a transformation between color spaces.
\since 5.14
\ingroup painting
\ingroup appearance
\inmodule QtGui
QColorTransform is an instantiation of a transformation between color spaces.
It can be applied on color and pixels to convert them from one color space to
another.
Setting up a QColorTransform takes some preprocessing, so keeping around
QColorTransforms that you need often is recommended, instead of generating
them on the fly.
*/
QColorTransform::~QColorTransform() noexcept
{
}
/*!
Applies the color transformation on the QRgb value \a argb.
The input should be opaque or unpremultiplied.
*/
QRgb QColorTransform::map(const QRgb &argb) const
{
if (!d_ptr)
return argb;
Q_D(const QColorTransform);
constexpr float f = 1.0f / 255.0f;
QColorVector c = { qRed(argb) * f, qGreen(argb) * f, qBlue(argb) * f };
c.x = d->colorSpaceIn->trc[0].apply(c.x);
c.y = d->colorSpaceIn->trc[1].apply(c.y);
c.z = d->colorSpaceIn->trc[2].apply(c.z);
c = d->colorMatrix.map(c);
c.x = std::max(0.0f, std::min(1.0f, c.x));
c.y = std::max(0.0f, std::min(1.0f, c.y));
c.z = std::max(0.0f, std::min(1.0f, c.z));
if (d->colorSpaceOut->lutsGenerated.loadAcquire()) {
c.x = d->colorSpaceOut->lut[0]->fromLinear(c.x);
c.y = d->colorSpaceOut->lut[1]->fromLinear(c.y);
c.z = d->colorSpaceOut->lut[2]->fromLinear(c.z);
} else {
c.x = d->colorSpaceOut->trc[0].applyInverse(c.x);
c.y = d->colorSpaceOut->trc[1].applyInverse(c.y);
c.z = d->colorSpaceOut->trc[2].applyInverse(c.z);
}
return qRgba(c.x * 255 + 0.5f, c.y * 255 + 0.5f, c.z * 255 + 0.5f, qAlpha(argb));
}
/*!
Applies the color transformation on the QRgba64 value \a rgba64.
The input should be opaque or unpremultiplied.
*/
QRgba64 QColorTransform::map(const QRgba64 &rgba64) const
{
if (!d_ptr)
return rgba64;
Q_D(const QColorTransform);
constexpr float f = 1.0f / 65535.0f;
QColorVector c = { rgba64.red() * f, rgba64.green() * f, rgba64.blue() * f };
c.x = d->colorSpaceIn->trc[0].apply(c.x);
c.y = d->colorSpaceIn->trc[1].apply(c.y);
c.z = d->colorSpaceIn->trc[2].apply(c.z);
c = d->colorMatrix.map(c);
c.x = std::max(0.0f, std::min(1.0f, c.x));
c.y = std::max(0.0f, std::min(1.0f, c.y));
c.z = std::max(0.0f, std::min(1.0f, c.z));
if (d->colorSpaceOut->lutsGenerated.loadAcquire()) {
c.x = d->colorSpaceOut->lut[0]->fromLinear(c.x);
c.y = d->colorSpaceOut->lut[1]->fromLinear(c.y);
c.z = d->colorSpaceOut->lut[2]->fromLinear(c.z);
} else {
c.x = d->colorSpaceOut->trc[0].applyInverse(c.x);
c.y = d->colorSpaceOut->trc[1].applyInverse(c.y);
c.z = d->colorSpaceOut->trc[2].applyInverse(c.z);
}
return QRgba64::fromRgba64(c.x * 65535, c.y * 65535, c.z * 65535, rgba64.alpha());
}
/*!
Applies the color transformation on the QColor value \a color.
*/
QColor QColorTransform::map(const QColor &color) const
{
if (!d_ptr)
return color;
Q_D(const QColorTransform);
QColorVector c = { (float)color.redF(), (float)color.greenF(), (float)color.blueF() };
c.x = d->colorSpaceIn->trc[0].apply(c.x);
c.y = d->colorSpaceIn->trc[1].apply(c.y);
c.z = d->colorSpaceIn->trc[2].apply(c.z);
c = d->colorMatrix.map(c);
if (d_ptr->colorSpaceOut->lutsGenerated.loadAcquire()) {
c.x = d->colorSpaceOut->lut[0]->fromLinear(c.x);
c.y = d->colorSpaceOut->lut[1]->fromLinear(c.y);
c.z = d->colorSpaceOut->lut[2]->fromLinear(c.z);
} else {
c.x = d->colorSpaceOut->trc[0].applyInverse(c.x);
c.y = d->colorSpaceOut->trc[1].applyInverse(c.y);
c.z = d->colorSpaceOut->trc[2].applyInverse(c.z);
}
QColor out;
out.setRgbF(c.x, c.y, c.z, color.alphaF());
return out;
}
// Optimized sub-routines for fast block based conversion:
static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorMatrix &colorMatrix)
{
#if defined(__SSE2__)
const __m128 minV = _mm_set1_ps(0.0f);
const __m128 maxV = _mm_set1_ps(1.0f);
const __m128 xMat = _mm_loadu_ps(&colorMatrix.r.x);
const __m128 yMat = _mm_loadu_ps(&colorMatrix.g.x);
const __m128 zMat = _mm_loadu_ps(&colorMatrix.b.x);
for (qsizetype j = 0; j < len; ++j) {
__m128 c = _mm_loadu_ps(&buffer[j].x);
__m128 cx = _mm_shuffle_ps(c, c, _MM_SHUFFLE(0, 0, 0, 0));
__m128 cy = _mm_shuffle_ps(c, c, _MM_SHUFFLE(1, 1, 1, 1));
__m128 cz = _mm_shuffle_ps(c, c, _MM_SHUFFLE(2, 2, 2, 2));
cx = _mm_mul_ps(cx, xMat);
cy = _mm_mul_ps(cy, yMat);
cz = _mm_mul_ps(cz, zMat);
cx = _mm_add_ps(cx, cy);
cx = _mm_add_ps(cx, cz);
// Clamp:
cx = _mm_min_ps(cx, maxV);
cx = _mm_max_ps(cx, minV);
_mm_storeu_ps(&buffer[j].x, cx);
}
#else
for (int j = 0; j < len; ++j) {
const QColorVector cv = colorMatrix.map(buffer[j]);
buffer[j].x = std::max(0.0f, std::min(1.0f, cv.x));
buffer[j].y = std::max(0.0f, std::min(1.0f, cv.y));
buffer[j].z = std::max(0.0f, std::min(1.0f, cv.z));
}
#endif
}
template<typename T>
static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr);
template<typename T>
static void loadUnpremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr);
#if defined(__SSE2__)
// Load to [0-alpha] in 4x32 SIMD
template<typename T>
static inline void loadP(const T &p, __m128i &v);
template<>
inline void loadP<QRgb>(const QRgb &p, __m128i &v)
{
v = _mm_cvtsi32_si128(p);
#if defined(__SSE4_1__)
v = _mm_cvtepu8_epi32(v);
#else
v = _mm_unpacklo_epi8(v, _mm_setzero_si128());
v = _mm_unpacklo_epi16(v, _mm_setzero_si128());
#endif
}
template<>
inline void loadP<QRgba64>(const QRgba64 &p, __m128i &v)
{
v = _mm_loadl_epi64((const __m128i *)&p);
#if defined(__SSE4_1__)
v = _mm_cvtepu16_epi32(v);
#else
v = _mm_unpacklo_epi16(v, _mm_setzero_si128());
#endif
// Shuffle to ARGB as the template below expects it
v = _mm_shuffle_epi32(v, _MM_SHUFFLE(3, 0, 1, 2));
}
template<typename T>
static void loadPremultiplied(QColorVector *buffer, const T *src, 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));
for (qsizetype i = 0; i < len; ++i) {
__m128i v;
loadP<T>(src[i], v);
__m128 vf = _mm_cvtepi32_ps(v);
// Approximate 1/a:
__m128 va = _mm_shuffle_ps(vf, vf, _MM_SHUFFLE(3, 3, 3, 3));
__m128 via = _mm_rcp_ps(va);
via = _mm_sub_ps(_mm_add_ps(via, via), _mm_mul_ps(via, _mm_mul_ps(via, va)));
// v * (1/a)
vf = _mm_mul_ps(vf, via);
// Handle zero alpha
__m128 vAlphaMask = _mm_cmpeq_ps(va, _mm_set1_ps(0.0f));
vf = _mm_andnot_ps(vAlphaMask, vf);
// LUT
v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
const int ridx = _mm_extract_epi16(v, 4);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 0);
v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[0]->m_toLinear[ridx], 0);
v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[1]->m_toLinear[gidx], 2);
v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[2]->m_toLinear[bidx], 4);
vf = _mm_mul_ps(_mm_cvtepi32_ps(v), iFF00);
_mm_storeu_ps(&buffer[i].x, vf);
}
}
// Load to [0-4080] in 4x32 SIMD
template<typename T>
static inline void loadPU(const T &p, __m128i &v);
template<>
inline void loadPU<QRgb>(const QRgb &p, __m128i &v)
{
v = _mm_cvtsi32_si128(p);
#if defined(__SSE4_1__)
v = _mm_cvtepu8_epi32(v);
#else
v = _mm_unpacklo_epi8(v, _mm_setzero_si128());
v = _mm_unpacklo_epi16(v, _mm_setzero_si128());
#endif
v = _mm_slli_epi32(v, 4);
}
template<>
inline void loadPU<QRgba64>(const QRgba64 &p, __m128i &v)
{
v = _mm_loadl_epi64((const __m128i *)&p);
v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8));
#if defined(__SSE4_1__)
v = _mm_cvtepu16_epi32(v);
#else
v = _mm_unpacklo_epi16(v, _mm_setzero_si128());
#endif
v = _mm_srli_epi32(v, 4);
// Shuffle to ARGB as the template below expects it
v = _mm_shuffle_epi32(v, _MM_SHUFFLE(3, 0, 1, 2));
}
template<typename T>
void loadUnpremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
for (qsizetype i = 0; i < len; ++i) {
__m128i v;
loadPU<T>(src[i], v);
const int ridx = _mm_extract_epi16(v, 4);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 0);
v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[0]->m_toLinear[ridx], 0);
v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[1]->m_toLinear[gidx], 2);
v = _mm_insert_epi16(v, d_ptr->colorSpaceIn->lut[2]->m_toLinear[bidx], 4);
__m128 vf = _mm_mul_ps(_mm_cvtepi32_ps(v), iFF00);
_mm_storeu_ps(&buffer[i].x, vf);
}
}
#else
template<>
void loadPremultiplied<QRgb>(QColorVector *buffer, const QRgb *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
const uint p = src[i];
const int a = qAlpha(p);
if (a) {
const float ia = 4080.0f / a;
const int ridx = int(qRed(p) * ia + 0.5f);
const int gidx = int(qGreen(p) * ia + 0.5f);
const int bidx = int(qBlue(p) * ia + 0.5f);
buffer[i].x = d_ptr->colorSpaceIn->lut[0]->m_toLinear[ridx] * (1.0f / (255 * 256));
buffer[i].y = d_ptr->colorSpaceIn->lut[1]->m_toLinear[gidx] * (1.0f / (255 * 256));
buffer[i].z = d_ptr->colorSpaceIn->lut[2]->m_toLinear[bidx] * (1.0f / (255 * 256));
} else {
buffer[i].x = buffer[i].y = buffer[i].z = 0.0f;
}
}
}
template<>
void loadPremultiplied<QRgba64>(QColorVector *buffer, const QRgba64 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
const QRgba64 &p = src[i];
const int a = p.alpha();
if (a) {
const float ia = 4080.0f / a;
const int ridx = int(p.red() * ia + 0.5f);
const int gidx = int(p.green() * ia + 0.5f);
const int bidx = int(p.blue() * ia + 0.5f);
buffer[i].x = d_ptr->colorSpaceIn->lut[0]->m_toLinear[ridx] * (1.0f / (255 * 256));
buffer[i].y = d_ptr->colorSpaceIn->lut[1]->m_toLinear[gidx] * (1.0f / (255 * 256));
buffer[i].z = d_ptr->colorSpaceIn->lut[2]->m_toLinear[bidx] * (1.0f / (255 * 256));
} else {
buffer[i].x = buffer[i].y = buffer[i].z = 0.0f;
}
}
}
template<>
void loadUnpremultiplied<QRgb>(QColorVector *buffer, const QRgb *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
const uint p = src[i];
buffer[i].x = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(qRed(p));
buffer[i].y = d_ptr->colorSpaceIn->lut[1]->u8ToLinearF32(qGreen(p));
buffer[i].z = d_ptr->colorSpaceIn->lut[2]->u8ToLinearF32(qBlue(p));
}
}
template<>
void loadUnpremultiplied<QRgba64>(QColorVector *buffer, const QRgba64 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
const QRgba64 &p = src[i];
buffer[i].x = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(p.red());
buffer[i].y = d_ptr->colorSpaceIn->lut[1]->u16ToLinearF32(p.green());
buffer[i].z = d_ptr->colorSpaceIn->lut[2]->u16ToLinearF32(p.blue());
}
}
#endif
static void storePremultiplied(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
#if defined(__SSE2__)
const __m128 v4080 = _mm_set1_ps(4080.f);
const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
for (qsizetype i = 0; i < len; ++i) {
const int a = qAlpha(src[i]);
__m128 vf = _mm_loadu_ps(&buffer[i].x);
__m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
__m128 va = _mm_set1_ps(a);
va = _mm_mul_ps(va, iFF00);
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], 4);
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], 2);
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], 0);
vf = _mm_cvtepi32_ps(v);
vf = _mm_mul_ps(vf, va);
v = _mm_cvtps_epi32(vf);
v = _mm_packs_epi32(v, v);
v = _mm_insert_epi16(v, a, 3);
v = _mm_packus_epi16(v, v);
dst[i] = _mm_cvtsi128_si32(v);
}
#else
for (qsizetype i = 0; i < len; ++i) {
const int a = qAlpha(src[i]);
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)];
const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * 4080.0f + 0.5f)];
dst[i] = qRgba(r * fa + 0.5f, g * fa + 0.5f, b * fa + 0.5f, a);
}
#endif
}
static void storeUnpremultiplied(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
#if defined(__SSE2__)
const __m128 v4080 = _mm_set1_ps(4080.f);
for (qsizetype i = 0; i < len; ++i) {
const int a = qAlpha(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);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
v = _mm_setzero_si128();
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], 2);
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], 0);
v = _mm_add_epi16(v, _mm_set1_epi16(0x80));
v = _mm_srli_epi16(v, 8);
v = _mm_insert_epi16(v, a, 3);
v = _mm_packus_epi16(v, v);
dst[i] = _mm_cvtsi128_si32(v);
}
#else
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);
const int b = d_ptr->colorSpaceOut->lut[2]->u8FromLinearF32(buffer[i].z);
dst[i] = (src[i] & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
}
#endif
}
static void storeOpaque(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
Q_UNUSED(src);
#if defined(__SSE2__)
const __m128 v4080 = _mm_set1_ps(4080.f);
for (qsizetype i = 0; i < len; ++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);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
v = _mm_setzero_si128();
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], 2);
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], 0);
v = _mm_add_epi16(v, _mm_set1_epi16(0x80));
v = _mm_srli_epi16(v, 8);
v = _mm_insert_epi16(v, 255, 3);
v = _mm_packus_epi16(v, v);
dst[i] = _mm_cvtsi128_si32(v);
}
#else
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);
const int b = d_ptr->colorSpaceOut->lut[2]->u8FromLinearF32(buffer[i].z);
dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
}
#endif
}
static void storePremultiplied(QRgba64 *dst, const QRgba64 *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 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)];
const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * 4080.0f + 0.5f)];
dst[i] = qRgba64(r * fa + 0.5f, g * fa + 0.5f, b * fa + 0.5f, a);
}
}
static void storeUnpremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
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);
const int b = d_ptr->colorSpaceOut->lut[2]->u16FromLinearF32(buffer[i].z);
dst[i] = qRgba64(r, g, b, src[i].alpha());
}
}
static void storeOpaque(QRgba64 *dst, const QRgba64 *src, 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);
const int b = d_ptr->colorSpaceOut->lut[2]->u16FromLinearF32(buffer[i].z);
dst[i] = qRgba64(r, g, b, 0xFFFF);
}
}
static constexpr qsizetype WorkBlockSize = 256;
template<typename T>
void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const
{
if (!colorMatrix.isValid())
return;
updateLutsIn();
updateLutsOut();
bool doApplyMatrix = (colorMatrix != QColorMatrix::identity());
QColorVector buffer[WorkBlockSize];
qsizetype i = 0;
while (i < count) {
const qsizetype len = qMin(count - i, WorkBlockSize);
if (flags & InputPremultiplied)
loadPremultiplied(buffer, src + i, len, this);
else
loadUnpremultiplied(buffer, src + i, len, this);
if (doApplyMatrix)
applyMatrix(buffer, len, colorMatrix);
if (flags & InputOpaque)
storeOpaque(dst + i, src + i, buffer, len, this);
else if (flags & OutputPremultiplied)
storePremultiplied(dst + i, src + i, buffer, len, this);
else
storeUnpremultiplied(dst + i, src + i, buffer, len, this);
i += len;
}
}
/*!
\internal
\enum QColorTransformPrivate::TransformFlag
Defines how the transform is to be applied.
\value Unpremultiplied The input and output should both be unpremultiplied.
\value InputOpaque The input is guaranteed to be opaque.
\value InputPremultiplied The input is premultiplied.
\value OutputPremultiplied The output should be premultiplied.
\value Premultiplied Both input and output should both be premultiplied.
*/
/*!
\internal
Prepares a color transformation for fast application. You do not need to
call this explicitly as it will be called implicitly on the first transforms, but
if you want predictable performance on the first transforms, you can perform it
in advance.
\sa QColorTransform::map(), apply()
*/
void QColorTransformPrivate::prepare()
{
updateLutsIn();
updateLutsOut();
}
/*!
\internal
Applies the color transformation on \a count QRgb pixels starting from
\a src and stores the result in \a dst.
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);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,93 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORTRANSFORM_H
#define QCOLORTRANSFORM_H
#include <QtGui/qtguiglobal.h>
#include <QtCore/qsharedpointer.h>
#include <QtGui/qrgb.h>
QT_BEGIN_NAMESPACE
class QColor;
class QRgba64;
class QColorSpacePrivate;
class QColorTransformPrivate;
class Q_GUI_EXPORT QColorTransform
{
public:
QColorTransform() noexcept : d_ptr(nullptr) { }
~QColorTransform() noexcept;
QColorTransform(const QColorTransform &colorTransform) noexcept
: d_ptr(colorTransform.d_ptr)
{ }
QColorTransform(QColorTransform &&colorTransform) noexcept
: d_ptr(std::move(colorTransform.d_ptr))
{ }
QColorTransform &operator=(const QColorTransform &other) noexcept
{
d_ptr = other.d_ptr;
return *this;
}
QColorTransform &operator=(QColorTransform &&other) noexcept
{
d_ptr = std::move(other.d_ptr);
return *this;
}
bool isNull() const { return d_ptr.isNull(); }
QRgb map(const QRgb &argb) const;
QRgba64 map(const QRgba64 &rgba64) const;
QColor map(const QColor &color) const;
private:
friend class QColorSpace;
friend class QColorSpacePrivate;
friend class QImage;
Q_DECLARE_PRIVATE(QColorTransform)
QSharedPointer<QColorTransformPrivate> d_ptr;
};
QT_END_NAMESPACE
#endif // QCOLORTRANSFORM_H

View File

@ -0,0 +1,89 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORTRANSFORM_P_H
#define QCOLORTRANSFORM_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qcolormatrix_p.h"
#include "qcolorspace_p.h"
QT_BEGIN_NAMESPACE
class QColorTransformPrivate
{
public:
QColorMatrix colorMatrix;
QExplicitlySharedDataPointer<const QColorSpacePrivate> colorSpaceIn;
QExplicitlySharedDataPointer<const QColorSpacePrivate> colorSpaceOut;
void updateLutsIn() const;
void updateLutsOut() const;
bool simpleGammaCorrection() const;
void prepare();
enum TransformFlag {
Unpremultiplied = 0,
InputOpaque = 1,
InputPremultiplied = 2,
OutputPremultiplied = 4,
Premultiplied = (InputPremultiplied | OutputPremultiplied)
};
Q_DECLARE_FLAGS(TransformFlags, TransformFlag)
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;
template<typename T>
void apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
};
QT_END_NAMESPACE
#endif // QCOLORTRANSFORM_P_H

View File

@ -0,0 +1,129 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCOLORTRC_P_H
#define QCOLORTRC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtGui/private/qtguiglobal_p.h>
#include "qcolortransferfunction_p.h"
#include "qcolortransfertable_p.h"
QT_BEGIN_NAMESPACE
// Defines an ICC TRC (Tone Reproduction Curve)
class Q_GUI_EXPORT QColorTrc
{
public:
QColorTrc() noexcept : m_type(Type::Uninitialized)
{ }
QColorTrc(const QColorTransferFunction &fun) : m_type(Type::Function), m_fun(fun)
{ }
QColorTrc(const QColorTransferTable &table) : m_type(Type::Table), m_table(table)
{ }
enum class Type {
Uninitialized,
Function,
Table
};
bool isLinear() const
{
return m_type == Type::Uninitialized || (m_type == Type::Function && m_fun.isLinear());
}
bool isValid() const
{
return m_type != Type::Uninitialized;
}
float apply(float x) const
{
if (m_type == Type::Table)
return m_table.apply(x);
if (m_type == Type::Function)
return m_fun.apply(x);
return x;
}
float applyInverse(float x) const
{
if (m_type == Type::Table)
return m_table.applyInverse(x);
if (m_type == Type::Function)
return m_fun.inverted().apply(x);
return x;
}
friend inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2);
friend inline bool operator==(const QColorTrc &o1, const QColorTrc &o2);
Type m_type;
QColorTransferFunction m_fun;
QColorTransferTable m_table;
};
inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2)
{
if (o1.m_type != o2.m_type)
return true;
if (o1.m_type == QColorTrc::Type::Function)
return o1.m_fun != o2.m_fun;
if (o1.m_type == QColorTrc::Type::Table)
return o1.m_table != o2.m_table;
return false;
}
inline bool operator==(const QColorTrc &o1, const QColorTrc &o2)
{
return !(o1 != o2);
}
QT_END_NAMESPACE
#endif // QCOLORTRC

View File

@ -37,14 +37,16 @@
**
****************************************************************************/
#include "qcolorprofile_p.h"
#include "qcolortrclut_p.h"
#include "qcolortransferfunction_p.h"
#include "qcolortransfertable_p.h"
#include <qmath.h>
QT_BEGIN_NAMESPACE
QColorProfile *QColorProfile::fromGamma(qreal gamma)
QColorTrcLut *QColorTrcLut::fromGamma(qreal gamma)
{
QColorProfile *cp = new QColorProfile;
QColorTrcLut *cp = new QColorTrcLut;
for (int i = 0; i <= (255 * 16); ++i) {
cp->m_toLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), gamma) * (255 * 256)));
@ -54,31 +56,28 @@ QColorProfile *QColorProfile::fromGamma(qreal gamma)
return cp;
}
static qreal srgbToLinear(qreal v)
QColorTrcLut *QColorTrcLut::fromTransferFunction(const QColorTransferFunction &fun)
{
const qreal a = 0.055;
if (v <= qreal(0.04045))
return v / qreal(12.92);
else
return qPow((v + a) / (qreal(1) + a), qreal(2.4));
}
static qreal linearToSrgb(qreal v)
{
const qreal a = 0.055;
if (v <= qreal(0.0031308))
return v * qreal(12.92);
else
return (qreal(1) + a) * qPow(v, qreal(1.0 / 2.4)) - a;
}
QColorProfile *QColorProfile::fromSRgb()
{
QColorProfile *cp = new QColorProfile;
QColorTrcLut *cp = new QColorTrcLut;
QColorTransferFunction inv = fun.inverted();
for (int i = 0; i <= (255 * 16); ++i) {
cp->m_toLinear[i] = ushort(qRound(srgbToLinear(i / qreal(255 * 16)) * (255 * 256)));
cp->m_fromLinear[i] = ushort(qRound(linearToSrgb(i / qreal(255 * 16)) * (255 * 256)));
cp->m_toLinear[i] = ushort(qRound(fun.apply(i / qreal(255 * 16)) * (255 * 256)));
cp->m_fromLinear[i] = ushort(qRound(inv.apply(i / qreal(255 * 16)) * (255 * 256)));
}
return cp;
}
QColorTrcLut *QColorTrcLut::fromTransferTable(const QColorTransferTable &table)
{
QColorTrcLut *cp = new QColorTrcLut;
float minInverse = 0.0f;
for (int i = 0; i <= (255 * 16); ++i) {
cp->m_toLinear[i] = ushort(qBound(0, qRound(table.apply(i / qreal(255 * 16)) * (255 * 256)), 65280));
minInverse = table.applyInverse(i / qreal(255 * 16), minInverse);
cp->m_fromLinear[i] = ushort(qBound(0, qRound(minInverse * (255 * 256)), 65280));
}
return cp;

View File

@ -37,8 +37,8 @@
**
****************************************************************************/
#ifndef QCOLORPROFILE_P_H
#define QCOLORPROFILE_P_H
#ifndef QCOLORTRCLUT_P_H
#define QCOLORTRCLUT_P_H
//
// W A R N I N G
@ -52,21 +52,29 @@
//
#include <QtGui/private/qtguiglobal_p.h>
#include <QtCore/qsharedpointer.h>
#include <QtGui/qrgb.h>
#include <QtGui/qrgba64.h>
#include <cmath>
#if defined(__SSE2__)
#include <emmintrin.h>
#elif defined(__ARM_NEON__) || defined(__ARM_NEON)
#include <arm_neon.h>
#endif
QT_BEGIN_NAMESPACE
class Q_GUI_EXPORT QColorProfile
class QColorTransferFunction;
class QColorTransferTable;
class Q_GUI_EXPORT QColorTrcLut : public QEnableSharedFromThis<QColorTrcLut>
{
public:
static QColorProfile *fromGamma(qreal gamma);
static QColorProfile *fromSRgb();
static QColorTrcLut *fromGamma(qreal gamma);
static QColorTrcLut *fromTransferFunction(const QColorTransferFunction &transfn);
static QColorTrcLut *fromTransferTable(const QColorTransferTable &transTable);
// The following methods all convert opaque or unpremultiplied colors:
@ -121,6 +129,25 @@ public:
return convertWithTable(rgb64, m_toLinear);
}
float u8ToLinearF32(int c) const
{
ushort v = m_toLinear[c << 4];
return v * (1.0f / (255*256));
}
float u16ToLinearF32(int c) const
{
c -= (c >> 8);
ushort v = m_toLinear[c >> 4];
return v * (1.0f / (255*256));
}
float toLinear(float f) const
{
ushort v = m_toLinear[(int)(f * (255 * 16) + 0.5f)];
return v * (1.0f / (255*256));
}
QRgb fromLinear64(QRgba64 rgb64) const
{
#if defined(__SSE2__)
@ -176,8 +203,31 @@ public:
return convertWithTable(rgb64, m_fromLinear);
}
int u8FromLinearF32(float f) const
{
ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
return (v + 0x80) >> 8;
}
int u16FromLinearF32(float f) const
{
ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
return v + (v >> 8);
}
float fromLinear(float f) const
{
ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
return v * (1.0f / (255*256));
}
// We translate to 0-65280 (255*256) instead to 0-65535 to make simple
// shifting an accurate conversion.
// We translate from 0-4080 (255*16) for the same speed up, and to keep
// the tables small enough to fit in most inner caches.
ushort m_toLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
ushort m_fromLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
private:
QColorProfile() { }
QColorTrcLut() { }
Q_ALWAYS_INLINE static QRgb convertWithTable(QRgb rgb32, const ushort *table)
{
@ -230,16 +280,8 @@ private:
return QRgba64::fromRgba64(r, g, b, rgb64.alpha());
#endif
}
// We translate to 0-65280 (255*256) instead to 0-65535 to make simple
// shifting an accurate conversion.
// We translate from 0-4080 (255*16) for the same speed up, and to keep
// the tables small enough to fit in most inner caches.
ushort m_toLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
ushort m_fromLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
};
QT_END_NAMESPACE
#endif // QCOLORPROFILE_P_H
#endif // QCOLORTRCLUT_P_H

View File

@ -43,7 +43,7 @@
#include <qstylehints.h>
#include <qguiapplication.h>
#include <qatomic.h>
#include <private/qcolorprofile_p.h>
#include <private/qcolortrclut_p.h>
#include <private/qdrawhelper_p.h>
#include <private/qpaintengine_raster_p.h>
#include <private/qpainter_p.h>
@ -5523,7 +5523,7 @@ inline static void qt_bitmapblit_quint16(QRasterBuffer *rasterBuffer,
map, mapWidth, mapHeight, mapStride);
}
static inline void alphamapblend_generic(int coverage, QRgba64 *dest, int x, const QRgba64 &srcLinear, const QRgba64 &src, const QColorProfile *colorProfile)
static inline void alphamapblend_generic(int coverage, QRgba64 *dest, int x, const QRgba64 &srcLinear, const QRgba64 &src, const QColorTrcLut *colorProfile)
{
if (coverage == 0) {
// nothing
@ -5558,7 +5558,7 @@ static void qt_alphamapblit_generic(QRasterBuffer *rasterBuffer,
if (color.isTransparent())
return;
const QColorProfile *colorProfile = nullptr;
const QColorTrcLut *colorProfile = nullptr;
if (useGammaCorrection)
colorProfile = QGuiApplicationPrivate::instance()->colorProfileForA8Text();
@ -5684,7 +5684,7 @@ void qt_alphamapblit_quint16(QRasterBuffer *rasterBuffer,
}
}
static inline void rgbBlendPixel(quint32 *dst, int coverage, QRgba64 slinear, const QColorProfile *colorProfile)
static inline void rgbBlendPixel(quint32 *dst, int coverage, QRgba64 slinear, const QColorTrcLut *colorProfile)
{
// Do a gammacorrected RGB alphablend...
const QRgba64 dlinear = colorProfile ? colorProfile->toLinear64(*dst) : QRgba64::fromArgb32(*dst);
@ -5694,7 +5694,7 @@ static inline void rgbBlendPixel(quint32 *dst, int coverage, QRgba64 slinear, co
*dst = colorProfile ? colorProfile->fromLinear64(blend) : toArgb32(blend);
}
static inline void grayBlendPixel(quint32 *dst, int coverage, QRgba64 srcLinear, const QColorProfile *colorProfile)
static inline void grayBlendPixel(quint32 *dst, int coverage, QRgba64 srcLinear, const QColorTrcLut *colorProfile)
{
// Do a gammacorrected gray alphablend...
const QRgba64 dstLinear = colorProfile ? colorProfile->toLinear64(*dst) : QRgba64::fromArgb32(*dst);
@ -5704,7 +5704,7 @@ static inline void grayBlendPixel(quint32 *dst, int coverage, QRgba64 srcLinear,
*dst = colorProfile ? colorProfile->fromLinear64(blend) : toArgb32(blend);
}
static inline void alphamapblend_argb32(quint32 *dst, int coverage, QRgba64 srcLinear, quint32 src, const QColorProfile *colorProfile)
static inline void alphamapblend_argb32(quint32 *dst, int coverage, QRgba64 srcLinear, quint32 src, const QColorTrcLut *colorProfile)
{
if (coverage == 0) {
// nothing
@ -5734,7 +5734,7 @@ static void qt_alphamapblit_argb32(QRasterBuffer *rasterBuffer,
if (color.isTransparent())
return;
const QColorProfile *colorProfile = nullptr;
const QColorTrcLut *colorProfile = nullptr;
if (useGammaCorrection)
colorProfile = QGuiApplicationPrivate::instance()->colorProfileForA8Text();
@ -5830,7 +5830,7 @@ static inline QRgb rgbBlend(QRgb d, QRgb s, uint rgbAlpha)
#endif
}
static inline void alphargbblend_generic(uint coverage, QRgba64 *dest, int x, const QRgba64 &srcLinear, const QRgba64 &src, const QColorProfile *colorProfile)
static inline void alphargbblend_generic(uint coverage, QRgba64 *dest, int x, const QRgba64 &srcLinear, const QRgba64 &src, const QColorTrcLut *colorProfile)
{
if (coverage == 0xff000000) {
// nothing
@ -5852,7 +5852,7 @@ static inline void alphargbblend_generic(uint coverage, QRgba64 *dest, int x, co
}
}
static inline void alphargbblend_argb32(quint32 *dst, uint coverage, const QRgba64 &srcLinear, quint32 src, const QColorProfile *colorProfile)
static inline void alphargbblend_argb32(quint32 *dst, uint coverage, const QRgba64 &srcLinear, quint32 src, const QColorTrcLut *colorProfile)
{
if (coverage == 0xff000000) {
// nothing
@ -5877,7 +5877,7 @@ static void qt_alphargbblit_generic(QRasterBuffer *rasterBuffer,
if (color.isTransparent())
return;
const QColorProfile *colorProfile = nullptr;
const QColorTrcLut *colorProfile = nullptr;
if (useGammaCorrection)
colorProfile = QGuiApplicationPrivate::instance()->colorProfileForA32Text();
@ -5954,7 +5954,7 @@ static void qt_alphargbblit_argb32(QRasterBuffer *rasterBuffer,
const quint32 c = color.toArgb32();
const QColorProfile *colorProfile = nullptr;
const QColorTrcLut *colorProfile = nullptr;
if (useGammaCorrection)
colorProfile = QGuiApplicationPrivate::instance()->colorProfileForA32Text();

669
src/gui/painting/qicc.cpp Normal file
View File

@ -0,0 +1,669 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qicc_p.h"
#include <qbuffer.h>
#include <qbytearray.h>
#include <qdatastream.h>
#include <qloggingcategory.h>
#include <qendian.h>
#include "qcolorspace_p.h"
#include "qcolortrc_p.h"
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcIcc, "qt.gui.icc")
struct ICCProfileHeader
{
quint32_be profileSize;
quint32_be preferredCmmType;
quint32_be profileVersion;
quint32_be profileClass;
quint32_be inputColorSpace;
quint32_be pcs;
quint32_be datetime[3];
quint32_be signature;
quint32_be platformSignature;
quint32_be flags;
quint32_be deviceManufacturer;
quint32_be deviceModel;
quint32_be deviceAttributes[2];
quint32_be renderingIntent;
qint32_be illuminantXyz[3];
quint32_be creatorSignature;
quint32_be profileId[4];
quint32_be reserved[7];
// Technically after the header, but easier to include here:
quint32_be tagCount;
};
constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
{
return (a << 24) | (b << 16) | (c << 8) | d;
}
enum class ProfileClass : quint32 {
Input = IccTag('s', 'c', 'r', 'n'),
Display = IccTag('m', 'n', 't', 'r'),
// Not supported:
Output = IccTag('p', 'r', 't', 'r'),
ColorSpace = IccTag('s', 'p', 'a', 'c'),
};
enum class Tag : quint32 {
acsp = IccTag('a', 'c', 's', 'p'),
RGB_ = IccTag('R', 'G', 'B', ' '),
XYZ_ = IccTag('X', 'Y', 'Z', ' '),
rXYZ = IccTag('r', 'X', 'Y', 'Z'),
gXYZ = IccTag('g', 'X', 'Y', 'Z'),
bXYZ = IccTag('b', 'X', 'Y', 'Z'),
rTRC = IccTag('r', 'T', 'R', 'C'),
gTRC = IccTag('g', 'T', 'R', 'C'),
bTRC = IccTag('b', 'T', 'R', 'C'),
A2B0 = IccTag('A', '2', 'B', '0'),
A2B1 = IccTag('A', '2', 'B', '1'),
B2A0 = IccTag('B', '2', 'A', '0'),
B2A1 = IccTag('B', '2', 'A', '1'),
desc = IccTag('d', 'e', 's', 'c'),
text = IccTag('t', 'e', 'x', 't'),
cprt = IccTag('c', 'p', 'r', 't'),
curv = IccTag('c', 'u', 'r', 'v'),
para = IccTag('p', 'a', 'r', 'a'),
wtpt = IccTag('w', 't', 'p', 't'),
bkpt = IccTag('b', 'k', 'p', 't'),
mft1 = IccTag('m', 'f', 't', '1'),
mft2 = IccTag('m', 'f', 't', '2'),
mAB_ = IccTag('m', 'A', 'B', ' '),
mBA_ = IccTag('m', 'B', 'A', ' '),
chad = IccTag('c', 'h', 'a', 'd'),
sf32 = IccTag('s', 'f', '3', '2'),
// Apple extensions for ICCv2:
aarg = IccTag('a', 'a', 'r', 'g'),
aagg = IccTag('a', 'a', 'g', 'g'),
aabg = IccTag('a', 'a', 'b', 'g'),
};
inline uint qHash(const Tag &key, uint seed = 0)
{
return qHash(quint32(key), seed);
}
namespace QIcc {
struct TagTableEntry
{
quint32_be signature;
quint32_be offset;
quint32_be size;
};
struct GenericTagData {
quint32_be type;
quint32_be null;
};
struct XYZTagData : GenericTagData {
qint32_be fixedX;
qint32_be fixedY;
qint32_be fixedZ;
};
struct CurvTagData : GenericTagData {
quint32_be valueCount;
quint16_be value[1];
};
struct ParaTagData : GenericTagData {
quint16_be curveType;
quint16_be null2;
quint32_be parameter[1];
};
// For both mAB and mBA
struct mABTagData : GenericTagData {
quint8 inputChannels;
quint8 outputChannels;
quint8 padding[2];
quint32_be bCurvesOffset;
quint32_be matrixOffset;
quint32_be mCurvesOffset;
quint32_be clutOffset;
quint32_be aCurvesOffset;
};
struct Sf32TagData : GenericTagData {
quint32_be value[1];
};
static int toFixedS1516(float x)
{
return int(x * 65536.0f + 0.5f);
}
static float fromFixedS1516(int x)
{
return x * (1.0f / 65536.0f);
}
QColorVector fromXyzData(const XYZTagData *xyz)
{
const float x = fromFixedS1516(xyz->fixedX);
const float y = fromFixedS1516(xyz->fixedY);
const float z = fromFixedS1516(xyz->fixedZ);
qCDebug(lcIcc) << "XYZ_ " << x << y << z;
return QColorVector(x, y, z);
}
static bool isValidIccProfile(const ICCProfileHeader &header)
{
if (header.signature != uint(Tag::acsp)) {
qCWarning(lcIcc, "Failed ICC signature test");
return false;
}
if (header.profileSize < (sizeof(ICCProfileHeader) + header.tagCount * sizeof(TagTableEntry))) {
qCWarning(lcIcc, "Failed basic size sanity");
return false;
}
if (header.profileClass != uint(ProfileClass::Input)
&& header.profileClass != uint(ProfileClass::Display)) {
qCWarning(lcIcc, "Unsupported ICC profile class %x", quint32(header.profileClass));
return false;
}
if (header.inputColorSpace != 0x52474220 /* 'RGB '*/) {
qCWarning(lcIcc, "Unsupported ICC input color space %x", quint32(header.inputColorSpace));
return false;
}
if (header.pcs != 0x58595a20 /* 'XYZ '*/) {
// ### support PCSLAB
qCWarning(lcIcc, "Unsupported ICC profile connection space %x", quint32(header.pcs));
return false;
}
QColorVector illuminant;
illuminant.x = fromFixedS1516(header.illuminantXyz[0]);
illuminant.y = fromFixedS1516(header.illuminantXyz[1]);
illuminant.z = fromFixedS1516(header.illuminantXyz[2]);
if (illuminant != QColorVector::D50()) {
qCWarning(lcIcc, "Invalid ICC illuminant");
return false;
}
return true;
}
static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
{
if (trc.isLinear()) {
stream << uint(Tag::curv) << uint(0);
stream << uint(0);
return 12;
}
if (trc.m_type == QColorTrc::Type::Function) {
const QColorTransferFunction &fun = trc.m_fun;
stream << uint(Tag::para) << uint(0);
if (fun.isGamma()) {
stream << ushort(0) << ushort(0);
stream << toFixedS1516(fun.m_g);
return 12 + 4;
}
bool type3 = qFuzzyIsNull(fun.m_e) && qFuzzyIsNull(fun.m_f);
stream << ushort(type3 ? 3 : 4) << ushort(0);
stream << toFixedS1516(fun.m_g);
stream << toFixedS1516(fun.m_a);
stream << toFixedS1516(fun.m_b);
stream << toFixedS1516(fun.m_c);
stream << toFixedS1516(fun.m_d);
if (type3)
return 12 + 5 * 4;
stream << toFixedS1516(fun.m_e);
stream << toFixedS1516(fun.m_f);
return 12 + 7 * 4;
}
Q_ASSERT(trc.m_type == QColorTrc::Type::Table);
stream << uint(Tag::curv) << uint(0);
stream << uint(trc.m_table.m_tableSize);
if (!trc.m_table.m_table16.isEmpty()) {
for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
stream << ushort(trc.m_table.m_table16[i]);
}
} else {
for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
stream << ushort(trc.m_table.m_table8[i] * 257U);
}
}
return 12 + 2 * trc.m_table.m_tableSize;
}
QByteArray toIccProfile(const QColorSpace &space)
{
if (!space.isValid())
return QByteArray();
const QColorSpacePrivate *spaceDPtr = space.d_func();
constexpr int tagCount = 9;
constexpr uint profileDataOffset = 128 + 4 + 12 * tagCount;
constexpr uint variableTagTableOffsets = 128 + 4 + 12 * 5;
uint currentOffset = 0;
uint rTrcOffset, gTrcOffset, bTrcOffset;
uint rTrcSize, gTrcSize, bTrcSize;
uint descOffset, descSize;
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
QDataStream stream(&buffer);
// Profile header:
stream << uint(0); // Size, we will update this later
stream << uint(0);
stream << uint(0x02400000); // Version 2.4 (note we use 'para' from version 4)
stream << uint(ProfileClass::Display);
stream << uint(Tag::RGB_);
stream << uint(Tag::XYZ_);
stream << uint(0) << uint(0) << uint(0);
stream << uint(Tag::acsp);
stream << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0);
stream << uint(1); // Rendering intent
stream << uint(0x0000f6d6); // D50 X
stream << uint(0x00010000); // D50 Y
stream << uint(0x0000d32d); // D50 Z
stream << IccTag('Q','t', QT_VERSION_MAJOR, QT_VERSION_MINOR);
stream << uint(0) << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
// Tag table:
stream << uint(tagCount);
stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(12);
// From here the offset and size will be updated later:
stream << uint(Tag::rTRC) << uint(0) << uint(0);
stream << uint(Tag::gTRC) << uint(0) << uint(0);
stream << uint(Tag::bTRC) << uint(0) << uint(0);
stream << uint(Tag::desc) << uint(0) << uint(0);
// TODO: consider adding 'chad' tag (required in ICC >=4 when we have non-D50 whitepoint)
currentOffset = profileDataOffset;
// Tag data:
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.r.x);
stream << toFixedS1516(spaceDPtr->toXyz.r.y);
stream << toFixedS1516(spaceDPtr->toXyz.r.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.g.x);
stream << toFixedS1516(spaceDPtr->toXyz.g.y);
stream << toFixedS1516(spaceDPtr->toXyz.g.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.b.x);
stream << toFixedS1516(spaceDPtr->toXyz.b.y);
stream << toFixedS1516(spaceDPtr->toXyz.b.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->whitePoint.x);
stream << toFixedS1516(spaceDPtr->whitePoint.y);
stream << toFixedS1516(spaceDPtr->whitePoint.z);
stream << uint(Tag::text) << uint(0);
stream << uint(IccTag('N', '/', 'A', '\0'));
currentOffset += 92;
// From now on the data is variable sized:
rTrcOffset = currentOffset;
rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
currentOffset += rTrcSize;
if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
gTrcOffset = rTrcOffset;
gTrcSize = rTrcSize;
} else {
gTrcOffset = currentOffset;
gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
currentOffset += gTrcSize;
}
if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
bTrcOffset = rTrcOffset;
bTrcSize = rTrcSize;
} else {
bTrcOffset = currentOffset;
bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
currentOffset += bTrcSize;
}
descOffset = currentOffset;
QByteArray description = spaceDPtr->description.toUtf8();
stream << uint(Tag::desc) << uint(0);
stream << uint(description.size() + 1);
stream.writeRawData(description.constData(), description.size() + 1);
stream << uint(0) << uint(0);
stream << ushort(0) << uchar(0);
QByteArray macdesc(67, '\0');
stream.writeRawData(macdesc.constData(), 67);
descSize = 90 + description.size() + 1;
currentOffset += descSize;
buffer.close();
QByteArray iccProfile = buffer.buffer();
// Now write final size
*(quint32_be *)iccProfile.data() = iccProfile.size();
// And the final indices and sizes of variable size tags:
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
Q_ASSERT(qsizetype(iccHeader->profileSize) == qsizetype(iccProfile.size()));
Q_ASSERT(isValidIccProfile(*iccHeader));
#endif
return iccProfile;
}
bool parseTRC(const GenericTagData *trcData, QColorTrc &gamma)
{
if (trcData->type == quint32(Tag::curv)) {
const CurvTagData *curv = reinterpret_cast<const CurvTagData *>(trcData);
qCDebug(lcIcc) << "curv" << uint(curv->valueCount);
if (curv->valueCount == 0) {
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(); // Linear
} else if (curv->valueCount == 1) {
float g = curv->value[0] * (1.0f / 256.0f);
qCDebug(lcIcc) << g;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction::fromGamma(g);
} else {
QVector<quint16> tabl;
tabl.resize(curv->valueCount);
for (uint i = 0; i < curv->valueCount; ++i)
tabl[i] = curv->value[i];
QColorTransferTable table = QColorTransferTable(curv->valueCount, std::move(tabl));
QColorTransferFunction curve;
if (!table.asColorTransferFunction(&curve)) {
gamma.m_type = QColorTrc::Type::Table;
gamma.m_table = table;
} else {
qCDebug(lcIcc) << "Detected curv table as function";
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = curve;
}
}
return true;
}
if (trcData->type == quint32(Tag::para)) {
const ParaTagData *para = reinterpret_cast<const ParaTagData *>(trcData);
qCDebug(lcIcc) << "para" << uint(para->curveType);
switch (para->curveType) {
case 0: {
float g = fromFixedS1516(para->parameter[0]);
qCDebug(lcIcc) << g;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction::fromGamma(g);
break;
}
case 1: {
float g = fromFixedS1516(para->parameter[0]);
float a = fromFixedS1516(para->parameter[1]);
float b = fromFixedS1516(para->parameter[2]);
float d = -b / a;
qCDebug(lcIcc) << g << a << b;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
break;
}
case 2: {
float g = fromFixedS1516(para->parameter[0]);
float a = fromFixedS1516(para->parameter[1]);
float b = fromFixedS1516(para->parameter[2]);
float c = fromFixedS1516(para->parameter[3]);
float d = -b / a;
qCDebug(lcIcc) << g << a << b << c;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
break;
}
case 3: {
float g = fromFixedS1516(para->parameter[0]);
float a = fromFixedS1516(para->parameter[1]);
float b = fromFixedS1516(para->parameter[2]);
float c = fromFixedS1516(para->parameter[3]);
float d = fromFixedS1516(para->parameter[4]);
qCDebug(lcIcc) << g << a << b << c << d;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
break;
}
case 4: {
float g = fromFixedS1516(para->parameter[0]);
float a = fromFixedS1516(para->parameter[1]);
float b = fromFixedS1516(para->parameter[2]);
float c = fromFixedS1516(para->parameter[3]);
float d = fromFixedS1516(para->parameter[4]);
float e = fromFixedS1516(para->parameter[5]);
float f = fromFixedS1516(para->parameter[6]);
qCDebug(lcIcc) << g << a << b << c << d << e << f;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
break;
}
default:
qCWarning(lcIcc) << "Unknown para type" << uint(para->curveType);
return false;
}
return true;
}
qCWarning(lcIcc) << "Invalid TRC data type";
return false;
}
bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
{
if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
qCWarning(lcIcc) << "fromIccProfile: failed size sanity 1";
return false;
}
const ICCProfileHeader *header = (const ICCProfileHeader *)data.constData();
if (!isValidIccProfile(*header)) {
qCWarning(lcIcc) << "fromIccProfile: failed general sanity check";
return false;
}
if (qsizetype(header->profileSize) > data.size()) {
qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
return false;
}
// Read tag index
const TagTableEntry *tagTable = (const TagTableEntry *)(data.constData() + sizeof(ICCProfileHeader));
const qsizetype offsetToData = sizeof(ICCProfileHeader) + header->tagCount * sizeof(TagTableEntry);
QHash<Tag, quint32> tagIndex;
for (uint i = 0; i < header->tagCount; ++i) {
// Sanity check tag sizes and offsets:
if (qsizetype(tagTable[i].offset) < offsetToData) {
qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1";
return false;
}
// Checked separately from (+ size) to handle overflow.
if (tagTable[i].offset > header->profileSize) {
qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
return false;
}
if ((tagTable[i].offset + tagTable[i].size) > header->profileSize) {
qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity";
return false;
}
// printf("'%4s' %d %d\n", (const char *)&tagTable[i].signature,
// quint32(tagTable[i].offset),
// quint32(tagTable[i].size));
tagIndex.insert(Tag(quint32(tagTable[i].signature)), tagTable[i].offset);
}
// Check the profile is three-component matrix based (what we currently support):
if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
!tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
!tagIndex.contains(Tag::wtpt)) {
qCWarning(lcIcc) << "fromIccProfile: Unsupported ICC profile - not three component matrix based";
return false;
}
// Parse XYZ tags
const XYZTagData *rXyz = (const XYZTagData *)(data.constData() + tagIndex[Tag::rXYZ]);
const XYZTagData *gXyz = (const XYZTagData *)(data.constData() + tagIndex[Tag::gXYZ]);
const XYZTagData *bXyz = (const XYZTagData *)(data.constData() + tagIndex[Tag::bXYZ]);
const XYZTagData *wXyz = (const XYZTagData *)(data.constData() + tagIndex[Tag::wtpt]);
if (rXyz->type != quint32(Tag::XYZ_) || gXyz->type != quint32(Tag::XYZ_) ||
wXyz->type != quint32(Tag::XYZ_) || wXyz->type != quint32(Tag::XYZ_)) {
qCWarning(lcIcc) << "fromIccProfile: Bad XYZ data type";
return false;
}
QColorSpacePrivate *colorspaceDPtr = colorSpace->d_func();
colorspaceDPtr->toXyz.r = fromXyzData(rXyz);
colorspaceDPtr->toXyz.g = fromXyzData(gXyz);
colorspaceDPtr->toXyz.b = fromXyzData(bXyz);
QColorVector whitePoint = fromXyzData(wXyz);
colorspaceDPtr->whitePoint = whitePoint;
colorspaceDPtr->gamut = QColorSpace::Gamut::Custom;
if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
qCDebug(lcIcc) << "fromIccProfile: sRGB gamut detected";
colorspaceDPtr->gamut = QColorSpace::Gamut::SRgb;
} else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
qCDebug(lcIcc) << "fromIccProfile: Adobe RGB gamut detected";
colorspaceDPtr->gamut = QColorSpace::Gamut::AdobeRgb;
} else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 gamut detected";
colorspaceDPtr->gamut = QColorSpace::Gamut::DciP3D65;
} else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromBt2020()) {
qCDebug(lcIcc) << "fromIccProfile: BT.2020 gamut detected";
colorspaceDPtr->gamut = QColorSpace::Gamut::Bt2020;
}
if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB gamut detected";
colorspaceDPtr->gamut = QColorSpace::Gamut::ProPhotoRgb;
}
// Reset the matrix to our canonical values:
if (colorspaceDPtr->gamut != QColorSpace::Gamut::Custom)
colorspaceDPtr->setToXyzMatrix();
// Parse TRC tags
const GenericTagData *rTrc;
const GenericTagData *gTrc;
const GenericTagData *bTrc;
if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) {
// Apple extension for parametric version of TRCs in ICCv2:
rTrc = (const GenericTagData *)(data.constData() + tagIndex[Tag::aarg]);
gTrc = (const GenericTagData *)(data.constData() + tagIndex[Tag::aagg]);
bTrc = (const GenericTagData *)(data.constData() + tagIndex[Tag::aabg]);
} else {
rTrc = (const GenericTagData *)(data.constData() + tagIndex[Tag::rTRC]);
gTrc = (const GenericTagData *)(data.constData() + tagIndex[Tag::gTRC]);
bTrc = (const GenericTagData *)(data.constData() + tagIndex[Tag::bTRC]);
}
QColorTrc rCurve;
QColorTrc gCurve;
QColorTrc bCurve;
if (!parseTRC(rTrc, rCurve))
return false;
if (!parseTRC(gTrc, gCurve))
return false;
if (!parseTRC(bTrc, bCurve))
return false;
if (rCurve == gCurve && gCurve == bCurve && rCurve.m_type == QColorTrc::Type::Function) {
if (rCurve.m_fun.isLinear()) {
qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction();
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
colorspaceDPtr->gamma = 1.0f;
} else if (rCurve.m_fun.isGamma()) {
qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
colorspaceDPtr->gamma = rCurve.m_fun.m_g;
} else if (rCurve.m_fun.isSRgb()) {
qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
} else {
colorspaceDPtr->trc[0] = rCurve;
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
}
colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
} else {
colorspaceDPtr->trc[0] = rCurve;
colorspaceDPtr->trc[1] = gCurve;
colorspaceDPtr->trc[2] = bCurve;
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
}
// FIXME: try to parse the description..
if (!colorspaceDPtr->identifyColorSpace())
colorspaceDPtr->id = QColorSpace::Unknown;
else
qCDebug(lcIcc) << "fromIccProfile: Named colorspace detected: " << colorSpace->colorSpaceId();
colorspaceDPtr->iccProfile = data;
return true;
}
} // namespace QIcc
QT_END_NAMESPACE

70
src/gui/painting/qicc_p.h Normal file
View File

@ -0,0 +1,70 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QICC_P_H
#define QICC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/qbytearray.h>
#include <QtGui/qtguiglobal.h>
QT_BEGIN_NAMESPACE
class QColorSpace;
namespace QIcc {
Q_GUI_EXPORT bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace);
Q_GUI_EXPORT QByteArray toIccProfile(const QColorSpace &space);
}
QT_END_NAMESPACE
#endif // QICC_P_H

View File

@ -54,6 +54,8 @@
#include <QtCore/qvarlengtharray.h>
#include <QtGui/private/qtguiglobal_p.h>
#include "QtGui/qbrush.h"
#include "QtGui/qcolorspace.h"
#include "QtGui/qcolortransform.h"
#include "QtGui/qfont.h"
#include "QtGui/qpen.h"
#include "QtGui/qregion.h"

View File

@ -40,10 +40,14 @@
#include "qjpeghandler_p.h"
#include <qimage.h>
#include <qcolorspace.h>
#include <qcolortransform.h>
#include <qdebug.h>
#include <qvariant.h>
#include <qvector.h>
#include <qbuffer.h>
#include <qmath.h>
#include <private/qicc_p.h>
#include <private/qsimd_p.h>
#include <private/qimage_p.h> // for qt_getImageText
@ -725,6 +729,7 @@ public:
QRect clipRect;
QString description;
QStringList readTexts;
QByteArray iccProfile;
struct jpeg_decompress_struct info;
struct my_jpeg_source_mgr * iod_src;
@ -887,6 +892,7 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
if (!setjmp(err.setjmp_buffer)) {
jpeg_save_markers(&info, JPEG_COM, 0xFFFF);
jpeg_save_markers(&info, JPEG_APP0 + 1, 0xFFFF); // Exif uses APP1 marker
jpeg_save_markers(&info, JPEG_APP0 + 2, 0xFFFF); // ICC uses APP2 marker
(void) jpeg_read_header(&info, TRUE);
@ -919,6 +925,10 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
readTexts.append(value);
} else if (marker->marker == JPEG_APP0 + 1) {
exifData.append((const char*)marker->data, marker->data_length);
} else if (marker->marker == JPEG_APP0 + 2) {
if (marker->data_length > 128 + 4 + 14 && strcmp((const char *)marker->data, "ICC_PROFILE") == 0) {
iccProfile.append((const char*)marker->data + 14, marker->data_length - 14);
}
}
}
@ -954,6 +964,9 @@ bool QJpegHandlerPrivate::read(QImage *image)
for (int i = 0; i < readTexts.size()-1; i+=2)
image->setText(readTexts.at(i), readTexts.at(i+1));
if (!iccProfile.isEmpty())
image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
state = ReadingEnd;
return true;
}
@ -962,7 +975,6 @@ bool QJpegHandlerPrivate::read(QImage *image)
}
return false;
}
Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len);

View File

@ -3,6 +3,7 @@ SUBDIRS=\
qpainterpath \
qpainterpathstroker \
qcolor \
qcolorspace \
qbrush \
qregion \
qpagelayout \

View File

@ -33,7 +33,7 @@
#include <qcolor.h>
#include <qdebug.h>
#include <private/qcolorprofile_p.h>
#include <private/qcolortrclut_p.h>
#include <private/qdrawingprimitive_sse2_p.h>
#include <qrgba64.h>
@ -1632,14 +1632,13 @@ void tst_QColor::qcolorprofile_data()
QTest::newRow("gamma=1.7") << qreal(1.7) << 2;
QTest::newRow("gamma=2.0") << qreal(2.0) << 8;
QTest::newRow("gamma=2.31") << qreal(2.31) << 33;
QTest::newRow("SRgb") << qreal(0.0) << 7;
}
void tst_QColor::qcolorprofile()
{
QFETCH(qreal, gammaC);
QFETCH(int, tolerance);
QColorProfile *cp = (gammaC == 0) ? QColorProfile::fromSRgb(): QColorProfile::fromGamma(gammaC);
QColorTrcLut *cp = QColorTrcLut::fromGamma(gammaC);
// Test we are accurate for most values after converting through gamma-correction.
int error = 0;

View File

@ -0,0 +1,9 @@
CONFIG += testcase
QT += testlib gui-private core-private
TARGET = tst_qcolorspace
SOURCES += tst_qcolorspace.cpp
RESOURCES += $$files(resources/*)
TESTDATA += resources/*

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,238 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <qcolorspace.h>
#include <qimage.h>
#include <qimagereader.h>
Q_DECLARE_METATYPE(QColorSpace::ColorSpaceId)
Q_DECLARE_METATYPE(QColorSpace::Gamut)
Q_DECLARE_METATYPE(QColorSpace::TransferFunction)
class tst_QColorSpace : public QObject
{
Q_OBJECT
public:
tst_QColorSpace();
private slots:
void namedColorSpaces_data();
void namedColorSpaces();
void toIccProfile_data();
void toIccProfile();
void fromIccProfile();
void imageConversion_data();
void imageConversion();
void loadImage();
};
tst_QColorSpace::tst_QColorSpace()
{ }
void tst_QColorSpace::namedColorSpaces_data()
{
QTest::addColumn<QColorSpace::ColorSpaceId>("colorSpaceId");
QTest::addColumn<QColorSpace::Gamut>("gamutId");
QTest::addColumn<QColorSpace::TransferFunction>("transferFunctionId");
QTest::newRow("sRGB") << QColorSpace::SRgb
<< QColorSpace::Gamut::SRgb
<< QColorSpace::TransferFunction::SRgb;
QTest::newRow("sRGB Linear") << QColorSpace::SRgbLinear
<< QColorSpace::Gamut::SRgb
<< QColorSpace::TransferFunction::Linear;
QTest::newRow("Adobe RGB") << QColorSpace::AdobeRgb
<< QColorSpace::Gamut::AdobeRgb
<< QColorSpace::TransferFunction::Gamma;
QTest::newRow("Display-P3") << QColorSpace::DisplayP3
<< QColorSpace::Gamut::DciP3D65
<< QColorSpace::TransferFunction::SRgb;
QTest::newRow("ProPhoto RGB") << QColorSpace::ProPhotoRgb
<< QColorSpace::Gamut::ProPhotoRgb
<< QColorSpace::TransferFunction::ProPhotoRgb;
QTest::newRow("BT.2020") << QColorSpace::Bt2020
<< QColorSpace::Gamut::Bt2020
<< QColorSpace::TransferFunction::Bt2020;
}
void tst_QColorSpace::namedColorSpaces()
{
QFETCH(QColorSpace::ColorSpaceId, colorSpaceId);
QFETCH(QColorSpace::Gamut, gamutId);
QFETCH(QColorSpace::TransferFunction, transferFunctionId);
QColorSpace colorSpace = colorSpaceId;
QVERIFY(colorSpace.isValid());
QCOMPARE(colorSpace.colorSpaceId(), colorSpaceId);
QCOMPARE(colorSpace.gamut(), gamutId);
QCOMPARE(colorSpace.transferFunction(), transferFunctionId);
}
void tst_QColorSpace::toIccProfile_data()
{
namedColorSpaces_data();
}
void tst_QColorSpace::toIccProfile()
{
QFETCH(QColorSpace::ColorSpaceId, colorSpaceId);
QFETCH(QColorSpace::Gamut, gamutId);
QFETCH(QColorSpace::TransferFunction, transferFunctionId);
Q_UNUSED(gamutId);
Q_UNUSED(transferFunctionId);
QColorSpace colorSpace = colorSpaceId;
QByteArray iccProfile = colorSpace.iccProfile();
QVERIFY(!iccProfile.isEmpty());
QColorSpace colorSpace2 = QColorSpace::fromIccProfile(iccProfile);
QVERIFY(colorSpace2.isValid());
QCOMPARE(colorSpace2, colorSpace);
QByteArray iccProfile2 = colorSpace2.iccProfile();
QVERIFY(!iccProfile2.isEmpty());
QCOMPARE(iccProfile2, iccProfile);
}
void tst_QColorSpace::fromIccProfile()
{
// Read the official sRGB ICCv2 profile:
QString prefix = QFINDTESTDATA("resources/");
QFile file(prefix + "sRGB2014.icc");
file.open(QIODevice::ReadOnly);
QByteArray iccProfile = file.readAll();
QColorSpace stdSRgb = QColorSpace::fromIccProfile(iccProfile);
QVERIFY(stdSRgb.isValid());
QCOMPARE(stdSRgb.gamut(), QColorSpace::Gamut::SRgb);
QCOMPARE(stdSRgb.transferFunction(), QColorSpace::TransferFunction::SRgb);
QCOMPARE(stdSRgb.colorSpaceId(), QColorSpace::SRgb);
QCOMPARE(stdSRgb, QColorSpace(QColorSpace::SRgb));
}
void tst_QColorSpace::imageConversion_data()
{
QTest::addColumn<QColorSpace::ColorSpaceId>("fromColorSpace");
QTest::addColumn<QColorSpace::ColorSpaceId>("toColorSpace");
QTest::addColumn<int>("tolerance");
QTest::newRow("sRGB -> Display-P3") << QColorSpace::SRgb << QColorSpace::DisplayP3 << 0;
QTest::newRow("sRGB -> Adobe RGB") << QColorSpace::SRgb << QColorSpace::AdobeRgb << 2;
QTest::newRow("Display-P3 -> sRGB") << QColorSpace::DisplayP3 << QColorSpace::SRgb << 0;
QTest::newRow("Adobe RGB -> sRGB") << QColorSpace::AdobeRgb << QColorSpace::SRgb << 2;
QTest::newRow("Display-P3 -> Adobe RGB") << QColorSpace::DisplayP3 << QColorSpace::AdobeRgb << 2;
QTest::newRow("Display-P3 -> BT.2020") << QColorSpace::DisplayP3 << QColorSpace::Bt2020 << 4;
QTest::newRow("sRGB -> sRGB Linear") << QColorSpace::SRgb << QColorSpace::SRgbLinear << 0;
}
void tst_QColorSpace::imageConversion()
{
QFETCH(QColorSpace::ColorSpaceId, fromColorSpace);
QFETCH(QColorSpace::ColorSpaceId, toColorSpace);
QFETCH(int, tolerance);
QImage testImage(256, 1, QImage::Format_RGB32);
for (int i = 0; i < 256; ++i)
testImage.setPixel(i, 0, qRgb(i, i, i));
testImage.setColorSpace(fromColorSpace);
QCOMPARE(testImage.colorSpace(), QColorSpace(fromColorSpace));
testImage.convertToColorSpace(toColorSpace);
QCOMPARE(testImage.colorSpace(), QColorSpace(toColorSpace));
int lastRed = 0;
int lastGreen = 0;
int lastBlue = 0;
for (int i = 0; i < 256; ++i) {
QRgb p = testImage.pixel(i, 0);
QVERIFY(qRed(p) >= lastRed);
QVERIFY(qGreen(p) >= lastGreen);
QVERIFY(qBlue(p) >= lastBlue);
lastRed = qRed(p);
lastGreen = qGreen(p);
lastBlue = qBlue(p);
}
lastRed = 0;
lastGreen = 0;
lastBlue = 0;
testImage.convertToColorSpace(fromColorSpace);
QCOMPARE(testImage.colorSpace(), QColorSpace(fromColorSpace));
for (int i = 0; i < 256; ++i) {
QRgb p = testImage.pixel(i, 0);
QVERIFY(qAbs(qRed(p) - qGreen(p)) <= tolerance);
QVERIFY(qAbs(qRed(p) - qBlue(p)) <= tolerance);
QVERIFY((lastRed - qRed(p)) <= (tolerance / 2));
QVERIFY((lastGreen - qGreen(p)) <= (tolerance / 2));
QVERIFY((lastBlue - qBlue(p)) <= (tolerance / 2));
lastRed = qRed(p);
lastGreen = qGreen(p);
lastBlue = qBlue(p);
}
}
void tst_QColorSpace::loadImage()
{
QString prefix = QFINDTESTDATA("resources/");
QImageReader reader(prefix + "ProPhoto.jpg");
QImage image = reader.read();
QVERIFY(!image.isNull());
QVERIFY(image.colorSpace().isValid());
QCOMPARE(image.colorSpace().colorSpaceId(), QColorSpace::ProPhotoRgb);
QVERIFY(!image.colorSpace().iccProfile().isEmpty());
QColorSpace defaultProPhotoRgb = QColorSpace::ProPhotoRgb;
QVERIFY(!defaultProPhotoRgb.iccProfile().isEmpty());
// Test the iccProfile getter returns the ICC profile from the image
// which since we didn't write it, isn't identical to our defaults.
QVERIFY(defaultProPhotoRgb.iccProfile() != image.colorSpace().iccProfile());
}
QTEST_MAIN(tst_QColorSpace)
#include "tst_qcolorspace.moc"