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:
parent
17512d497d
commit
90a8de656f
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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...
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 \
|
||||
|
214
src/gui/painting/qcolormatrix_p.h
Normal file
214
src/gui/painting/qcolormatrix_p.h
Normal 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
|
633
src/gui/painting/qcolorspace.cpp
Normal file
633
src/gui/painting/qcolorspace.cpp
Normal 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
|
136
src/gui/painting/qcolorspace.h
Normal file
136
src/gui/painting/qcolorspace.h
Normal 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
|
96
src/gui/painting/qcolorspace_p.h
Normal file
96
src/gui/painting/qcolorspace_p.h
Normal 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
|
207
src/gui/painting/qcolortransferfunction_p.h
Normal file
207
src/gui/painting/qcolortransferfunction_p.h
Normal 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
|
245
src/gui/painting/qcolortransfertable_p.h
Normal file
245
src/gui/painting/qcolortransfertable_p.h
Normal 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
|
679
src/gui/painting/qcolortransform.cpp
Normal file
679
src/gui/painting/qcolortransform.cpp
Normal 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
|
93
src/gui/painting/qcolortransform.h
Normal file
93
src/gui/painting/qcolortransform.h
Normal 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
|
89
src/gui/painting/qcolortransform_p.h
Normal file
89
src/gui/painting/qcolortransform_p.h
Normal 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
|
129
src/gui/painting/qcolortrc_p.h
Normal file
129
src/gui/painting/qcolortrc_p.h
Normal 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
|
@ -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;
|
@ -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
|
@ -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
669
src/gui/painting/qicc.cpp
Normal 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
70
src/gui/painting/qicc_p.h
Normal 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
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -3,6 +3,7 @@ SUBDIRS=\
|
||||
qpainterpath \
|
||||
qpainterpathstroker \
|
||||
qcolor \
|
||||
qcolorspace \
|
||||
qbrush \
|
||||
qregion \
|
||||
qpagelayout \
|
||||
|
@ -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;
|
||||
|
9
tests/auto/gui/painting/qcolorspace/qcolorspace.pro
Normal file
9
tests/auto/gui/painting/qcolorspace/qcolorspace.pro
Normal 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/*
|
BIN
tests/auto/gui/painting/qcolorspace/resources/ProPhoto.jpg
Normal file
BIN
tests/auto/gui/painting/qcolorspace/resources/ProPhoto.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
tests/auto/gui/painting/qcolorspace/resources/sRGB2014.icc
Normal file
BIN
tests/auto/gui/painting/qcolorspace/resources/sRGB2014.icc
Normal file
Binary file not shown.
238
tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp
Normal file
238
tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp
Normal 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"
|
Loading…
Reference in New Issue
Block a user