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