xref: /aosp_15_r20/external/skia/src/encode/SkICC.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/encode/SkICC.h"
9 
10 #include "include/core/SkColorSpace.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkFourByteTag.h"
13 #include "include/core/SkStream.h"
14 #include "include/core/SkString.h"
15 #include "include/core/SkTypes.h"
16 #include "include/private/base/SkFixed.h"
17 #include "include/private/base/SkFloatingPoint.h"
18 #include "modules/skcms/skcms.h"
19 #include "src/base/SkAutoMalloc.h"
20 #include "src/base/SkEndian.h"
21 #include "src/core/SkMD5.h"
22 #include "src/core/SkStreamPriv.h"
23 #include "src/encode/SkICCPriv.h"
24 
25 #include <algorithm>
26 #include <cmath>
27 #include <cstring>
28 #include <string>
29 #include <utility>
30 #include <vector>
31 
32 namespace {
33 
34 // The number of input and output channels.
35 constexpr size_t kNumChannels = 3;
36 
37 // The D50 illuminant.
38 constexpr float kD50_x = 0.9642f;
39 constexpr float kD50_y = 1.0000f;
40 constexpr float kD50_z = 0.8249f;
41 
42 // This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible
43 // when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed).
44 // The use of double is necessary to accommodate the full potential 32-bit mantissa of the 16.16
45 // SkFixed value, and so avoiding rounding problems with float. Also, see the comment in SkFixed.h.
float_round_to_fixed(float x)46 SkFixed float_round_to_fixed(float x) {
47     return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5));
48 }
49 
50 // Convert a float to a uInt16Number, with 0.0 mapping go 0 and 1.0 mapping to |one|.
float_to_uInt16Number(float x,uint16_t one)51 uint16_t float_to_uInt16Number(float x, uint16_t one) {
52     x = x * one + 0.5;
53     if (x > one) return one;
54     if (x < 0) return 0;
55     return static_cast<uint16_t>(x);
56 }
57 
58 // The uInt16Number used by curveType has 1.0 map to 0xFFFF. See section "10.6. curveType".
59 constexpr uint16_t kOne16CurveType = 0xFFFF;
60 
61 // The uInt16Number used to encoude XYZ values has 1.0 map to 0x8000. See section "6.3.4.2 General
62 // PCS encoding" and Table 11.
63 constexpr uint16_t kOne16XYZ = 0x8000;
64 
65 struct ICCHeader {
66     // Size of the profile (computed)
67     uint32_t size;
68 
69     // Preferred CMM type (ignored)
70     uint32_t cmm_type = 0;
71 
72     // Version 4.3 or 4.4 if CICP is included.
73     uint32_t version = SkEndian_SwapBE32(0x04300000);
74 
75     // Display device profile
76     uint32_t profile_class = SkEndian_SwapBE32(kDisplay_Profile);
77 
78     // RGB input color space;
79     uint32_t data_color_space = SkEndian_SwapBE32(kRGB_ColorSpace);
80 
81     // Profile connection space.
82     uint32_t pcs = SkEndian_SwapBE32(kXYZ_PCSSpace);
83 
84     // Date and time (ignored)
85     uint16_t creation_date_year = SkEndian_SwapBE16(2016);
86     uint16_t creation_date_month = SkEndian_SwapBE16(1);  // 1-12
87     uint16_t creation_date_day = SkEndian_SwapBE16(1);  // 1-31
88     uint16_t creation_date_hours = 0;  // 0-23
89     uint16_t creation_date_minutes = 0;  // 0-59
90     uint16_t creation_date_seconds = 0;  // 0-59
91 
92     // Profile signature
93     uint32_t signature = SkEndian_SwapBE32(kACSP_Signature);
94 
95     // Platform target (ignored)
96     uint32_t platform = 0;
97 
98     // Flags: not embedded, can be used independently
99     uint32_t flags = 0x00000000;
100 
101     // Device manufacturer (ignored)
102     uint32_t device_manufacturer = 0;
103 
104     // Device model (ignored)
105     uint32_t device_model = 0;
106 
107     // Device attributes (ignored)
108     uint8_t device_attributes[8] = {0};
109 
110     // Relative colorimetric rendering intent
111     uint32_t rendering_intent = SkEndian_SwapBE32(1);
112 
113     // D50 standard illuminant (X, Y, Z)
114     uint32_t illuminant_X = SkEndian_SwapBE32(float_round_to_fixed(kD50_x));
115     uint32_t illuminant_Y = SkEndian_SwapBE32(float_round_to_fixed(kD50_y));
116     uint32_t illuminant_Z = SkEndian_SwapBE32(float_round_to_fixed(kD50_z));
117 
118     // Profile creator (ignored)
119     uint32_t creator = 0;
120 
121     // Profile id checksum (ignored)
122     uint8_t profile_id[16] = {0};
123 
124     // Reserved (ignored)
125     uint8_t reserved[28] = {0};
126 
127     // Technically not part of header, but required
128     uint32_t tag_count = 0;
129 };
130 
write_xyz_tag(float x,float y,float z)131 sk_sp<SkData> write_xyz_tag(float x, float y, float z) {
132     uint32_t data[] = {
133             SkEndian_SwapBE32(kXYZ_PCSSpace),
134             0,
135             SkEndian_SwapBE32(float_round_to_fixed(x)),
136             SkEndian_SwapBE32(float_round_to_fixed(y)),
137             SkEndian_SwapBE32(float_round_to_fixed(z)),
138     };
139     return SkData::MakeWithCopy(data, sizeof(data));
140 }
141 
write_matrix(const skcms_Matrix3x4 * matrix)142 sk_sp<SkData> write_matrix(const skcms_Matrix3x4* matrix) {
143     uint32_t data[12];
144     // See layout details in section "10.12.5 Matrix".
145     size_t k = 0;
146     for (int i = 0; i < 3; ++i) {
147         for (int j = 0; j < 3; ++j) {
148             data[k++] = SkEndian_SwapBE32(float_round_to_fixed(matrix->vals[i][j]));
149         }
150     }
151     for (int i = 0; i < 3; ++i) {
152         data[k++] = SkEndian_SwapBE32(float_round_to_fixed(matrix->vals[i][3]));
153     }
154     return SkData::MakeWithCopy(data, sizeof(data));
155 }
156 
nearly_equal(float x,float y)157 bool nearly_equal(float x, float y) {
158     // A note on why I chose this tolerance:  transfer_fn_almost_equal() uses a
159     // tolerance of 0.001f, which doesn't seem to be enough to distinguish
160     // between similar transfer functions, for example: gamma2.2 and sRGB.
161     //
162     // If the tolerance is 0.0f, then this we can't distinguish between two
163     // different encodings of what is clearly the same colorspace.  Some
164     // experimentation with example files lead to this number:
165     static constexpr float kTolerance = 1.0f / (1 << 11);
166     return ::fabsf(x - y) <= kTolerance;
167 }
168 
nearly_equal(const skcms_TransferFunction & u,const skcms_TransferFunction & v)169 bool nearly_equal(const skcms_TransferFunction& u,
170                   const skcms_TransferFunction& v) {
171     return nearly_equal(u.g, v.g)
172         && nearly_equal(u.a, v.a)
173         && nearly_equal(u.b, v.b)
174         && nearly_equal(u.c, v.c)
175         && nearly_equal(u.d, v.d)
176         && nearly_equal(u.e, v.e)
177         && nearly_equal(u.f, v.f);
178 }
179 
nearly_equal(const skcms_Matrix3x3 & u,const skcms_Matrix3x3 & v)180 bool nearly_equal(const skcms_Matrix3x3& u, const skcms_Matrix3x3& v) {
181     for (int r = 0; r < 3; r++) {
182         for (int c = 0; c < 3; c++) {
183             if (!nearly_equal(u.vals[r][c], v.vals[r][c])) {
184                 return false;
185             }
186         }
187     }
188     return true;
189 }
190 
191 constexpr uint32_t kCICPPrimariesSRGB = 1;
192 constexpr uint32_t kCICPPrimariesP3 = 12;
193 constexpr uint32_t kCICPPrimariesRec2020 = 9;
194 
get_cicp_primaries(const skcms_Matrix3x3 & toXYZD50)195 uint32_t get_cicp_primaries(const skcms_Matrix3x3& toXYZD50) {
196     if (nearly_equal(toXYZD50, SkNamedGamut::kSRGB)) {
197         return kCICPPrimariesSRGB;
198     } else if (nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3)) {
199         return kCICPPrimariesP3;
200     } else if (nearly_equal(toXYZD50, SkNamedGamut::kRec2020)) {
201         return kCICPPrimariesRec2020;
202     }
203     return 0;
204 }
205 
206 constexpr uint32_t kCICPTrfnSRGB = 1;
207 constexpr uint32_t kCICPTrfn2Dot2 = 4;
208 constexpr uint32_t kCICPTrfnLinear = 8;
209 constexpr uint32_t kCICPTrfnPQ = 16;
210 constexpr uint32_t kCICPTrfnHLG = 18;
211 
get_cicp_trfn(const skcms_TransferFunction & fn)212 uint32_t get_cicp_trfn(const skcms_TransferFunction& fn) {
213     switch (skcms_TransferFunction_getType(&fn)) {
214         case skcms_TFType_Invalid:
215             return 0;
216         case skcms_TFType_sRGBish:
217             if (nearly_equal(fn, SkNamedTransferFn::kSRGB)) {
218                 return kCICPTrfnSRGB;
219             } else if (nearly_equal(fn, SkNamedTransferFn::k2Dot2)) {
220                 return kCICPTrfn2Dot2;
221             } else if (nearly_equal(fn, SkNamedTransferFn::kLinear)) {
222                 return kCICPTrfnLinear;
223             }
224             break;
225         case skcms_TFType_PQish:
226             // All PQ transfer functions are mapped to the single PQ value,
227             // ignoring their SDR white level.
228             return kCICPTrfnPQ;
229         case skcms_TFType_HLGish:
230             // All HLG transfer functions are mapped to the single HLG value.
231             return kCICPTrfnHLG;
232         case skcms_TFType_HLGinvish:
233             return 0;
234     }
235     return 0;
236 }
237 
get_desc_string(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)238 std::string get_desc_string(const skcms_TransferFunction& fn,
239                             const skcms_Matrix3x3& toXYZD50) {
240     const uint32_t cicp_trfn = get_cicp_trfn(fn);
241     const uint32_t cicp_primaries = get_cicp_primaries(toXYZD50);
242 
243     // Use a unique string for sRGB.
244     if (cicp_trfn == kCICPPrimariesSRGB && cicp_primaries == kCICPTrfnSRGB) {
245         return "sRGB";
246     }
247 
248     // If available, use the named CICP primaries and transfer function.
249     if (cicp_primaries && cicp_trfn) {
250         std::string result;
251         switch (cicp_primaries) {
252             case kCICPPrimariesSRGB:
253                 result += "sRGB";
254                 break;
255             case kCICPPrimariesP3:
256                 result += "Display P3";
257                 break;
258             case kCICPPrimariesRec2020:
259                 result += "Rec2020";
260                 break;
261             default:
262                 result += "Unknown";
263                 break;
264         }
265         result += " Gamut with ";
266         switch (cicp_trfn) {
267             case kCICPTrfnSRGB:
268                 result += "sRGB";
269                 break;
270             case kCICPTrfnLinear:
271                 result += "Linear";
272                 break;
273             case kCICPTrfn2Dot2:
274                 result += "2.2";
275                 break;
276             case kCICPTrfnPQ:
277                 result += "PQ";
278                 break;
279             case kCICPTrfnHLG:
280                 result += "HLG";
281                 break;
282             default:
283                 result += "Unknown";
284                 break;
285         }
286         result += " Transfer";
287         return result;
288     }
289 
290     // Fall back to a prefix plus md5 hash.
291     SkMD5 md5;
292     md5.write(&toXYZD50, sizeof(toXYZD50));
293     md5.write(&fn, sizeof(fn));
294     SkMD5::Digest digest = md5.finish();
295     return std::string("Google/Skia/") + digest.toHexString().c_str();
296 }
297 
write_text_tag(const char * text)298 sk_sp<SkData> write_text_tag(const char* text) {
299     uint32_t text_length = strlen(text);
300     uint32_t header[] = {
301             SkEndian_SwapBE32(kTAG_TextType),                         // Type signature
302             0,                                                        // Reserved
303             SkEndian_SwapBE32(1),                                     // Number of records
304             SkEndian_SwapBE32(12),                                    // Record size (must be 12)
305             SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')),  // English USA
306             SkEndian_SwapBE32(2 * text_length),                       // Length of string in bytes
307             SkEndian_SwapBE32(28),                                    // Offset of string
308     };
309     SkDynamicMemoryWStream s;
310     s.write(header, sizeof(header));
311     for (size_t i = 0; i < text_length; i++) {
312         // Convert ASCII to big-endian UTF-16.
313         s.write8(0);
314         s.write8(text[i]);
315     }
316     s.padToAlign4();
317     return s.detachAsData();
318 }
319 
320 // Write a CICP tag.
write_cicp_tag(const skcms_CICP & cicp)321 sk_sp<SkData> write_cicp_tag(const skcms_CICP& cicp) {
322     SkDynamicMemoryWStream s;
323     SkWStreamWriteU32BE(&s, kTAG_cicp);       // Type signature
324     SkWStreamWriteU32BE(&s, 0);               // Reserved
325     s.write8(cicp.color_primaries);           // Color primaries
326     s.write8(cicp.transfer_characteristics);  // Transfer characteristics
327     s.write8(cicp.matrix_coefficients);       // RGB matrix
328     s.write8(cicp.video_full_range_flag);     // Full range
329     return s.detachAsData();
330 }
331 
332 constexpr float kToneMapInputMax = 1000.f / 203.f;
333 constexpr float kToneMapOutputMax = 1.f;
334 
335 // Scalar tone map gain function.
tone_map_gain(float x)336 float tone_map_gain(float x) {
337     // The PQ transfer function will map to the range [0, 1]. Linearly scale
338     // it up to the range [0, 1,000/203]. We will then tone map that back
339     // down to [0, 1].
340     constexpr float kToneMapA = kToneMapOutputMax / (kToneMapInputMax * kToneMapInputMax);
341     constexpr float kToneMapB = 1.f / kToneMapOutputMax;
342     return (1.f + kToneMapA * x) / (1.f + kToneMapB * x);
343 }
344 
345 // Scalar tone map inverse function
tone_map_inverse(float y)346 float tone_map_inverse(float y) {
347     constexpr float kToneMapA = kToneMapOutputMax / (kToneMapInputMax * kToneMapInputMax);
348     constexpr float kToneMapB = 1.f / kToneMapOutputMax;
349 
350     // This is a quadratic equation of the form a*x*x + b*x + c = 0
351     const float a = kToneMapA;
352     const float b = (1 - kToneMapB * y);
353     const float c = -y;
354     const float discriminant = b * b - 4.f * a * c;
355     if (discriminant < 0.f) {
356         return 0.f;
357     }
358     return (-b + sqrtf(discriminant)) / (2.f * a);
359 }
360 
361 // Evaluate PQ and HLG transfer functions without tonemapping. The maximum returned value is
362 // kToneMapInputMax.
hdr_trfn_eval(const skcms_TransferFunction & fn,float x)363 float hdr_trfn_eval(const skcms_TransferFunction& fn, float x) {
364     if (skcms_TransferFunction_isHLGish(&fn)) {
365         // For HLG this curve is the inverse OETF and then a per-channel OOTF.
366         x = skcms_TransferFunction_eval(&SkNamedTransferFn::kHLG, x) / 12.f;
367         x *= std::pow(x, 0.2);
368     } else if (skcms_TransferFunction_isPQish(&fn)) {
369         // For PQ this is the EOTF, scaled so that 1,000 nits maps to 1.0.
370         x = 10.f * skcms_TransferFunction_eval(&SkNamedTransferFn::kPQ, x);
371         x = std::min(x, 1.f);
372     }
373 
374     // Scale x so that 203 nits maps to 1.0.
375     x *= kToneMapInputMax;
376     return x;
377 }
378 
379 // Write a lookup table based 1D curve.
write_trc_tag(const skcms_Curve & trc)380 sk_sp<SkData> write_trc_tag(const skcms_Curve& trc) {
381     SkDynamicMemoryWStream s;
382     if (trc.table_entries) {
383         SkWStreamWriteU32BE(&s, kTAG_CurveType);     // Type
384         SkWStreamWriteU32BE(&s, 0);                  // Reserved
385         SkWStreamWriteU32BE(&s, trc.table_entries);  // Value count
386         for (uint32_t i = 0; i < trc.table_entries; ++i) {
387             uint16_t value = reinterpret_cast<const uint16_t*>(trc.table_16)[i];
388             s.write16(value);
389         }
390     } else {
391         SkWStreamWriteU32BE(&s, kTAG_ParaCurveType);       // Type
392         s.write32(0);                                      // Reserved
393         const auto& fn = trc.parametric;
394         SkASSERT(skcms_TransferFunction_isSRGBish(&fn));
395         if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f && fn.d == 0.f && fn.e == 0.f &&
396             fn.f == 0.f) {
397             SkWStreamWriteU16BE(&s, kExponential_ParaCurveType);
398             SkWStreamWriteU16BE(&s, 0);
399             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.g));
400         } else {
401             SkWStreamWriteU16BE(&s, kGABCDEF_ParaCurveType);
402             SkWStreamWriteU16BE(&s, 0);
403             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.g));
404             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.a));
405             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.b));
406             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.c));
407             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.d));
408             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.e));
409             SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.f));
410         }
411     }
412     s.padToAlign4();
413     return s.detachAsData();
414 }
415 
write_clut(const uint8_t * grid_points,const uint8_t * grid_16)416 sk_sp<SkData> write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
417     SkDynamicMemoryWStream s;
418     for (size_t i = 0; i < 16; ++i) {
419         s.write8(i < kNumChannels ? grid_points[i] : 0);  // Grid size
420     }
421     s.write8(2);  // Grid byte width (always 16-bit)
422     s.write8(0);  // Reserved
423     s.write8(0);  // Reserved
424     s.write8(0);  // Reserved
425 
426     uint32_t value_count = kNumChannels;
427     for (uint32_t i = 0; i < kNumChannels; ++i) {
428         value_count *= grid_points[i];
429     }
430     for (uint32_t i = 0; i < value_count; ++i) {
431         uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
432         s.write16(value);
433     }
434     s.padToAlign4();
435     return s.detachAsData();
436 }
437 
438 // Write an A2B or B2A tag.
write_mAB_or_mBA_tag(uint32_t type,const skcms_Curve * b_curves,const skcms_Curve * a_curves,const uint8_t * grid_points,const uint8_t * grid_16,const skcms_Curve * m_curves,const skcms_Matrix3x4 * matrix)439 sk_sp<SkData> write_mAB_or_mBA_tag(uint32_t type,
440                                    const skcms_Curve* b_curves,
441                                    const skcms_Curve* a_curves,
442                                    const uint8_t* grid_points,
443                                    const uint8_t* grid_16,
444                                    const skcms_Curve* m_curves,
445                                    const skcms_Matrix3x4* matrix) {
446     size_t offset = 32;
447 
448     // The "B" curve is required.
449     size_t b_curves_offset = offset;
450     sk_sp<SkData> b_curves_data[kNumChannels];
451     SkASSERT(b_curves);
452     for (size_t i = 0; i < kNumChannels; ++i) {
453         b_curves_data[i] = write_trc_tag(b_curves[i]);
454         SkASSERT(b_curves_data[i]);
455         offset += b_curves_data[i]->size();
456     }
457 
458     // The CLUT.
459     size_t clut_offset = 0;
460     sk_sp<SkData> clut;
461     if (grid_points) {
462         SkASSERT(grid_16);
463         clut_offset = offset;
464         clut = write_clut(grid_points, grid_16);
465         SkASSERT(clut);
466         offset += clut->size();
467     }
468 
469     // The "A" curves.
470     size_t a_curves_offset = 0;
471     sk_sp<SkData> a_curves_data[kNumChannels];
472     if (a_curves) {
473         SkASSERT(grid_points);
474         SkASSERT(grid_16);
475         a_curves_offset = offset;
476         for (size_t i = 0; i < kNumChannels; ++i) {
477             a_curves_data[i] = write_trc_tag(a_curves[i]);
478             SkASSERT(a_curves_data[i]);
479             offset += a_curves_data[i]->size();
480         }
481     }
482 
483     // The matrix.
484     size_t matrix_offset = 0;
485     sk_sp<SkData> matrix_data;
486     if (matrix) {
487         SkASSERT(m_curves);
488         matrix_offset = offset;
489         matrix_data = write_matrix(matrix);
490         offset += matrix_data->size();
491     }
492 
493     // The "M" curves.
494     size_t m_curves_offset = 0;
495     sk_sp<SkData> m_curves_data[kNumChannels];
496     if (m_curves) {
497         SkASSERT(matrix);
498         m_curves_offset = offset;
499         for (size_t i = 0; i < kNumChannels; ++i) {
500             m_curves_data[i] = write_trc_tag(m_curves[i]);
501             SkASSERT(a_curves_data[i]);
502             offset += m_curves_data[i]->size();
503         }
504     }
505 
506     SkDynamicMemoryWStream s;
507     SkWStreamWriteU32BE(&s, type);  // Type signature
508     s.write32(0);                   // Reserved
509     s.write8(kNumChannels);         // Input channels
510     s.write8(kNumChannels);         // Output channels
511     s.write16(0);                   // Reserved
512     SkWStreamWriteU32BE(&s, b_curves_offset);  // B curve offset
513     SkWStreamWriteU32BE(&s, matrix_offset);    // Matrix offset
514     SkWStreamWriteU32BE(&s, m_curves_offset);  // M curve offset
515     SkWStreamWriteU32BE(&s, clut_offset);      // CLUT offset
516     SkWStreamWriteU32BE(&s, a_curves_offset);  // A curve offset
517     SkASSERT(s.bytesWritten() == b_curves_offset);
518     for (size_t i = 0; i < kNumChannels; ++i) {
519         s.write(b_curves_data[i]->data(), b_curves_data[i]->size());
520     }
521     if (clut) {
522         SkASSERT(s.bytesWritten() == clut_offset);
523         s.write(clut->data(), clut->size());
524     }
525     if (a_curves) {
526         SkASSERT(s.bytesWritten() == a_curves_offset);
527         for (size_t i = 0; i < kNumChannels; ++i) {
528             s.write(a_curves_data[i]->data(), a_curves_data[i]->size());
529         }
530     }
531     if (matrix_data) {
532         SkASSERT(s.bytesWritten() == matrix_offset);
533         s.write(matrix_data->data(), matrix_data->size());
534     }
535     if (m_curves) {
536         SkASSERT(s.bytesWritten() == m_curves_offset);
537         for (size_t i = 0; i < kNumChannels; ++i) {
538             s.write(m_curves_data[i]->data(), m_curves_data[i]->size());
539         }
540     }
541     return s.detachAsData();
542 }
543 
544 }  // namespace
545 
SkWriteICCProfile(const skcms_ICCProfile * profile,const char * desc)546 sk_sp<SkData> SkWriteICCProfile(const skcms_ICCProfile* profile, const char* desc) {
547     ICCHeader header;
548 
549     std::vector<std::pair<uint32_t, sk_sp<SkData>>> tags;
550 
551     // Compute primaries.
552     if (profile->has_toXYZD50) {
553         const auto& m = profile->toXYZD50;
554         tags.emplace_back(kTAG_rXYZ, write_xyz_tag(m.vals[0][0], m.vals[1][0], m.vals[2][0]));
555         tags.emplace_back(kTAG_gXYZ, write_xyz_tag(m.vals[0][1], m.vals[1][1], m.vals[2][1]));
556         tags.emplace_back(kTAG_bXYZ, write_xyz_tag(m.vals[0][2], m.vals[1][2], m.vals[2][2]));
557     }
558 
559     // Compute white point tag (must be D50)
560     tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
561 
562     // Compute transfer curves.
563     if (profile->has_trc) {
564         tags.emplace_back(kTAG_rTRC, write_trc_tag(profile->trc[0]));
565 
566         // Use empty data to indicate that the entry should use the previous tag's
567         // data.
568         if (!memcmp(&profile->trc[1], &profile->trc[0], sizeof(profile->trc[0]))) {
569             tags.emplace_back(kTAG_gTRC, SkData::MakeEmpty());
570         } else {
571             tags.emplace_back(kTAG_gTRC, write_trc_tag(profile->trc[1]));
572         }
573 
574         if (!memcmp(&profile->trc[2], &profile->trc[1], sizeof(profile->trc[1]))) {
575             tags.emplace_back(kTAG_bTRC, SkData::MakeEmpty());
576         } else {
577             tags.emplace_back(kTAG_bTRC, write_trc_tag(profile->trc[2]));
578         }
579     }
580 
581     // Compute CICP.
582     if (profile->has_CICP) {
583         // The CICP tag is present in ICC 4.4, so update the header's version.
584         header.version = SkEndian_SwapBE32(0x04400000);
585         tags.emplace_back(kTAG_cicp, write_cicp_tag(profile->CICP));
586     }
587 
588     // Compute A2B0.
589     if (profile->has_A2B) {
590         const auto& a2b = profile->A2B;
591         SkASSERT(a2b.output_channels == kNumChannels);
592         auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
593                                              a2b.output_curves,
594                                              a2b.input_channels ? a2b.input_curves : nullptr,
595                                              a2b.input_channels ? a2b.grid_points : nullptr,
596                                              a2b.input_channels ? a2b.grid_16 : nullptr,
597                                              a2b.matrix_channels ? a2b.matrix_curves : nullptr,
598                                              a2b.matrix_channels ? &a2b.matrix : nullptr);
599         tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
600     }
601 
602     // Compute B2A0.
603     if (profile->has_B2A) {
604         const auto& b2a = profile->B2A;
605         SkASSERT(b2a.input_channels == kNumChannels);
606         auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
607                                              b2a.input_curves,
608                                              b2a.output_channels ? b2a.input_curves : nullptr,
609                                              b2a.output_channels ? b2a.grid_points : nullptr,
610                                              b2a.output_channels ? b2a.grid_16 : nullptr,
611                                              b2a.matrix_channels ? b2a.matrix_curves : nullptr,
612                                              b2a.matrix_channels ? &b2a.matrix : nullptr);
613         tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
614     }
615 
616     // Compute copyright tag
617     tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2016"));
618 
619     // Ensure that the desc isn't empty https://crbug.com/329032158
620     std::string generatedDesc;
621     if (!desc || *desc == '\0') {
622         SkMD5 md5;
623         for (const auto& tag : tags) {
624             md5.write(&tag.first, sizeof(tag.first));
625             md5.write(tag.second->bytes(), tag.second->size());
626         }
627         SkMD5::Digest digest = md5.finish();
628         generatedDesc = std::string("Google/Skia/") + digest.toHexString().c_str();
629         desc = generatedDesc.c_str();
630     }
631     // Compute profile description tag
632     tags.emplace(tags.begin(), kTAG_desc, write_text_tag(desc));
633 
634     // Compute the size of the profile.
635     size_t tag_data_size = 0;
636     for (const auto& tag : tags) {
637         tag_data_size += tag.second->size();
638     }
639     size_t tag_table_size = kICCTagTableEntrySize * tags.size();
640     size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
641 
642     // Write the header.
643     header.data_color_space = SkEndian_SwapBE32(profile->data_color_space);
644     header.pcs = SkEndian_SwapBE32(profile->pcs);
645     header.size = SkEndian_SwapBE32(profile_size);
646     header.tag_count = SkEndian_SwapBE32(tags.size());
647 
648     SkAutoMalloc profile_data(profile_size);
649     uint8_t* ptr = (uint8_t*)profile_data.get();
650     memcpy(ptr, &header, sizeof(header));
651     ptr += sizeof(header);
652 
653     // Write the tag table. Track the offset and size of the previous tag to
654     // compute each tag's offset. An empty SkData indicates that the previous
655     // tag is to be reused.
656     size_t last_tag_offset = sizeof(header) + tag_table_size;
657     size_t last_tag_size = 0;
658     for (const auto& tag : tags) {
659         if (!tag.second->isEmpty()) {
660             last_tag_offset = last_tag_offset + last_tag_size;
661             last_tag_size = tag.second->size();
662         }
663         uint32_t tag_table_entry[3] = {
664                 SkEndian_SwapBE32(tag.first),
665                 SkEndian_SwapBE32(last_tag_offset),
666                 SkEndian_SwapBE32(last_tag_size),
667         };
668         memcpy(ptr, tag_table_entry, sizeof(tag_table_entry));
669         ptr += sizeof(tag_table_entry);
670     }
671 
672     // Write the tags.
673     for (const auto& tag : tags) {
674         if (tag.second->isEmpty()) continue;
675         memcpy(ptr, tag.second->data(), tag.second->size());
676         ptr += tag.second->size();
677     }
678 
679     SkASSERT(profile_size == static_cast<size_t>(ptr - (uint8_t*)profile_data.get()));
680     return SkData::MakeFromMalloc(profile_data.release(), profile_size);
681 }
682 
SkWriteICCProfile(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)683 sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn, const skcms_Matrix3x3& toXYZD50) {
684     skcms_ICCProfile profile;
685     memset(&profile, 0, sizeof(profile));
686     std::vector<uint16_t> trc_table;
687     std::vector<uint16_t> a2b_grid;
688 
689     profile.data_color_space = skcms_Signature_RGB;
690     profile.pcs = skcms_Signature_XYZ;
691 
692     // Populate toXYZD50.
693     {
694         profile.has_toXYZD50 = true;
695         profile.toXYZD50 = toXYZD50;
696     }
697 
698     // Populate the analytic TRC for sRGB-like curves.
699     if (skcms_TransferFunction_isSRGBish(&fn)) {
700         profile.has_trc = true;
701         profile.trc[0].table_entries = 0;
702         profile.trc[0].parametric = fn;
703         memcpy(&profile.trc[1], &profile.trc[0], sizeof(profile.trc[0]));
704         memcpy(&profile.trc[2], &profile.trc[0], sizeof(profile.trc[0]));
705     }
706 
707     // Populate A2B (PQ and HLG only).
708     if (skcms_TransferFunction_isPQish(&fn) || skcms_TransferFunction_isHLGish(&fn)) {
709         // Populate a 1D curve to perform per-channel conversion to linear and tone mapping.
710         constexpr uint32_t kTrcTableSize = 65;
711         trc_table.resize(kTrcTableSize);
712         for (uint32_t i = 0; i < kTrcTableSize; ++i) {
713             float x = i / (kTrcTableSize - 1.f);
714             x = hdr_trfn_eval(fn, x);
715             x *= tone_map_gain(x);
716             trc_table[i] = SkEndian_SwapBE16(float_to_uInt16Number(x, kOne16CurveType));
717         }
718 
719         // Populate the grid with a 3D LUT to do cross-channel tone mapping.
720         constexpr uint32_t kGridSize = 11;
721         a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels);
722         size_t a2b_grid_index = 0;
723         for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
724             for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
725                 for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
726                     float rgb[3] = {
727                             r_index / (kGridSize - 1.f),
728                             g_index / (kGridSize - 1.f),
729                             b_index / (kGridSize - 1.f),
730                     };
731 
732                     // Un-apply the per-channel tone mapping.
733                     for (auto& c : rgb) {
734                         c = tone_map_inverse(c);
735                     }
736 
737                     // For HLG, mix the channels according to the OOTF.
738                     if (skcms_TransferFunction_isHLGish(&fn)) {
739                         // Scale to [0, 1].
740                         for (auto& c : rgb) {
741                             c /= kToneMapInputMax;
742                         }
743 
744                         // Un-apply the per-channel OOTF.
745                         for (auto& c : rgb) {
746                             c = std::pow(c, 1 / 1.2);
747                         }
748 
749                         // Re-apply the cross-channel OOTF.
750                         float Y = 0.2627f * rgb[0] + 0.6780f * rgb[1] + 0.0593f * rgb[2];
751                         for (auto& c : rgb) {
752                             c *= std::pow(Y, 0.2);
753                         }
754 
755                         // Scale back up to 1.0 being 1,000/203.
756                         for (auto& c : rgb) {
757                             c *= kToneMapInputMax;
758                         }
759                     }
760 
761                     // Apply tone mapping to take 1,000/203 to 1.0.
762                     {
763                         float max_rgb = std::max(std::max(rgb[0], rgb[1]), rgb[2]);
764                         for (auto& c : rgb) {
765                             c *= tone_map_gain(0.5 * (c + max_rgb));
766                             c = std::min(c, 1.f);
767                         }
768                     }
769 
770                     // Write the result to the LUT.
771                     for (const auto& c : rgb) {
772                         a2b_grid[a2b_grid_index++] =
773                                 SkEndian_SwapBE16(float_to_uInt16Number(c, kOne16XYZ));
774                     }
775                 }
776             }
777         }
778 
779         // Populate A2B as this tone mapping.
780         profile.has_A2B = true;
781         profile.A2B.input_channels = kNumChannels;
782         profile.A2B.output_channels = kNumChannels;
783         profile.A2B.matrix_channels = kNumChannels;
784         for (size_t i = 0; i < kNumChannels; ++i) {
785             profile.A2B.grid_points[i] = kGridSize;
786             // Set the input curve to convert to linear pre-OOTF space.
787             profile.A2B.input_curves[i].table_entries = kTrcTableSize;
788             profile.A2B.input_curves[i].table_16 = reinterpret_cast<uint8_t*>(trc_table.data());
789             // The output and matrix curves are the identity.
790             profile.A2B.output_curves[i].parametric = SkNamedTransferFn::kLinear;
791             profile.A2B.matrix_curves[i].parametric = SkNamedTransferFn::kLinear;
792             // Set the matrix to convert from the primaries to XYZD50.
793             for (size_t j = 0; j < 3; ++j) {
794                 profile.A2B.matrix.vals[i][j] = toXYZD50.vals[i][j];
795             }
796             profile.A2B.matrix.vals[i][3] = 0.f;
797         }
798         profile.A2B.grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
799 
800         // Populate B2A as the identity.
801         profile.has_B2A = true;
802         profile.B2A.input_channels = kNumChannels;
803         for (size_t i = 0; i < 3; ++i) {
804             profile.B2A.input_curves[i].parametric = SkNamedTransferFn::kLinear;
805         }
806     }
807 
808     // Populate CICP.
809     if (skcms_TransferFunction_isHLGish(&fn) || skcms_TransferFunction_isPQish(&fn)) {
810         profile.has_CICP = true;
811         profile.CICP.color_primaries = get_cicp_primaries(toXYZD50);
812         profile.CICP.transfer_characteristics = get_cicp_trfn(fn);
813         profile.CICP.matrix_coefficients = 0;
814         profile.CICP.video_full_range_flag = 1;
815         SkASSERT(profile.CICP.color_primaries);
816         SkASSERT(profile.CICP.transfer_characteristics);
817     }
818 
819     std::string description = get_desc_string(fn, toXYZD50);
820     return SkWriteICCProfile(&profile, description.c_str());
821 }
822