Krita Source Code Documentation
Loading...
Searching...
No Matches
LcmsPredefinedPipelineFunctions.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2026 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 */
7#include "kis_dom_utils.h"
9#include <KoColorProfile.h>
10#include <QMatrix4x4>
11#include <QVector4D>
12
20static QMatrix4x4 bradfordMatrix(QVector4D src, QVector4D dst) {
21
22 QVector4D srcLMS(src[0], src[1], src[2], 0.0);
23 QVector4D dstLMS(dst[0], dst[1], dst[2], 0.0);
24
25 const QMatrix4x4 bradfordMatrix(
26 0.8951000, 0.2664000, -0.1614000, 0.0,
27 -0.7502000, 1.7135000, 0.0367000, 0.0,
28 0.0389000, -0.0685000, 1.0296000, 0.0,
29 0.0, 0.0, 0.0, 1.0
30 );
31 const QMatrix4x4 bradfordMatrixInverted (
32 0.9869929, -0.1470543, 0.1599627, 0.0,
33 0.4323053, 0.5183603, 0.0492912, 0.0,
34 -0.0085287, 0.0400428, 0.9684867, 0.0,
35 0.0, 0.0, 0.0, 1.0
36 );
37
38 srcLMS = bradfordMatrix * srcLMS;
39 dstLMS = bradfordMatrix * dstLMS;
40
41 const QMatrix4x4 adaptationMatrix (
42 dstLMS.x()/srcLMS.x(), 0, 0, 0,
43 0, dstLMS.y()/srcLMS.y(), 0, 0,
44 0, 0, dstLMS.z()/srcLMS.z(), 0,
45 0.0, 0.0, 0.0, 1.0
46 );
47
48
49 return bradfordMatrixInverted * adaptationMatrix * bradfordMatrix;
50}
58static QMatrix4x4 rgbMatrix(ColorPrimaries primaries, double factor = 1.0, double *luma = nullptr) {
59 const double mul = 1.0/factor;
60 // Calculation: http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
61 // Calculates XYZ with Y = 1.0
64 KoColorProfile::colorantsForType(primaries, wpXY, col, true);
65
66 QMatrix4x4 initialXYZMatrix(
67 col[0].toXYZ().X, col[1].toXYZ().X, col[2].toXYZ().X, 0.0,
68 1.0, 1.0, 1.0, 0.0,
69 col[0].toXYZ().Z, col[1].toXYZ().Z, col[2].toXYZ().Z, 0.0,
70 0.0, 0.0, 0.0, 1.0
71 );
72
73 // Calculate scaling factor with white point.
74 QVector4D wp(wpXY.toXYZ().X, 1.0, wpXY.toXYZ().Z, 0.0);
75
76 QVector4D S = initialXYZMatrix.inverted() * wp;
77 if (luma) {
78 luma[0] = S.x();
79 luma[1] = S.y();
80 luma[2] = S.z();
81 }
82
83 // Apply scaling factor to normalize the Y and get the matrix.
84 QMatrix4x4 finalXYZMatrix(
85 initialXYZMatrix(0, 0) * S.x(), initialXYZMatrix(0, 1) * S.y(), initialXYZMatrix(0, 2) * S.z(), 0.0,
86 initialXYZMatrix(1, 0) * S.x(), initialXYZMatrix(1, 1) * S.y(), initialXYZMatrix(1, 2) * S.z(), 0.0,
87 initialXYZMatrix(2, 0) * S.x(), initialXYZMatrix(2, 1) * S.y(), initialXYZMatrix(2, 2) * S.z(), 0.0,
88 0.0, 0.0, 0.0, 1.0
89 );
90 // Apply bradford white point adaptation.
91 QVector4D wpD50(0.9642, 1.0, 0.8249, 0.0);
92 finalXYZMatrix = bradfordMatrix(wp, wpD50) * finalXYZMatrix;
93
94 finalXYZMatrix.scale(mul);
95 return finalXYZMatrix;
96}
97
99 bool toXYZ{false};
100 int channels {3};
101 double *luma{nullptr};
103 };
104
110cmsInt32Number samplePQDummyClut(const cmsUInt16Number In[], cmsUInt16Number Out[], void *Cargo) {
111 struct perceptualDummyHelper *helper = (struct perceptualDummyHelper *) Cargo;
112 const float pqScale = 125.0;
113 const float nominalPeak = 10000.0/(helper->diffuseWhiteNits * 3.7743);
114
115 float lin[3];
116 for (int i = 0; i < helper->channels; i++) {
117 const float val = float(In[i])/65535.0;
118 if (helper->toXYZ) {
119 lin[i] = (removeSmpte2048Curve(val) / pqScale) * nominalPeak;
120 } else {
121 lin[i] = removeHLGCurve(val);
122 }
123 }
124 for (int i = 0; i < helper->channels; i++) {
125 cmsUInt16Number finalResult = In[i];
126 if (helper->toXYZ) {
127 finalResult = qBound(0, qRound( applyHLGCurve(lin[i]) * 65535 ), 65535);
128 } else {
129 finalResult = qBound(0, qRound( applySmpte2048Curve((lin[i] / nominalPeak) * pqScale) * 65535 ), 65535);
130 }
131 Out[i] = finalResult;
132 }
133 return true;
134};
135
136bool LcmsPredefinedPipelineFunctions::setPerceptualQuantizerAToBDummyPipeline(cmsHPROFILE iccProfile, cmsTagSignature tag, ColorPrimaries primaries, double diffuseWhiteNits)
137{
138 // From profile space to XYZ.
139 const double factor = 1.0;
140 const int lutSize = 4;
141
142 double luma[3];
143 const QMatrix4x4 tf = rgbMatrix(primaries, factor, luma);
144
145 // linear curves, obligatory for A2B with both matrix and clut: linear-clut-curves-matrix-linear.
146 cmsPipeline *a2B = cmsPipelineAlloc(nullptr, 3, 3);
147
148 cmsToneCurve *linearCurves[3];
149 linearCurves[0] = linearCurves[1] = linearCurves[2] = cmsBuildGamma(nullptr, 1.0);
150 cmsStage *lCurveStage = cmsStageAllocToneCurves(nullptr, 3, linearCurves);
151 cmsPipelineInsertStage(a2B, cmsAT_END, lCurveStage);
152
153 cmsStage *clutStage = cmsStageAllocCLut16bit(nullptr, lutSize, 3, 3, nullptr);
155 helper.toXYZ = true;
156 helper.luma = luma;
157 helper.diffuseWhiteNits = diffuseWhiteNits;
158 cmsStageSampleCLut16bit(clutStage, &samplePQDummyClut, &helper, 0);
159 cmsPipelineInsertStage(a2B, cmsAT_END, clutStage);
160
161 // close to "PQ" curve
162 cmsToneCurve *curve[3];
163 curve[0] = curve[1] = curve[2] = cmsBuildGamma(nullptr, 2.2);
164 cmsStage *curveStage = cmsStageAllocToneCurves(nullptr, 3, curve);
165 cmsPipelineInsertStage(a2B, cmsAT_END, curveStage);
166
167 // matrix
168
169 double m[] = {
170 tf(0, 0), tf(0, 1), tf(0, 2),
171 tf(1, 0), tf(1, 1), tf(1, 2),
172 tf(2, 0), tf(2, 1), tf(2, 2)
173 };
174
175 cmsStage* matrixStage = cmsStageAllocMatrix(nullptr, 3, 3, m, nullptr);
176 cmsPipelineInsertStage(a2B, cmsAT_END, matrixStage);
177
178 // curves, linear
179 cmsPipelineInsertStage(a2B, cmsAT_END, cmsStageDup(lCurveStage));
180
181 bool success = cmsWriteTag(iccProfile, tag, a2B);
182 cmsFreeToneCurve(curve[0]);
183 cmsFreeToneCurve(linearCurves[0]);
184 cmsPipelineFree(a2B);
185 return success;
186}
187
188bool LcmsPredefinedPipelineFunctions::setPerceptualQuantizerBToADummyPipeline(cmsHPROFILE iccProfile, cmsTagSignature tag, ColorPrimaries primaries, double diffuseWhiteNits)
189{
190 // From XYZ to profile space.
191 const double factor = 1.0;
192 const int lutSize = 4;
193 cmsPipeline *b2A = cmsPipelineAlloc(nullptr, 3, 3);
194
195 // curves, linear
196 cmsToneCurve *linearCurves[3];
197 linearCurves[0] = linearCurves[1] = linearCurves[2] = cmsBuildGamma(nullptr, 1.0);
198 cmsStage *lCurveStage = cmsStageAllocToneCurves(nullptr, 3, linearCurves);
199 cmsPipelineInsertStage(b2A, cmsAT_END, lCurveStage);
200
201 // matrix
202 double luma[3];
203 const QMatrix4x4 tf = rgbMatrix(primaries, factor, luma).inverted();
204 double m[] = {
205 tf(0, 0), tf(0, 1), tf(0, 2),
206 tf(1, 0), tf(1, 1), tf(1, 2),
207 tf(2, 0), tf(2, 1), tf(2, 2)
208 };
209
210 cmsStage* matrixStage = cmsStageAllocMatrix(nullptr, 3, 3, m, nullptr);
211 cmsPipelineInsertStage(b2A, cmsAT_END, matrixStage);
212 // pq curve
213 cmsToneCurve *curve[3];
214 curve[0] = curve[1] = curve[2] = cmsBuildGamma(nullptr, 1/2.2);
215 cmsStage *curveStage = cmsStageAllocToneCurves(nullptr, 3, curve);
216 cmsPipelineInsertStage(b2A, cmsAT_END, curveStage);
217
218 cmsStage *clutStage = cmsStageAllocCLut16bit(nullptr, lutSize, 3, 3, nullptr);
220 helper.toXYZ = false;
221 helper.luma = luma;
222 helper.diffuseWhiteNits = diffuseWhiteNits;
223 cmsStageSampleCLut16bit(clutStage, &samplePQDummyClut, &helper, 0);
224 cmsPipelineInsertStage(b2A, cmsAT_END, clutStage);
225
226 cmsPipelineInsertStage(b2A, cmsAT_END, cmsStageDup(lCurveStage));
227
228 bool success = cmsWriteTag(iccProfile, tag, b2A);
229 cmsFreeToneCurve(curve[0]);
230 cmsFreeToneCurve(linearCurves[0]);
231 cmsPipelineFree(b2A);
232 return success;
233}
234
236{
237 bool success = false;
238 cmsHANDLE dictionary = cmsDictAlloc(nullptr);
239
240
241
242 // https://registry.color.org/dicttype-metadata/crwl
243 cmsMLU *mluName = cmsMLUalloc (NULL, 1);
244 QString name("CRWL");
245 cmsMLUsetASCII (mluName, "en", "US", name.toLatin1());
246
247 // Hack to avoid having to figure out how to get the value.
248 int count = cmsMLUgetWide(mluName, "en", "US", nullptr, 0);
249 std::vector<wchar_t> nameArray(count);
250 cmsMLUgetWide(mluName, "en", "US", nameArray.data(), count);
251
252 cmsMLU *mluValue = cmsMLUalloc (NULL, 1);
253 QString value = KisDomUtils::toString(diffuseWhiteNits);
254 cmsMLUsetASCII (mluValue, "en", "US", value.toLatin1());
255 count = cmsMLUgetWide(mluValue, "en", "US", nullptr, 0);
256 std::vector<wchar_t> valArray(count);
257 cmsMLUgetWide(mluValue, "en", "US", valArray.data(), count);
258
259 success = cmsDictAddEntry(dictionary, nameArray.data(), valArray.data(), mluName, mluValue);
260
261 if (success) {
262 success = cmsWriteTag(iccProfile, cmsSigMetaTag, dictionary);
263 }
264
265 cmsMLUfree(mluName);
266 cmsMLUfree(mluValue);
267 cmsDictFree(dictionary);
268 return success;
269}
float value(const T *src, size_t ch)
Eigen::Matrix< double, 4, 2 > S
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
ALWAYS_INLINE float removeHLGCurve(float x) noexcept
ALWAYS_INLINE float applySmpte2048Curve(float x) noexcept
ALWAYS_INLINE float removeSmpte2048Curve(float x) noexcept
ALWAYS_INLINE float applyHLGCurve(float x) noexcept
static QMatrix4x4 rgbMatrix(ColorPrimaries primaries, double factor=1.0, double *luma=nullptr)
rgbMatrix Generates a proper rgb matrix from a primaries type.
cmsInt32Number samplePQDummyClut(const cmsUInt16Number In[], cmsUInt16Number Out[], void *Cargo)
samplePQDummyClut In this sampler, we convert the PQ data to linear and then to HLG....
static QMatrix4x4 bradfordMatrix(QVector4D src, QVector4D dst)
bradfordMatrix From http://brucelindbloom.com/index.html?Eqn_ChromAdapt.html
static bool setPerceptualQuantizerAToBDummyPipeline(cmsHPROFILE iccProfile, cmsTagSignature tag, ColorPrimaries primaries, double diffuseWhiteNits=80.0)
static bool setDiffuseWhitePerceptualQuantizer(cmsHPROFILE iccProfile, double diffuseWhiteNits=80.0)
setDiffuseWhitePerceptualQuantizer This adds a dictionary with an CRWL entry to the profile.
static bool setPerceptualQuantizerBToADummyPipeline(cmsHPROFILE iccProfile, cmsTagSignature tag, ColorPrimaries primaries, double diffuseWhiteNits=80.0)
QString toString(const QString &value)
static void colorantsForType(ColorPrimaries primaries, KoColorimetryUtils::xy &whitePoint, QList< KoColorimetryUtils::xy > &colorants, const bool prequantized=false)
colorantsForPrimaries fills a QVector<float> with the xy values of the whitepoint and red,...