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