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