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