xref: /aosp_15_r20/external/skia/src/core/SkColorSpace.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkColorSpace.h"
9 #include "include/core/SkData.h"
10 #include "include/private/base/SkTemplates.h"
11 #include "modules/skcms/skcms.h"
12 #include "src/core/SkChecksum.h"
13 #include "src/core/SkColorSpacePriv.h"
14 
15 #include <cmath>
16 #include <cstring>
17 
18 namespace SkNamedPrimaries {
19 
GetCicp(CicpId primaries,SkColorSpacePrimaries & sk_primaries)20 bool GetCicp(CicpId primaries, SkColorSpacePrimaries& sk_primaries) {
21     // Rec. ITU-T H.273, Table 2.
22     switch (primaries) {
23         case CicpId::kRec709:
24             sk_primaries = kRec709;
25             return true;
26         case CicpId::kRec470SystemM:
27             sk_primaries = kRec470SystemM;
28             return true;
29         case CicpId::kRec470SystemBG:
30             sk_primaries = kRec470SystemBG;
31             return true;
32         case CicpId::kRec601:
33             sk_primaries = kRec601;
34             return true;
35         case CicpId::kSMPTE_ST_240:
36             sk_primaries = kSMPTE_ST_240;
37             return true;
38         case CicpId::kGenericFilm:
39             sk_primaries = kGenericFilm;
40             return true;
41         case CicpId::kRec2020:
42             sk_primaries = kRec2020;
43             return true;
44         case CicpId::kSMPTE_ST_428_1:
45             sk_primaries = kSMPTE_ST_428_1;
46             return true;
47         case CicpId::kSMPTE_RP_431_2:
48             sk_primaries = kSMPTE_RP_431_2;
49             return true;
50         case CicpId::kSMPTE_EG_432_1:
51             sk_primaries = kSMPTE_EG_432_1;
52             return true;
53         case CicpId::kITU_T_H273_Value22:
54             sk_primaries = kITU_T_H273_Value22;
55             return true;
56         default:
57             // Reserved or unimplemented.
58             break;
59     }
60     return false;
61 }
62 
63 }  // namespace SkNamedPrimaries
64 
65 namespace SkNamedTransferFn {
66 
GetCicp(SkNamedTransferFn::CicpId transfer_characteristics,skcms_TransferFunction & trfn)67 bool GetCicp(SkNamedTransferFn::CicpId transfer_characteristics, skcms_TransferFunction& trfn) {
68     // Rec. ITU-T H.273, Table 3.
69     switch (transfer_characteristics) {
70         case SkNamedTransferFn::CicpId::kRec709:
71             trfn = kRec709;
72             return true;
73         case SkNamedTransferFn::CicpId::kRec470SystemM:
74             trfn = kRec470SystemM;
75             return true;
76         case SkNamedTransferFn::CicpId::kRec470SystemBG:
77             trfn = kRec470SystemBG;
78             return true;
79         case SkNamedTransferFn::CicpId::kRec601:
80             trfn = kRec601;
81             return true;
82         case SkNamedTransferFn::CicpId::kSMPTE_ST_240:
83             trfn = kSMPTE_ST_240;
84             return true;
85         case SkNamedTransferFn::CicpId::kLinear:
86             trfn = SkNamedTransferFn::kLinear;
87             return true;
88         case SkNamedTransferFn::CicpId::kIEC61966_2_4:
89             trfn = kIEC61966_2_4;
90             break;
91         case SkNamedTransferFn::CicpId::kIEC61966_2_1:
92             trfn = SkNamedTransferFn::kIEC61966_2_1;
93             return true;
94         case SkNamedTransferFn::CicpId::kRec2020_10bit:
95             trfn = kRec2020_10bit;
96             return true;
97         case SkNamedTransferFn::CicpId::kRec2020_12bit:
98             trfn = kRec2020_12bit;
99             return true;
100         case SkNamedTransferFn::CicpId::kPQ:
101             trfn = SkNamedTransferFn::kPQ;
102             return true;
103         case SkNamedTransferFn::CicpId::kSMPTE_ST_428_1:
104             trfn = kSMPTE_ST_428_1;
105             return true;
106         case SkNamedTransferFn::CicpId::kHLG:
107             trfn = SkNamedTransferFn::kHLG;
108             return true;
109         default:
110             // Reserved or unimplemented.
111             break;
112     }
113     return false;
114 }
115 
116 }  // namespace SkNamedTransferFn
117 
toXYZD50(skcms_Matrix3x3 * toXYZ_D50) const118 bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const {
119     return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50);
120 }
121 
SkColorSpace(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZD50)122 SkColorSpace::SkColorSpace(const skcms_TransferFunction& transferFn,
123                            const skcms_Matrix3x3& toXYZD50)
124         : fTransferFn(transferFn)
125         , fToXYZD50(toXYZD50) {
126     fTransferFnHash = SkChecksum::Hash32(&fTransferFn, 7*sizeof(float));
127     fToXYZD50Hash = SkChecksum::Hash32(&fToXYZD50, 9*sizeof(float));
128 }
129 
xyz_almost_equal(const skcms_Matrix3x3 & mA,const skcms_Matrix3x3 & mB)130 static bool xyz_almost_equal(const skcms_Matrix3x3& mA, const skcms_Matrix3x3& mB) {
131     for (int r = 0; r < 3; ++r) {
132         for (int c = 0; c < 3; ++c) {
133             if (!color_space_almost_equal(mA.vals[r][c], mB.vals[r][c])) {
134                 return false;
135             }
136         }
137     }
138 
139     return true;
140 }
141 
MakeRGB(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & toXYZ)142 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn,
143                                           const skcms_Matrix3x3& toXYZ) {
144     if (skcms_TransferFunction_getType(&transferFn) == skcms_TFType_Invalid) {
145         return nullptr;
146     }
147 
148     const skcms_TransferFunction* tf = &transferFn;
149 
150     if (is_almost_srgb(transferFn)) {
151         if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
152             return SkColorSpace::MakeSRGB();
153         }
154         tf = &SkNamedTransferFn::kSRGB;
155     } else if (is_almost_2dot2(transferFn)) {
156         tf = &SkNamedTransferFn::k2Dot2;
157     } else if (is_almost_linear(transferFn)) {
158         if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
159             return SkColorSpace::MakeSRGBLinear();
160         }
161         tf = &SkNamedTransferFn::kLinear;
162     }
163 
164     return sk_sp<SkColorSpace>(new SkColorSpace(*tf, toXYZ));
165 }
166 
MakeCICP(SkNamedPrimaries::CicpId color_primaries,SkNamedTransferFn::CicpId transfer_characteristics)167 sk_sp<SkColorSpace> SkColorSpace::MakeCICP(SkNamedPrimaries::CicpId color_primaries,
168                                            SkNamedTransferFn::CicpId transfer_characteristics) {
169     skcms_TransferFunction trfn;
170     if (!SkNamedTransferFn::GetCicp(transfer_characteristics, trfn)) {
171         return nullptr;
172     }
173 
174     SkColorSpacePrimaries primaries;
175     if (!SkNamedPrimaries::GetCicp(color_primaries, primaries)) {
176         return nullptr;
177     }
178 
179     skcms_Matrix3x3 primaries_matrix;
180     if (!primaries.toXYZD50(&primaries_matrix)) {
181         return nullptr;
182     }
183 
184     return SkColorSpace::MakeRGB(trfn, primaries_matrix);
185 }
186 
187 class SkColorSpaceSingletonFactory {
188 public:
Make(const skcms_TransferFunction & transferFn,const skcms_Matrix3x3 & to_xyz)189     static SkColorSpace* Make(const skcms_TransferFunction& transferFn,
190                               const skcms_Matrix3x3& to_xyz) {
191         return new SkColorSpace(transferFn, to_xyz);
192     }
193 };
194 
sk_srgb_singleton()195 SkColorSpace* sk_srgb_singleton() {
196     static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kSRGB,
197                                                                  SkNamedGamut::kSRGB);
198     return cs;
199 }
200 
sk_srgb_linear_singleton()201 SkColorSpace* sk_srgb_linear_singleton() {
202     static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kLinear,
203                                                                  SkNamedGamut::kSRGB);
204     return cs;
205 }
206 
MakeSRGB()207 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
208     return sk_ref_sp(sk_srgb_singleton());
209 }
210 
MakeSRGBLinear()211 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
212     return sk_ref_sp(sk_srgb_linear_singleton());
213 }
214 
computeLazyDstFields() const215 void SkColorSpace::computeLazyDstFields() const {
216     fLazyDstFieldsOnce([this] {
217 
218         // Invert 3x3 gamut, defaulting to sRGB if we can't.
219         {
220             if (!skcms_Matrix3x3_invert(&fToXYZD50, &fFromXYZD50)) {
221                 SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50,
222                                                       &fFromXYZD50));
223             }
224         }
225 
226         // Invert transfer function, defaulting to sRGB if we can't.
227         {
228             if (!skcms_TransferFunction_invert(&fTransferFn, &fInvTransferFn)) {
229                 fInvTransferFn = *skcms_sRGB_Inverse_TransferFunction();
230             }
231         }
232 
233     });
234 }
235 
isNumericalTransferFn(skcms_TransferFunction * coeffs) const236 bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
237     // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers
238     // already pass pointers to an skcms struct). Then remove this function, and update the two
239     // remaining callers to do the right thing with transferFn and classify.
240     this->transferFn(coeffs);
241     return skcms_TransferFunction_getType(coeffs) == skcms_TFType_sRGBish;
242 }
243 
transferFn(float gabcdef[7]) const244 void SkColorSpace::transferFn(float gabcdef[7]) const {
245     memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
246 }
247 
transferFn(skcms_TransferFunction * fn) const248 void SkColorSpace::transferFn(skcms_TransferFunction* fn) const {
249     *fn = fTransferFn;
250 }
251 
invTransferFn(skcms_TransferFunction * fn) const252 void SkColorSpace::invTransferFn(skcms_TransferFunction* fn) const {
253     this->computeLazyDstFields();
254     *fn = fInvTransferFn;
255 }
256 
toXYZD50(skcms_Matrix3x3 * toXYZD50) const257 bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
258     *toXYZD50 = fToXYZD50;
259     return true;
260 }
261 
gamutTransformTo(const SkColorSpace * dst,skcms_Matrix3x3 * src_to_dst) const262 void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, skcms_Matrix3x3* src_to_dst) const {
263     dst->computeLazyDstFields();
264     *src_to_dst = skcms_Matrix3x3_concat(&dst->fFromXYZD50, &fToXYZD50);
265 }
266 
isSRGB() const267 bool SkColorSpace::isSRGB() const {
268     return sk_srgb_singleton() == this;
269 }
270 
gammaCloseToSRGB() const271 bool SkColorSpace::gammaCloseToSRGB() const {
272     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
273     return memcmp(&fTransferFn, &SkNamedTransferFn::kSRGB, 7*sizeof(float)) == 0;
274 }
275 
gammaIsLinear() const276 bool SkColorSpace::gammaIsLinear() const {
277     // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
278     return memcmp(&fTransferFn, &SkNamedTransferFn::kLinear, 7*sizeof(float)) == 0;
279 }
280 
makeLinearGamma() const281 sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const {
282     if (this->gammaIsLinear()) {
283         return sk_ref_sp(const_cast<SkColorSpace*>(this));
284     }
285     return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, fToXYZD50);
286 }
287 
makeSRGBGamma() const288 sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const {
289     if (this->gammaCloseToSRGB()) {
290         return sk_ref_sp(const_cast<SkColorSpace*>(this));
291     }
292     return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, fToXYZD50);
293 }
294 
makeColorSpin() const295 sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const {
296     skcms_Matrix3x3 spin = {{
297         { 0, 0, 1 },
298         { 1, 0, 0 },
299         { 0, 1, 0 },
300     }};
301 
302     skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&fToXYZD50, &spin);
303 
304     return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun));
305 }
306 
toProfile(skcms_ICCProfile * profile) const307 void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
308     skcms_Init               (profile);
309     skcms_SetTransferFunction(profile, &fTransferFn);
310     skcms_SetXYZD50          (profile, &fToXYZD50);
311 }
312 
Make(const skcms_ICCProfile & profile)313 sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) {
314     // TODO: move below ≈sRGB test?
315     if (!profile.has_toXYZD50 || !profile.has_trc) {
316         return nullptr;
317     }
318 
319     if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) {
320         return SkColorSpace::MakeSRGB();
321     }
322 
323     // TODO: can we save this work and skip lazily inverting the matrix later?
324     skcms_Matrix3x3 inv;
325     if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) {
326         return nullptr;
327     }
328 
329     // We can't work with tables or mismatched parametric curves,
330     // but if they all look close enough to sRGB, that's fine.
331     // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB?
332     const skcms_Curve* trc = profile.trc;
333     if (trc[0].table_entries != 0 ||
334         trc[1].table_entries != 0 ||
335         trc[2].table_entries != 0 ||
336         0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) ||
337         0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric)))
338     {
339         if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) {
340             return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50);
341         }
342         return nullptr;
343     }
344 
345     return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50);
346 }
347 
348 ///////////////////////////////////////////////////////////////////////////////////////////////////
349 
350 enum Version {
351     k0_Version, // Initial (deprecated) version, no longer supported
352     k1_Version, // Simple header (version tag) + 16 floats
353 
354     kCurrent_Version = k1_Version,
355 };
356 
357 struct ColorSpaceHeader {
358     uint8_t fVersion = kCurrent_Version;
359 
360     // Other fields were only used by k0_Version. Could be re-purposed in future versions.
361     uint8_t fReserved0 = 0;
362     uint8_t fReserved1 = 0;
363     uint8_t fReserved2 = 0;
364 };
365 
writeToMemory(void * memory) const366 size_t SkColorSpace::writeToMemory(void* memory) const {
367     if (memory) {
368         *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
369         memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
370 
371         memcpy(memory, &fTransferFn, 7 * sizeof(float));
372         memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
373 
374         memcpy(memory, &fToXYZD50, 9 * sizeof(float));
375     }
376 
377     return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
378 }
379 
serialize() const380 sk_sp<SkData> SkColorSpace::serialize() const {
381     sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
382     this->writeToMemory(data->writable_data());
383     return data;
384 }
385 
Deserialize(const void * data,size_t length)386 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
387     if (length < sizeof(ColorSpaceHeader)) {
388         return nullptr;
389     }
390 
391     ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
392     data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
393     length -= sizeof(ColorSpaceHeader);
394     if (header.fVersion != k1_Version) {
395         return nullptr;
396     }
397 
398     if (length < 16 * sizeof(float)) {
399         return nullptr;
400     }
401 
402     skcms_TransferFunction transferFn;
403     memcpy(&transferFn, data, 7 * sizeof(float));
404     data = SkTAddOffset<const void>(data, 7 * sizeof(float));
405 
406     skcms_Matrix3x3 toXYZ;
407     memcpy(&toXYZ, data, 9 * sizeof(float));
408     return SkColorSpace::MakeRGB(transferFn, toXYZ);
409 }
410 
Equals(const SkColorSpace * x,const SkColorSpace * y)411 bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) {
412     if (x == y) {
413         return true;
414     }
415 
416     if (!x || !y) {
417         return false;
418     }
419 
420     if (x->hash() == y->hash()) {
421     #if defined(SK_DEBUG)
422         // Do these floats function equivalently?
423         // This returns true more often than simple float comparison   (NaN vs. NaN) and,
424         // also returns true more often than simple bitwise comparison (+0 vs. -0) and,
425         // even returns true more often than those two OR'd together   (two different NaNs).
426         auto equiv = [](float X, float Y) {
427             return (X==Y)
428                 || (std::isnan(X) && std::isnan(Y));
429         };
430 
431         for (int i = 0; i < 7; i++) {
432             float X = (&x->fTransferFn.g)[i],
433                   Y = (&y->fTransferFn.g)[i];
434             SkASSERTF(equiv(X,Y), "Hash collision at tf[%d], !equiv(%g,%g)\n", i, X,Y);
435         }
436         for (int r = 0; r < 3; r++)
437         for (int c = 0; c < 3; c++) {
438             float X = x->fToXYZD50.vals[r][c],
439                   Y = y->fToXYZD50.vals[r][c];
440             SkASSERTF(equiv(X,Y), "Hash collision at toXYZD50[%d][%d], !equiv(%g,%g)\n", r,c, X,Y);
441         }
442     #endif
443         return true;
444     }
445     return false;
446 }
447