• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "include/private/SkFixed.h"
10 #include "src/core/SkAutoMalloc.h"
11 #include "src/core/SkColorSpacePriv.h"
12 #include "src/core/SkEndian.h"
13 #include "src/core/SkICCPriv.h"
14 #include "src/core/SkMD5.h"
15 #include "src/core/SkUtils.h"
16 #include <securec.h>
17 
18 static constexpr char kDescriptionTagBodyPrefix[12] =
19         { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'};
20 
21 static constexpr size_t kICCDescriptionTagSize = 44;
22 
23 static_assert(kICCDescriptionTagSize ==
24               sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), "");
25 static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2;  // ascii->utf16be
26 
27 static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes.");
28 static constexpr uint32_t kDescriptionTagHeader[7] {
29     SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
30     0,                                                       // Reserved
31     SkEndian_SwapBE32(1),                                    // Number of records
32     SkEndian_SwapBE32(12),                                   // Record size (must be 12)
33     SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
34     SkEndian_SwapBE32(kDescriptionTagBodySize),              // Length of string
35     SkEndian_SwapBE32(28),                                   // Offset of string
36 };
37 
38 static constexpr uint32_t kWhitePointTag[5] {
39     SkEndian_SwapBE32(kXYZ_PCSSpace),
40     0,
41     SkEndian_SwapBE32(0x0000f6d6), // X = 0.96420 (D50)
42     SkEndian_SwapBE32(0x00010000), // Y = 1.00000 (D50)
43     SkEndian_SwapBE32(0x0000d32d), // Z = 0.82491 (D50)
44 };
45 
46 // Google Inc. 2016 (UTF-16)
47 static constexpr uint8_t kCopyrightTagBody[] = {
48         0x00, 0x47, 0x00, 0x6f,
49         0x00, 0x6f, 0x00, 0x67,
50         0x00, 0x6c, 0x00, 0x65,
51         0x00, 0x20, 0x00, 0x49,
52         0x00, 0x6e, 0x00, 0x63,
53         0x00, 0x2e, 0x00, 0x20,
54         0x00, 0x32, 0x00, 0x30,
55         0x00, 0x31, 0x00, 0x36,
56 };
57 static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes.");
58 static constexpr uint32_t kCopyrightTagHeader[7] {
59     SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
60     0,                                                       // Reserved
61     SkEndian_SwapBE32(1),                                    // Number of records
62     SkEndian_SwapBE32(12),                                   // Record size (must be 12)
63     SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
64     SkEndian_SwapBE32(sizeof(kCopyrightTagBody)),            // Length of string
65     SkEndian_SwapBE32(28),                                   // Offset of string
66 };
67 
68 // We will write a profile with the minimum nine required tags.
69 static constexpr uint32_t kICCNumEntries = 9;
70 
71 static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
72 static constexpr uint32_t kTAG_desc_Bytes = sizeof(kDescriptionTagHeader) +
73                                             kDescriptionTagBodySize;
74 static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize +
75                                              kICCNumEntries * kICCTagTableEntrySize;
76 
77 static constexpr uint32_t kTAG_XYZ_Bytes = 20;
78 static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
79 static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
80 static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
81 
82 static constexpr uint32_t kTAG_TRC_Bytes = 40;
83 static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
84 static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset;
85 static constexpr uint32_t kTAG_bTRC_Offset = kTAG_rTRC_Offset;
86 
87 static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
88 static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + kTAG_TRC_Bytes;
89 
90 static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
91 static constexpr uint32_t kTAG_cprt_Bytes = sizeof(kCopyrightTagHeader) +
92                                             sizeof(kCopyrightTagBody);
93 static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
94 // icc profile cicp tag size, reference ICC Chapter 10.3
95 static constexpr uint32_t kTAG_cicp_bytes = 12;
96 
97 static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
98 static constexpr uint32_t kICCProfileSizeWithCicp = kICCProfileSize + kTAG_cicp_bytes;
99 
100 static constexpr uint32_t kICCHeader[kICCHeaderSize / 4] {
101     SkEndian_SwapBE32(kICCProfileSize),  // Size of the profile
102     0,                                   // Preferred CMM type (ignored)
103     SkEndian_SwapBE32(0x04300000),       // Version 4.3
104     SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
105     SkEndian_SwapBE32(kRGB_ColorSpace),  // RGB input color space
106     SkEndian_SwapBE32(kXYZ_PCSSpace),    // XYZ profile connection space
107     0, 0, 0,                             // Date and time (ignored)
108     SkEndian_SwapBE32(kACSP_Signature),  // Profile signature
109     0,                                   // Platform target (ignored)
110     0x00000000,                          // Flags: not embedded, can be used independently
111     0,                                   // Device manufacturer (ignored)
112     0,                                   // Device model (ignored)
113     0, 0,                                // Device attributes (ignored)
114     SkEndian_SwapBE32(1),                // Relative colorimetric rendering intent
115     SkEndian_SwapBE32(0x0000f6d6),       // D50 standard illuminant (X)
116     SkEndian_SwapBE32(0x00010000),       // D50 standard illuminant (Y)
117     SkEndian_SwapBE32(0x0000d32d),       // D50 standard illuminant (Z)
118     0,                                   // Profile creator (ignored)
119     0, 0, 0, 0,                          // Profile id checksum (ignored)
120     0, 0, 0, 0, 0, 0, 0,                 // Reserved (ignored)
121     SkEndian_SwapBE32(kICCNumEntries),   // Number of tags
122 };
123 
124 static constexpr uint32_t kICCTagTable[3 * kICCNumEntries] {
125     // Profile description
126     SkEndian_SwapBE32(kTAG_desc),
127     SkEndian_SwapBE32(kTAG_desc_Offset),
128     SkEndian_SwapBE32(kTAG_desc_Bytes),
129 
130     // rXYZ
131     SkEndian_SwapBE32(kTAG_rXYZ),
132     SkEndian_SwapBE32(kTAG_rXYZ_Offset),
133     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
134 
135     // gXYZ
136     SkEndian_SwapBE32(kTAG_gXYZ),
137     SkEndian_SwapBE32(kTAG_gXYZ_Offset),
138     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
139 
140     // bXYZ
141     SkEndian_SwapBE32(kTAG_bXYZ),
142     SkEndian_SwapBE32(kTAG_bXYZ_Offset),
143     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
144 
145     // rTRC
146     SkEndian_SwapBE32(kTAG_rTRC),
147     SkEndian_SwapBE32(kTAG_rTRC_Offset),
148     SkEndian_SwapBE32(kTAG_TRC_Bytes),
149 
150     // gTRC
151     SkEndian_SwapBE32(kTAG_gTRC),
152     SkEndian_SwapBE32(kTAG_gTRC_Offset),
153     SkEndian_SwapBE32(kTAG_TRC_Bytes),
154 
155     // bTRC
156     SkEndian_SwapBE32(kTAG_bTRC),
157     SkEndian_SwapBE32(kTAG_bTRC_Offset),
158     SkEndian_SwapBE32(kTAG_TRC_Bytes),
159 
160     // White point
161     SkEndian_SwapBE32(kTAG_wtpt),
162     SkEndian_SwapBE32(kTAG_wtpt_Offset),
163     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
164 
165     // Copyright
166     SkEndian_SwapBE32(kTAG_cprt),
167     SkEndian_SwapBE32(kTAG_cprt_Offset),
168     SkEndian_SwapBE32(kTAG_cprt_Bytes),
169 };
170 
171 // This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible
172 // when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed).
173 // The use of double is necessary to accommodate the full potential 32-bit mantissa of the 16.16
174 // SkFixed value, and so avoiding rounding problems with float. Also, see the comment in SkFixed.h.
float_round_to_fixed(float x)175 static SkFixed float_round_to_fixed(float x) {
176     return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5));
177 }
178 
write_xyz_tag(uint32_t * ptr,const skcms_Matrix3x3 & toXYZD50,int col)179 static void write_xyz_tag(uint32_t* ptr, const skcms_Matrix3x3& toXYZD50, int col) {
180     ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
181     ptr[1] = 0;
182     ptr[2] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[0][col]));
183     ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[1][col]));
184     ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[2][col]));
185 }
186 
write_trc_tag(uint32_t * ptr,const skcms_TransferFunction & fn)187 static void write_trc_tag(uint32_t* ptr, const skcms_TransferFunction& fn) {
188     ptr[0] = SkEndian_SwapBE32(kTAG_ParaCurveType);
189     ptr[1] = 0;
190     ptr[2] = (uint32_t) (SkEndian_SwapBE16(kGABCDEF_ParaCurveType));
191     ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(fn.g));
192     ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(fn.a));
193     ptr[5] = SkEndian_SwapBE32(float_round_to_fixed(fn.b));
194     ptr[6] = SkEndian_SwapBE32(float_round_to_fixed(fn.c));
195     ptr[7] = SkEndian_SwapBE32(float_round_to_fixed(fn.d));
196     ptr[8] = SkEndian_SwapBE32(float_round_to_fixed(fn.e));
197     ptr[9] = SkEndian_SwapBE32(float_round_to_fixed(fn.f));
198 }
199 
write_cicp_tag(uint32_t * ptr,const skcms_CICP & cicp)200 static void write_cicp_tag(uint32_t* ptr, const skcms_CICP& cicp) {
201     ptr[0] = SkEndian_SwapBE32(kTAG_CICP);
202     ptr[1] = 0;
203     ptr[2] =  SkEndian_SwapBE32((((uint32_t)cicp.colour_primaries & 0xFF) << 24) |
204              (((uint32_t)cicp.transfer_characteristics & 0xFF) << 16) |
205              (((uint32_t)cicp.matrix_coefficients & 0xFF) << 8) |
206              (uint32_t)cicp.full_range_flag);
207 }
208 
nearly_equal(float x,float y)209 static bool nearly_equal(float x, float y) {
210     // A note on why I chose this tolerance:  transfer_fn_almost_equal() uses a
211     // tolerance of 0.001f, which doesn't seem to be enough to distinguish
212     // between similar transfer functions, for example: gamma2.2 and sRGB.
213     //
214     // If the tolerance is 0.0f, then this we can't distinguish between two
215     // different encodings of what is clearly the same colorspace.  Some
216     // experimentation with example files lead to this number:
217     static constexpr float kTolerance = 1.0f / (1 << 11);
218     return ::fabsf(x - y) <= kTolerance;
219 }
220 
nearly_equal(const skcms_TransferFunction & u,const skcms_TransferFunction & v)221 static bool nearly_equal(const skcms_TransferFunction& u,
222                          const skcms_TransferFunction& v) {
223     return nearly_equal(u.g, v.g)
224         && nearly_equal(u.a, v.a)
225         && nearly_equal(u.b, v.b)
226         && nearly_equal(u.c, v.c)
227         && nearly_equal(u.d, v.d)
228         && nearly_equal(u.e, v.e)
229         && nearly_equal(u.f, v.f);
230 }
231 
nearly_equal(const skcms_Matrix3x3 & u,const skcms_Matrix3x3 & v)232 static bool nearly_equal(const skcms_Matrix3x3& u, const skcms_Matrix3x3& v) {
233     for (int r = 0; r < 3; r++) {
234         for (int c = 0; c < 3; c++) {
235             if (!nearly_equal(u.vals[r][c], v.vals[r][c])) {
236                 return false;
237             }
238         }
239     }
240     return true;
241 }
242 
243 // Return nullptr if the color profile doen't have a special name.
get_color_profile_description(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)244 const char* get_color_profile_description(const skcms_TransferFunction& fn,
245                                           const skcms_Matrix3x3& toXYZD50) {
246     bool srgb_xfer = nearly_equal(fn, SkNamedTransferFn::kSRGB);
247     bool srgb_gamut = nearly_equal(toXYZD50, SkNamedGamut::kSRGB);
248     if (srgb_xfer && srgb_gamut) {
249         return "sRGB";
250     }
251     bool line_xfer = nearly_equal(fn, SkNamedTransferFn::kLinear);
252     if (line_xfer && srgb_gamut) {
253         return "Linear Transfer with sRGB Gamut";
254     }
255     bool twoDotTwo = nearly_equal(fn, SkNamedTransferFn::k2Dot2);
256     if (twoDotTwo && srgb_gamut) {
257         return "2.2 Transfer with sRGB Gamut";
258     }
259     if (twoDotTwo && nearly_equal(toXYZD50, SkNamedGamut::kAdobeRGB)) {
260         return "AdobeRGB";
261     }
262     bool display_p3 = nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3);
263     if (srgb_xfer || line_xfer) {
264         if (srgb_xfer && display_p3) {
265             return "sRGB Transfer with Display P3 Gamut";
266         }
267         if (line_xfer && display_p3) {
268             return "Linear Transfer with Display P3 Gamut";
269         }
270         bool rec2020 = nearly_equal(toXYZD50, SkNamedGamut::kRec2020);
271         if (srgb_xfer && rec2020) {
272             return "sRGB Transfer with Rec-BT-2020 Gamut";
273         }
274         if (line_xfer && rec2020) {
275             return "Linear Transfer with Rec-BT-2020 Gamut";
276         }
277     }
278     return nullptr;
279 }
280 
get_color_profile_tag(char dst[kICCDescriptionTagSize],const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)281 static void get_color_profile_tag(char dst[kICCDescriptionTagSize],
282                                   const skcms_TransferFunction& fn,
283                                   const skcms_Matrix3x3& toXYZD50) {
284     SkASSERT(dst);
285     if (const char* description = get_color_profile_description(fn, toXYZD50)) {
286         SkASSERT(strlen(description) < kICCDescriptionTagSize);
287 
288         // Without these extra (), GCC would warn us something like
289         //    ... sepecified bound 44 equals destination size ...
290         // which, yeah, is exactly what we're trying to do, copy the string
291         // and zero the rest of the destination if any.  Sheesh.
292         (strncpy(dst, description, kICCDescriptionTagSize));
293         // "If the length of src is less than n, strncpy() writes additional
294         // null bytes to dest to ensure that a total of n bytes are written."
295     } else {
296         memcpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix));
297         SkMD5 md5;
298         md5.write(&toXYZD50, sizeof(toXYZD50));
299         static_assert(sizeof(fn) == sizeof(float) * 7, "packed");
300         md5.write(&fn, sizeof(fn));
301         SkMD5::Digest digest = md5.finish();
302         char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
303         for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
304             uint8_t byte = digest.data[i];
305             *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4];
306             *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF];
307         }
308         SkASSERT(ptr == dst + kICCDescriptionTagSize);
309     }
310 }
311 
SkWriteICCProfile(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)312 sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn,
313                                 const skcms_Matrix3x3& toXYZD50) {
314     // We can't encode HDR transfer functions in ICC
315     if (classify_transfer_fn(fn) != sRGBish_TF) {
316         return nullptr;
317     }
318 
319     SkAutoMalloc profile(kICCProfileSize);
320     uint8_t* ptr = (uint8_t*) profile.get();
321 
322     // Write profile header
323     memcpy(ptr, kICCHeader, sizeof(kICCHeader));
324     ptr += sizeof(kICCHeader);
325 
326     // Write tag table
327     memcpy(ptr, kICCTagTable, sizeof(kICCTagTable));
328     ptr += sizeof(kICCTagTable);
329 
330     // Write profile description tag
331     memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
332     ptr += sizeof(kDescriptionTagHeader);
333     {
334         char colorProfileTag[kICCDescriptionTagSize];
335         get_color_profile_tag(colorProfileTag, fn, toXYZD50);
336 
337         // ASCII --> big-endian UTF-16.
338         for (size_t i = 0; i < kICCDescriptionTagSize; i++) {
339             *ptr++ = 0;
340             *ptr++ = colorProfileTag[i];
341         }
342     }
343 
344     // Write XYZ tags
345     write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
346     ptr += kTAG_XYZ_Bytes;
347     write_xyz_tag((uint32_t*) ptr, toXYZD50, 1);
348     ptr += kTAG_XYZ_Bytes;
349     write_xyz_tag((uint32_t*) ptr, toXYZD50, 2);
350     ptr += kTAG_XYZ_Bytes;
351 
352     // Write TRC tag
353     write_trc_tag((uint32_t*) ptr, fn);
354     ptr += kTAG_TRC_Bytes;
355 
356     // Write white point tag (must be D50)
357     memcpy(ptr, kWhitePointTag, sizeof(kWhitePointTag));
358     ptr += sizeof(kWhitePointTag);
359 
360     // Write copyright tag
361     memcpy(ptr, kCopyrightTagHeader, sizeof(kCopyrightTagHeader));
362     ptr += sizeof(kCopyrightTagHeader);
363     memcpy(ptr, kCopyrightTagBody, sizeof(kCopyrightTagBody));
364     ptr += sizeof(kCopyrightTagBody);
365 
366     SkASSERT(kICCProfileSize == ptr - (uint8_t*) profile.get());
367     return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
368 }
369 
SkWriteICCProfileWithCicp(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50,const skcms_CICP & cicp)370 sk_sp<SkData> SkWriteICCProfileWithCicp(const skcms_TransferFunction& fn,
371                                         const skcms_Matrix3x3& toXYZD50,
372                                         const skcms_CICP& cicp) {
373     SkAutoMalloc profile(kICCProfileSizeWithCicp);
374     uint8_t* ptr = (uint8_t*) profile.get();
375 
376     // Write profile header
377     if (memcpy_s(ptr, sizeof(kICCHeader), kICCHeader, sizeof(kICCHeader)) != EOK) {
378         return nullptr;
379     }
380     ptr += sizeof(kICCHeader);
381 
382     // Write tag table
383     if (memcpy_s(ptr, sizeof(kICCTagTable), kICCTagTable, sizeof(kICCTagTable)) != EOK) {
384         return nullptr;
385     }
386     ptr += sizeof(kICCTagTable);
387 
388     // Write profile description tag
389     if (memcpy_s(ptr, sizeof(kDescriptionTagHeader), kDescriptionTagHeader, sizeof(kDescriptionTagHeader)) != EOK) {
390         return nullptr;
391     }
392     ptr += sizeof(kDescriptionTagHeader);
393     {
394         char colorProfileTag[kICCDescriptionTagSize];
395         get_color_profile_tag(colorProfileTag, fn, toXYZD50);
396 
397         // ASCII --> big-endian UTF-16.
398         for (size_t i = 0; i < kICCDescriptionTagSize; i++) {
399             *ptr++ = 0;
400             *ptr++ = colorProfileTag[i];
401         }
402     }
403 
404     // Write XYZ tags
405     write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
406     ptr += kTAG_XYZ_Bytes;
407     write_xyz_tag((uint32_t*) ptr, toXYZD50, 1);
408     ptr += kTAG_XYZ_Bytes;
409     write_xyz_tag((uint32_t*) ptr, toXYZD50, 2);
410     ptr += kTAG_XYZ_Bytes;
411 
412     // Write TRC tag
413     write_trc_tag((uint32_t*) ptr, fn);
414     ptr += kTAG_TRC_Bytes;
415 
416     // Write white point tag (must be D50)
417     if (memcpy_s(ptr, sizeof(kWhitePointTag), kWhitePointTag, sizeof(kWhitePointTag)) != EOK) {
418         return nullptr;
419     }
420     ptr += sizeof(kWhitePointTag);
421 
422     // Write copyright tag
423     if (memcpy_s(ptr, sizeof(kCopyrightTagHeader), kCopyrightTagHeader, sizeof(kCopyrightTagHeader)) != EOK) {
424         return nullptr;
425     }
426     ptr += sizeof(kCopyrightTagHeader);
427     if (memcpy_s(ptr, sizeof(kCopyrightTagBody), kCopyrightTagBody, sizeof(kCopyrightTagBody) != EOK)) {
428         return nullptr;
429     }
430     ptr += sizeof(kCopyrightTagBody);
431 
432     // write cicp tag
433     write_cicp_tag((uint32_t*) ptr, cicp);
434     ptr += kTAG_cicp_bytes;
435 
436     SkASSERT(kICCProfileSizeWithCicp == ptr - (uint8_t*) profile.get());
437     return SkData::MakeFromMalloc(profile.release(), kICCProfileSizeWithCicp);
438 }
439