From 90a8de656fe689b6aa856e70e2d22de6630ea855 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Tue, 26 Jun 2018 17:12:02 +0200 Subject: 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 --- src/gui/painting/painting.pri | 16 +- src/gui/painting/qcolormatrix_p.h | 214 +++++++++ src/gui/painting/qcolorprofile.cpp | 87 ---- src/gui/painting/qcolorprofile_p.h | 245 ---------- src/gui/painting/qcolorspace.cpp | 633 ++++++++++++++++++++++++++ src/gui/painting/qcolorspace.h | 136 ++++++ src/gui/painting/qcolorspace_p.h | 96 ++++ src/gui/painting/qcolortransferfunction_p.h | 207 +++++++++ src/gui/painting/qcolortransfertable_p.h | 245 ++++++++++ src/gui/painting/qcolortransform.cpp | 679 ++++++++++++++++++++++++++++ src/gui/painting/qcolortransform.h | 93 ++++ src/gui/painting/qcolortransform_p.h | 89 ++++ src/gui/painting/qcolortrc_p.h | 129 ++++++ src/gui/painting/qcolortrclut.cpp | 86 ++++ src/gui/painting/qcolortrclut_p.h | 287 ++++++++++++ src/gui/painting/qdrawhelper.cpp | 22 +- src/gui/painting/qicc.cpp | 669 +++++++++++++++++++++++++++ src/gui/painting/qicc_p.h | 70 +++ src/gui/painting/qpainter_p.h | 2 + 19 files changed, 3660 insertions(+), 345 deletions(-) create mode 100644 src/gui/painting/qcolormatrix_p.h delete mode 100644 src/gui/painting/qcolorprofile.cpp delete mode 100644 src/gui/painting/qcolorprofile_p.h create mode 100644 src/gui/painting/qcolorspace.cpp create mode 100644 src/gui/painting/qcolorspace.h create mode 100644 src/gui/painting/qcolorspace_p.h create mode 100644 src/gui/painting/qcolortransferfunction_p.h create mode 100644 src/gui/painting/qcolortransfertable_p.h create mode 100644 src/gui/painting/qcolortransform.cpp create mode 100644 src/gui/painting/qcolortransform.h create mode 100644 src/gui/painting/qcolortransform_p.h create mode 100644 src/gui/painting/qcolortrc_p.h create mode 100644 src/gui/painting/qcolortrclut.cpp create mode 100644 src/gui/painting/qcolortrclut_p.h create mode 100644 src/gui/painting/qicc.cpp create mode 100644 src/gui/painting/qicc_p.h (limited to 'src/gui/painting') diff --git a/src/gui/painting/painting.pri b/src/gui/painting/painting.pri index c3585a4647..0e2dfed9ab 100644 --- a/src/gui/painting/painting.pri +++ b/src/gui/painting/painting.pri @@ -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 \ diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h new file mode 100644 index 0000000000..3d1dca6222 --- /dev/null +++ b/src/gui/painting/qcolormatrix_p.h @@ -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 +#include + +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 diff --git a/src/gui/painting/qcolorprofile.cpp b/src/gui/painting/qcolorprofile.cpp deleted file mode 100644 index 3b7b0a248b..0000000000 --- a/src/gui/painting/qcolorprofile.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** -** 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 "qcolorprofile_p.h" -#include - -QT_BEGIN_NAMESPACE - -QColorProfile *QColorProfile::fromGamma(qreal gamma) -{ - QColorProfile *cp = new QColorProfile; - - for (int i = 0; i <= (255 * 16); ++i) { - cp->m_toLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), gamma) * (255 * 256))); - cp->m_fromLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), qreal(1) / gamma) * (255 * 256))); - } - - return cp; -} - -static qreal srgbToLinear(qreal v) -{ - 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; - - 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))); - } - - return cp; -} - -QT_END_NAMESPACE diff --git a/src/gui/painting/qcolorprofile_p.h b/src/gui/painting/qcolorprofile_p.h deleted file mode 100644 index 425e9abace..0000000000 --- a/src/gui/painting/qcolorprofile_p.h +++ /dev/null @@ -1,245 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QCOLORPROFILE_P_H -#define QCOLORPROFILE_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 -#include -#include - -#if defined(__SSE2__) -#include -#elif defined(__ARM_NEON__) || defined(__ARM_NEON) -#include -#endif -QT_BEGIN_NAMESPACE - -class Q_GUI_EXPORT QColorProfile -{ -public: - static QColorProfile *fromGamma(qreal gamma); - static QColorProfile *fromSRgb(); - - // The following methods all convert opaque or unpremultiplied colors: - - QRgba64 toLinear64(QRgb rgb32) const - { -#if defined(__SSE2__) - __m128i v = _mm_cvtsi32_si128(rgb32); - v = _mm_unpacklo_epi8(v, _mm_setzero_si128()); - const __m128i vidx = _mm_slli_epi16(v, 4); - const int ridx = _mm_extract_epi16(vidx, 2); - const int gidx = _mm_extract_epi16(vidx, 1); - const int bidx = _mm_extract_epi16(vidx, 0); - v = _mm_slli_epi16(v, 8); // a * 256 - v = _mm_insert_epi16(v, m_toLinear[ridx], 0); - v = _mm_insert_epi16(v, m_toLinear[gidx], 1); - v = _mm_insert_epi16(v, m_toLinear[bidx], 2); - v = _mm_add_epi16(v, _mm_srli_epi16(v, 8)); - QRgba64 rgba64; - _mm_storel_epi64(reinterpret_cast<__m128i *>(&rgba64), v); - return rgba64; -#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN - uint8x8_t v8 = vreinterpret_u8_u32(vmov_n_u32(rgb32)); - uint16x4_t v16 = vget_low_u16(vmovl_u8(v8)); - const uint16x4_t vidx = vshl_n_u16(v16, 4); - const int ridx = vget_lane_u16(vidx, 2); - const int gidx = vget_lane_u16(vidx, 1); - const int bidx = vget_lane_u16(vidx, 0); - v16 = vshl_n_u16(v16, 8); // a * 256 - v16 = vset_lane_u16(m_toLinear[ridx], v16, 0); - v16 = vset_lane_u16(m_toLinear[gidx], v16, 1); - v16 = vset_lane_u16(m_toLinear[bidx], v16, 2); - v16 = vadd_u16(v16, vshr_n_u16(v16, 8)); - return QRgba64::fromRgba64(vget_lane_u64(vreinterpret_u64_u16(v16), 0)); -#else - uint r = m_toLinear[qRed(rgb32) << 4]; - uint g = m_toLinear[qGreen(rgb32) << 4]; - uint b = m_toLinear[qBlue(rgb32) << 4]; - r = r + (r >> 8); - g = g + (g >> 8); - b = b + (b >> 8); - return QRgba64::fromRgba64(r, g, b, qAlpha(rgb32) * 257); -#endif - } - - QRgb toLinear(QRgb rgb32) const - { - return convertWithTable(rgb32, m_toLinear); - } - - QRgba64 toLinear(QRgba64 rgb64) const - { - return convertWithTable(rgb64, m_toLinear); - } - - QRgb fromLinear64(QRgba64 rgb64) const - { -#if defined(__SSE2__) - __m128i v = _mm_loadl_epi64(reinterpret_cast(&rgb64)); - v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8)); - const __m128i vidx = _mm_srli_epi16(v, 4); - const int ridx = _mm_extract_epi16(vidx, 0); - const int gidx = _mm_extract_epi16(vidx, 1); - const int bidx = _mm_extract_epi16(vidx, 2); - v = _mm_insert_epi16(v, m_fromLinear[ridx], 2); - v = _mm_insert_epi16(v, m_fromLinear[gidx], 1); - v = _mm_insert_epi16(v, m_fromLinear[bidx], 0); - v = _mm_add_epi16(v, _mm_set1_epi16(0x80)); - v = _mm_srli_epi16(v, 8); - v = _mm_packus_epi16(v, v); - return _mm_cvtsi128_si32(v); -#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN - uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64)); - v = vsub_u16(v, vshr_n_u16(v, 8)); - const uint16x4_t vidx = vshr_n_u16(v, 4); - const int ridx = vget_lane_u16(vidx, 0); - const int gidx = vget_lane_u16(vidx, 1); - const int bidx = vget_lane_u16(vidx, 2); - v = vset_lane_u16(m_fromLinear[ridx], v, 2); - v = vset_lane_u16(m_fromLinear[gidx], v, 1); - v = vset_lane_u16(m_fromLinear[bidx], v, 0); - uint8x8_t v8 = vrshrn_n_u16(vcombine_u16(v, v), 8); - return vget_lane_u32(vreinterpret_u32_u8(v8), 0); -#else - uint a = rgb64.alpha(); - uint r = rgb64.red(); - uint g = rgb64.green(); - uint b = rgb64.blue(); - a = a - (a >> 8); - r = r - (r >> 8); - g = g - (g >> 8); - b = b - (b >> 8); - a = (a + 0x80) >> 8; - r = (m_fromLinear[r >> 4] + 0x80) >> 8; - g = (m_fromLinear[g >> 4] + 0x80) >> 8; - b = (m_fromLinear[b >> 4] + 0x80) >> 8; - return (a << 24) | (r << 16) | (g << 8) | b; -#endif - } - - QRgb fromLinear(QRgb rgb32) const - { - return convertWithTable(rgb32, m_fromLinear); - } - - QRgba64 fromLinear(QRgba64 rgb64) const - { - return convertWithTable(rgb64, m_fromLinear); - } - -private: - QColorProfile() { } - - Q_ALWAYS_INLINE static QRgb convertWithTable(QRgb rgb32, const ushort *table) - { - const int r = (table[qRed(rgb32) << 4] + 0x80) >> 8; - const int g = (table[qGreen(rgb32) << 4] + 0x80) >> 8; - const int b = (table[qBlue(rgb32) << 4] + 0x80) >> 8; - return (rgb32 & 0xff000000) | (r << 16) | (g << 8) | b; - } - Q_ALWAYS_INLINE static QRgba64 convertWithTable(QRgba64 rgb64, const ushort *table) - { -#if defined(__SSE2__) - __m128i v = _mm_loadl_epi64(reinterpret_cast(&rgb64)); - v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8)); - const __m128i vidx = _mm_srli_epi16(v, 4); - const int ridx = _mm_extract_epi16(vidx, 2); - const int gidx = _mm_extract_epi16(vidx, 1); - const int bidx = _mm_extract_epi16(vidx, 0); - v = _mm_insert_epi16(v, table[ridx], 2); - v = _mm_insert_epi16(v, table[gidx], 1); - v = _mm_insert_epi16(v, table[bidx], 0); - v = _mm_add_epi16(v, _mm_srli_epi16(v, 8)); - QRgba64 rgba64; - _mm_storel_epi64(reinterpret_cast<__m128i *>(&rgba64), v); - return rgba64; -#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN - uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64)); - v = vsub_u16(v, vshr_n_u16(v, 8)); - const uint16x4_t vidx = vshr_n_u16(v, 4); - const int ridx = vget_lane_u16(vidx, 2); - const int gidx = vget_lane_u16(vidx, 1); - const int bidx = vget_lane_u16(vidx, 0); - v = vset_lane_u16(table[ridx], v, 2); - v = vset_lane_u16(table[gidx], v, 1); - v = vset_lane_u16(table[bidx], v, 0); - v = vadd_u16(v, vshr_n_u16(v, 8)); - return QRgba64::fromRgba64(vget_lane_u64(vreinterpret_u64_u16(v), 0)); -#else - ushort r = rgb64.red(); - ushort g = rgb64.green(); - ushort b = rgb64.blue(); - r = r - (r >> 8); - g = g - (g >> 8); - b = b - (b >> 8); - r = table[r >> 4]; - g = table[g >> 4]; - b = table[b >> 4]; - r = r + (r >> 8); - g = g + (g >> 8); - b = b + (b >> 8); - 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 diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp new file mode 100644 index 0000000000..24785f7b61 --- /dev/null +++ b/src/gui/painting/qcolorspace.cpp @@ -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 +#include + +#include + +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 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 diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h new file mode 100644 index 0000000000..923546ec6f --- /dev/null +++ b/src/gui/painting/qcolorspace.h @@ -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 +#include +#include + +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 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 diff --git a/src/gui/painting/qcolorspace_p.h b/src/gui/painting/qcolorspace_p.h new file mode 100644 index 0000000000..91107a9a89 --- /dev/null +++ b/src/gui/painting/qcolorspace_p.h @@ -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 + +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 lut[3]; + mutable QAtomicInt lutsGenerated; +}; + +QT_END_NAMESPACE + +#endif // QCOLORSPACE_P_H diff --git a/src/gui/painting/qcolortransferfunction_p.h b/src/gui/painting/qcolortransferfunction_p.h new file mode 100644 index 0000000000..fd7cfa2b2b --- /dev/null +++ b/src/gui/painting/qcolortransferfunction_p.h @@ -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 + +#include + +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 diff --git a/src/gui/painting/qcolortransfertable_p.h b/src/gui/painting/qcolortransfertable_p.h new file mode 100644 index 0000000000..c8b2f7bd92 --- /dev/null +++ b/src/gui/painting/qcolortransfertable_p.h @@ -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 +#include "qcolortransferfunction_p.h" + +#include +#include + +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 &table) noexcept + : m_tableSize(size) + , m_table8(table) + { } + QColorTransferTable(uint32_t size, const QVector &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 m_table8; + QVector 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 diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp new file mode 100644 index 0000000000..b677c4b36b --- /dev/null +++ b/src/gui/painting/qcolortransform.cpp @@ -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 +#include +#include +#include +#include + +#include + +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 +static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr); +template +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 +static inline void loadP(const T &p, __m128i &v); + +template<> +inline void loadP(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(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 +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(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 +static inline void loadPU(const T &p, __m128i &v); + +template<> +inline void loadPU(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(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 +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(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(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(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(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(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 +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(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(dst, src, count, flags); +} + + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolortransform.h b/src/gui/painting/qcolortransform.h new file mode 100644 index 0000000000..9274387b97 --- /dev/null +++ b/src/gui/painting/qcolortransform.h @@ -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 +#include +#include + +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 d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QCOLORTRANSFORM_H diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h new file mode 100644 index 0000000000..74a1e7fe0a --- /dev/null +++ b/src/gui/painting/qcolortransform_p.h @@ -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 colorSpaceIn; + QExplicitlySharedDataPointer 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 + void apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const; +}; + +QT_END_NAMESPACE + +#endif // QCOLORTRANSFORM_P_H diff --git a/src/gui/painting/qcolortrc_p.h b/src/gui/painting/qcolortrc_p.h new file mode 100644 index 0000000000..3a649f3756 --- /dev/null +++ b/src/gui/painting/qcolortrc_p.h @@ -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 +#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 diff --git a/src/gui/painting/qcolortrclut.cpp b/src/gui/painting/qcolortrclut.cpp new file mode 100644 index 0000000000..268d7252b4 --- /dev/null +++ b/src/gui/painting/qcolortrclut.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 "qcolortrclut_p.h" +#include "qcolortransferfunction_p.h" +#include "qcolortransfertable_p.h" +#include + +QT_BEGIN_NAMESPACE + +QColorTrcLut *QColorTrcLut::fromGamma(qreal gamma) +{ + 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))); + cp->m_fromLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), qreal(1) / gamma) * (255 * 256))); + } + + return cp; +} + +QColorTrcLut *QColorTrcLut::fromTransferFunction(const QColorTransferFunction &fun) +{ + QColorTrcLut *cp = new QColorTrcLut; + QColorTransferFunction inv = fun.inverted(); + + for (int i = 0; i <= (255 * 16); ++i) { + 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; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolortrclut_p.h b/src/gui/painting/qcolortrclut_p.h new file mode 100644 index 0000000000..76a6a60803 --- /dev/null +++ b/src/gui/painting/qcolortrclut_p.h @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QCOLORTRCLUT_P_H +#define QCOLORTRCLUT_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 +#include +#include +#include + +#include + +#if defined(__SSE2__) +#include +#elif defined(__ARM_NEON__) || defined(__ARM_NEON) +#include +#endif + +QT_BEGIN_NAMESPACE + +class QColorTransferFunction; +class QColorTransferTable; + +class Q_GUI_EXPORT QColorTrcLut : public QEnableSharedFromThis +{ +public: + 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: + + QRgba64 toLinear64(QRgb rgb32) const + { +#if defined(__SSE2__) + __m128i v = _mm_cvtsi32_si128(rgb32); + v = _mm_unpacklo_epi8(v, _mm_setzero_si128()); + const __m128i vidx = _mm_slli_epi16(v, 4); + const int ridx = _mm_extract_epi16(vidx, 2); + const int gidx = _mm_extract_epi16(vidx, 1); + const int bidx = _mm_extract_epi16(vidx, 0); + v = _mm_slli_epi16(v, 8); // a * 256 + v = _mm_insert_epi16(v, m_toLinear[ridx], 0); + v = _mm_insert_epi16(v, m_toLinear[gidx], 1); + v = _mm_insert_epi16(v, m_toLinear[bidx], 2); + v = _mm_add_epi16(v, _mm_srli_epi16(v, 8)); + QRgba64 rgba64; + _mm_storel_epi64(reinterpret_cast<__m128i *>(&rgba64), v); + return rgba64; +#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN + uint8x8_t v8 = vreinterpret_u8_u32(vmov_n_u32(rgb32)); + uint16x4_t v16 = vget_low_u16(vmovl_u8(v8)); + const uint16x4_t vidx = vshl_n_u16(v16, 4); + const int ridx = vget_lane_u16(vidx, 2); + const int gidx = vget_lane_u16(vidx, 1); + const int bidx = vget_lane_u16(vidx, 0); + v16 = vshl_n_u16(v16, 8); // a * 256 + v16 = vset_lane_u16(m_toLinear[ridx], v16, 0); + v16 = vset_lane_u16(m_toLinear[gidx], v16, 1); + v16 = vset_lane_u16(m_toLinear[bidx], v16, 2); + v16 = vadd_u16(v16, vshr_n_u16(v16, 8)); + return QRgba64::fromRgba64(vget_lane_u64(vreinterpret_u64_u16(v16), 0)); +#else + uint r = m_toLinear[qRed(rgb32) << 4]; + uint g = m_toLinear[qGreen(rgb32) << 4]; + uint b = m_toLinear[qBlue(rgb32) << 4]; + r = r + (r >> 8); + g = g + (g >> 8); + b = b + (b >> 8); + return QRgba64::fromRgba64(r, g, b, qAlpha(rgb32) * 257); +#endif + } + + QRgb toLinear(QRgb rgb32) const + { + return convertWithTable(rgb32, m_toLinear); + } + + QRgba64 toLinear(QRgba64 rgb64) const + { + 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__) + __m128i v = _mm_loadl_epi64(reinterpret_cast(&rgb64)); + v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8)); + const __m128i vidx = _mm_srli_epi16(v, 4); + const int ridx = _mm_extract_epi16(vidx, 0); + const int gidx = _mm_extract_epi16(vidx, 1); + const int bidx = _mm_extract_epi16(vidx, 2); + v = _mm_insert_epi16(v, m_fromLinear[ridx], 2); + v = _mm_insert_epi16(v, m_fromLinear[gidx], 1); + v = _mm_insert_epi16(v, m_fromLinear[bidx], 0); + v = _mm_add_epi16(v, _mm_set1_epi16(0x80)); + v = _mm_srli_epi16(v, 8); + v = _mm_packus_epi16(v, v); + return _mm_cvtsi128_si32(v); +#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN + uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64)); + v = vsub_u16(v, vshr_n_u16(v, 8)); + const uint16x4_t vidx = vshr_n_u16(v, 4); + const int ridx = vget_lane_u16(vidx, 0); + const int gidx = vget_lane_u16(vidx, 1); + const int bidx = vget_lane_u16(vidx, 2); + v = vset_lane_u16(m_fromLinear[ridx], v, 2); + v = vset_lane_u16(m_fromLinear[gidx], v, 1); + v = vset_lane_u16(m_fromLinear[bidx], v, 0); + uint8x8_t v8 = vrshrn_n_u16(vcombine_u16(v, v), 8); + return vget_lane_u32(vreinterpret_u32_u8(v8), 0); +#else + uint a = rgb64.alpha(); + uint r = rgb64.red(); + uint g = rgb64.green(); + uint b = rgb64.blue(); + a = a - (a >> 8); + r = r - (r >> 8); + g = g - (g >> 8); + b = b - (b >> 8); + a = (a + 0x80) >> 8; + r = (m_fromLinear[r >> 4] + 0x80) >> 8; + g = (m_fromLinear[g >> 4] + 0x80) >> 8; + b = (m_fromLinear[b >> 4] + 0x80) >> 8; + return (a << 24) | (r << 16) | (g << 8) | b; +#endif + } + + QRgb fromLinear(QRgb rgb32) const + { + return convertWithTable(rgb32, m_fromLinear); + } + + QRgba64 fromLinear(QRgba64 rgb64) const + { + 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: + QColorTrcLut() { } + + Q_ALWAYS_INLINE static QRgb convertWithTable(QRgb rgb32, const ushort *table) + { + const int r = (table[qRed(rgb32) << 4] + 0x80) >> 8; + const int g = (table[qGreen(rgb32) << 4] + 0x80) >> 8; + const int b = (table[qBlue(rgb32) << 4] + 0x80) >> 8; + return (rgb32 & 0xff000000) | (r << 16) | (g << 8) | b; + } + Q_ALWAYS_INLINE static QRgba64 convertWithTable(QRgba64 rgb64, const ushort *table) + { +#if defined(__SSE2__) + __m128i v = _mm_loadl_epi64(reinterpret_cast(&rgb64)); + v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8)); + const __m128i vidx = _mm_srli_epi16(v, 4); + const int ridx = _mm_extract_epi16(vidx, 2); + const int gidx = _mm_extract_epi16(vidx, 1); + const int bidx = _mm_extract_epi16(vidx, 0); + v = _mm_insert_epi16(v, table[ridx], 2); + v = _mm_insert_epi16(v, table[gidx], 1); + v = _mm_insert_epi16(v, table[bidx], 0); + v = _mm_add_epi16(v, _mm_srli_epi16(v, 8)); + QRgba64 rgba64; + _mm_storel_epi64(reinterpret_cast<__m128i *>(&rgba64), v); + return rgba64; +#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN + uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64)); + v = vsub_u16(v, vshr_n_u16(v, 8)); + const uint16x4_t vidx = vshr_n_u16(v, 4); + const int ridx = vget_lane_u16(vidx, 2); + const int gidx = vget_lane_u16(vidx, 1); + const int bidx = vget_lane_u16(vidx, 0); + v = vset_lane_u16(table[ridx], v, 2); + v = vset_lane_u16(table[gidx], v, 1); + v = vset_lane_u16(table[bidx], v, 0); + v = vadd_u16(v, vshr_n_u16(v, 8)); + return QRgba64::fromRgba64(vget_lane_u64(vreinterpret_u64_u16(v), 0)); +#else + ushort r = rgb64.red(); + ushort g = rgb64.green(); + ushort b = rgb64.blue(); + r = r - (r >> 8); + g = g - (g >> 8); + b = b - (b >> 8); + r = table[r >> 4]; + g = table[g >> 4]; + b = table[b >> 4]; + r = r + (r >> 8); + g = g + (g >> 8); + b = b + (b >> 8); + return QRgba64::fromRgba64(r, g, b, rgb64.alpha()); +#endif + } +}; + +QT_END_NAMESPACE + +#endif // QCOLORTRCLUT_P_H diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp index 2dd18f6dfc..1ed51d26a2 100644 --- a/src/gui/painting/qdrawhelper.cpp +++ b/src/gui/painting/qdrawhelper.cpp @@ -43,7 +43,7 @@ #include #include #include -#include +#include #include #include #include @@ -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(); diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp new file mode 100644 index 0000000000..d88b005782 --- /dev/null +++ b/src/gui/painting/qicc.cpp @@ -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 +#include +#include +#include +#include + +#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(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 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(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 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 diff --git a/src/gui/painting/qicc_p.h b/src/gui/painting/qicc_p.h new file mode 100644 index 0000000000..c3220391f4 --- /dev/null +++ b/src/gui/painting/qicc_p.h @@ -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 +#include + +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 diff --git a/src/gui/painting/qpainter_p.h b/src/gui/painting/qpainter_p.h index 930180e9fa..9857a59070 100644 --- a/src/gui/painting/qpainter_p.h +++ b/src/gui/painting/qpainter_p.h @@ -54,6 +54,8 @@ #include #include #include "QtGui/qbrush.h" +#include "QtGui/qcolorspace.h" +#include "QtGui/qcolortransform.h" #include "QtGui/qfont.h" #include "QtGui/qpen.h" #include "QtGui/qregion.h" -- cgit v1.2.3