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