Flutter Engine
The Flutter Engine
SkICC.cpp
Go to the documentation of this file.
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
9
11#include "include/core/SkData.h"
17#include "modules/skcms/skcms.h"
19#include "src/base/SkEndian.h"
20#include "src/core/SkMD5.h"
23
24#include <algorithm>
25#include <cmath>
26#include <cstring>
27#include <string>
28#include <utility>
29#include <vector>
30
31namespace {
32
33// The number of input and output channels.
34constexpr size_t kNumChannels = 3;
35
36// The D50 illuminant.
37constexpr float kD50_x = 0.9642f;
38constexpr float kD50_y = 1.0000f;
39constexpr 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.
45SkFixed 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|.
50uint16_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".
58constexpr 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.
62constexpr uint16_t kOne16XYZ = 0x8000;
63
64struct 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
130sk_sp<SkData> write_xyz_tag(float x, float y, float z) {
131 uint32_t data[] = {
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
141sk_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
156bool 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
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
179bool 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
190constexpr uint32_t kCICPPrimariesSRGB = 1;
191constexpr uint32_t kCICPPrimariesP3 = 12;
192constexpr uint32_t kCICPPrimariesRec2020 = 9;
193
194uint32_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
205constexpr uint32_t kCICPTrfnSRGB = 1;
206constexpr uint32_t kCICPTrfn2Dot2 = 4;
207constexpr uint32_t kCICPTrfnLinear = 8;
208constexpr uint32_t kCICPTrfnPQ = 16;
209constexpr uint32_t kCICPTrfnHLG = 18;
210
211uint32_t get_cicp_trfn(const skcms_TransferFunction& fn) {
212 switch (skcms_TransferFunction_getType(&fn)) {
214 return 0;
217 return kCICPTrfnSRGB;
218 } else if (nearly_equal(fn, SkNamedTransferFn::k2Dot2)) {
219 return kCICPTrfn2Dot2;
221 return kCICPTrfnLinear;
222 }
223 break;
225 // All PQ transfer functions are mapped to the single PQ value,
226 // ignoring their SDR white level.
227 return kCICPTrfnPQ;
229 // All HLG transfer functions are mapped to the single HLG value.
230 return kCICPTrfnHLG;
232 return 0;
233 }
234 return 0;
235}
236
237std::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
297sk_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 };
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.
320sk_sp<SkData> write_cicp_tag(const skcms_CICP& cicp) {
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
331constexpr float kToneMapInputMax = 1000.f / 203.f;
332constexpr float kToneMapOutputMax = 1.f;
333
334// Scalar tone map gain function.
335float 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
345float 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.
362float hdr_trfn_eval(const skcms_TransferFunction& fn, float x) {
364 // For HLG this curve is the inverse OETF and then a per-channel OOTF.
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.
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.
379sk_sp<SkData> write_trc_tag(const skcms_Curve& trc) {
381 if (trc.table_entries) {
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 {
391 s.write32(0); // Reserved
392 const auto& fn = trc.parametric;
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) {
398 SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.g));
399 } else {
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
415sk_sp<SkData> write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
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.
438sk_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;
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) {
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
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
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
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;
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.
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).
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.
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.
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}
static SkMD5::Digest md5(const SkBitmap &bm)
Definition: CodecTest.cpp:77
static constexpr float kTolerance
Definition: GrQuadUtils.cpp:29
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SkEndian_SwapBE32(n)
Definition: SkEndian.h:136
#define SkEndian_SwapBE16(n)
Definition: SkEndian.h:135
int32_t SkFixed
Definition: SkFixed.h:25
#define SK_Fixed1
Definition: SkFixed.h:26
static constexpr int sk_float_saturate2int(float x)
static constexpr uint32_t kTAG_mBAType
Definition: SkICCPriv.h:53
static constexpr uint32_t kTAG_desc
Definition: SkICCPriv.h:44
static constexpr uint32_t kTAG_gTRC
Definition: SkICCPriv.h:39
static constexpr uint32_t kTAG_TextType
Definition: SkICCPriv.h:51
static constexpr uint32_t kTAG_cprt
Definition: SkICCPriv.h:47
static constexpr uint32_t kXYZ_PCSSpace
Definition: SkICCPriv.h:31
static constexpr uint32_t kTAG_bTRC
Definition: SkICCPriv.h:40
static constexpr size_t kICCHeaderSize
Definition: SkICCPriv.h:19
static constexpr uint32_t kTAG_gXYZ
Definition: SkICCPriv.h:36
static constexpr uint32_t kTAG_wtpt
Definition: SkICCPriv.h:46
static constexpr uint32_t kTAG_A2B0
Definition: SkICCPriv.h:42
static constexpr uint32_t kACSP_Signature
Definition: SkICCPriv.h:33
static constexpr uint32_t kTAG_B2A0
Definition: SkICCPriv.h:43
static constexpr uint32_t kTAG_ParaCurveType
Definition: SkICCPriv.h:50
static constexpr size_t kICCTagTableEntrySize
Definition: SkICCPriv.h:22
static constexpr uint32_t kTAG_mABType
Definition: SkICCPriv.h:52
static constexpr uint32_t kTAG_bXYZ
Definition: SkICCPriv.h:37
static constexpr uint32_t kRGB_ColorSpace
Definition: SkICCPriv.h:24
static constexpr uint32_t kTAG_rXYZ
Definition: SkICCPriv.h:35
static constexpr uint32_t kDisplay_Profile
Definition: SkICCPriv.h:27
static constexpr uint32_t kTAG_rTRC
Definition: SkICCPriv.h:38
static constexpr uint32_t kTAG_cicp
Definition: SkICCPriv.h:45
@ kExponential_ParaCurveType
Definition: SkICCPriv.h:56
@ kGABCDEF_ParaCurveType
Definition: SkICCPriv.h:60
static constexpr uint32_t kTAG_CurveType
Definition: SkICCPriv.h:49
sk_sp< SkData > SkWriteICCProfile(const skcms_ICCProfile *profile, const char *desc)
Definition: SkICC.cpp:545
bool SkWStreamWriteU32BE(SkWStream *s, uint32_t value)
Definition: SkStreamPriv.h:54
bool SkWStreamWriteU16BE(SkWStream *s, uint16_t value)
Definition: SkStreamPriv.h:49
static constexpr SkFourByteTag SkSetFourByteTag(char a, char b, char c, char d)
Definition: SkTypes.h:167
static const SkScalar Y
Definition: StrokeBench.cpp:55
static void clut(uint32_t input_channels, uint32_t output_channels, const uint8_t grid_points[4], const uint8_t *grid_8, const uint8_t *grid_16, F *r, F *g, F *b, F *a)
GLenum type
void * release()
Definition: SkAutoMalloc.h:71
void * get()
Definition: SkAutoMalloc.h:64
static sk_sp< SkData > MakeFromMalloc(const void *data, size_t length)
Definition: SkData.cpp:107
const void * data() const
Definition: SkData.h:37
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
Definition: SkData.cpp:111
static sk_sp< SkData > MakeEmpty()
Definition: SkData.cpp:94
size_t size() const
Definition: SkData.h:30
Definition: SkMD5.h:19
const char * c_str() const
Definition: SkString.h:133
static bool b
struct MyStruct s
struct MyStruct a[10]
FlutterSemanticsFlag flags
uint8_t value
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
std::u16string text
double y
double x
static constexpr skcms_Matrix3x3 kSRGB
Definition: SkColorSpace.h:67
static constexpr skcms_Matrix3x3 kRec2020
Definition: SkColorSpace.h:93
static constexpr skcms_Matrix3x3 kDisplayP3
Definition: SkColorSpace.h:87
static constexpr skcms_TransferFunction k2Dot2
Definition: SkColorSpace.h:48
static constexpr skcms_TransferFunction kSRGB
Definition: SkColorSpace.h:45
static constexpr skcms_TransferFunction kHLG
Definition: SkColorSpace.h:60
static constexpr skcms_TransferFunction kPQ
Definition: SkColorSpace.h:57
static constexpr skcms_TransferFunction kLinear
Definition: SkColorSpace.h:51
unsigned useCenter Optional< SkMatrix > matrix
Definition: SkRecords.h:258
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
SIN Vec< N, float > floor(const Vec< N, float > &x)
Definition: SkVx.h:703
static bool nearly_equal(SkColor4f x, SkColor4f y)
Definition: p3.cpp:35
bool skcms_TransferFunction_isHLGish(const skcms_TransferFunction *tf)
Definition: skcms.cc:192
skcms_TFType skcms_TransferFunction_getType(const skcms_TransferFunction *tf)
Definition: skcms.cc:183
float skcms_TransferFunction_eval(const skcms_TransferFunction *tf, float x)
Definition: skcms.cc:212
bool skcms_TransferFunction_isPQish(const skcms_TransferFunction *tf)
Definition: skcms.cc:189
bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction *tf)
Definition: skcms.cc:186
@ skcms_Signature_RGB
Definition: skcms_public.h:266
@ skcms_Signature_XYZ
Definition: skcms_public.h:270
@ skcms_TFType_Invalid
Definition: skcms_public.h:55
@ skcms_TFType_HLGish
Definition: skcms_public.h:58
@ skcms_TFType_sRGBish
Definition: skcms_public.h:56
@ skcms_TFType_HLGinvish
Definition: skcms_public.h:59
@ skcms_TFType_PQish
Definition: skcms_public.h:57
static const char header[]
Definition: skpbench.cpp:88
SeparatedVector2 offset
SkString toHexString() const
Definition: SkMD5.cpp:112
uint8_t color_primaries
Definition: skcms_public.h:167
uint8_t matrix_coefficients
Definition: skcms_public.h:169
uint8_t video_full_range_flag
Definition: skcms_public.h:170
uint8_t transfer_characteristics
Definition: skcms_public.h:168
float vals[3][3]
Definition: skcms_public.h:27
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
const uint8_t * table_16
Definition: skcms_public.h:114
skcms_TransferFunction parametric
Definition: skcms_public.h:109
uint32_t table_entries
Definition: skcms_public.h:112