OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2016 Google Inc. | 2 * Copyright 2016 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SkColorSpace.h" | 8 #include "SkColorSpace.h" |
9 #include "SkColorSpace_Base.h" | 9 #include "SkColorSpace_Base.h" |
10 #include "SkEndian.h" | 10 #include "SkColorSpacePriv.h" |
11 #include "SkOnce.h" | 11 #include "SkOnce.h" |
12 #include "SkReadBuffer.h" | |
13 #include "SkWriteBuffer.h" | |
14 | |
15 #define SkColorSpacePrintf(...) | |
16 | |
17 static bool color_space_almost_equal(float a, float b) { | |
18 return SkTAbs(a - b) < 0.01f; | |
19 } | |
20 | |
21 ////////////////////////////////////////////////////////////////////////////////
////////////////// | |
22 | 12 |
23 SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Na
med named) | 13 SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Na
med named) |
24 : fGammaNamed(gammaNamed) | 14 : fGammaNamed(gammaNamed) |
25 , fToXYZD50(toXYZD50) | 15 , fToXYZD50(toXYZD50) |
26 , fNamed(named) | 16 , fNamed(named) |
27 {} | 17 {} |
28 | 18 |
29 SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& to
XYZD50, Named named) | 19 SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& to
XYZD50, Named named) |
30 : INHERITED(gammaNamed, toXYZD50, named) | 20 : INHERITED(gammaNamed, toXYZD50, named) |
31 , fGammas(nullptr) | 21 , fGammas(nullptr) |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
70 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && | 60 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && |
71 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && | 61 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && |
72 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && | 62 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && |
73 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && | 63 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && |
74 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && | 64 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && |
75 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && | 65 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && |
76 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && | 66 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && |
77 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); | 67 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); |
78 } | 68 } |
79 | 69 |
80 static void set_gamma_value(SkGammaCurve* gamma, float value) { | |
81 if (color_space_almost_equal(2.2f, value)) { | |
82 gamma->fNamed = SkColorSpace::k2Dot2Curve_GammaNamed; | |
83 } else if (color_space_almost_equal(1.0f, value)) { | |
84 gamma->fNamed = SkColorSpace::kLinear_GammaNamed; | |
85 } else if (color_space_almost_equal(0.0f, value)) { | |
86 SkColorSpacePrintf("Treating invalid zero gamma as linear."); | |
87 gamma->fNamed = SkColorSpace::kLinear_GammaNamed; | |
88 } else { | |
89 gamma->fValue = value; | |
90 } | |
91 } | |
92 | |
93 sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(float values[3], const SkMatrix44&
toXYZD50) { | 70 sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(float values[3], const SkMatrix44&
toXYZD50) { |
94 SkGammaCurve curves[3]; | 71 SkGammaCurve curves[3]; |
95 set_gamma_value(&curves[0], values[0]); | 72 set_gamma_value(&curves[0], values[0]); |
96 set_gamma_value(&curves[1], values[1]); | 73 set_gamma_value(&curves[1], values[1]); |
97 set_gamma_value(&curves[2], values[2]); | 74 set_gamma_value(&curves[2], values[2]); |
98 | 75 |
99 GammaNamed gammaNamed = SkGammas::Named(curves); | 76 GammaNamed gammaNamed = SkGammas::Named(curves); |
100 if (kNonStandard_GammaNamed == gammaNamed) { | 77 if (kNonStandard_GammaNamed == gammaNamed) { |
101 sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curv
es[1]), | 78 sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curv
es[1]), |
102 std::move(curves[2]))); | 79 std::move(curves[2]))); |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
157 return sk_ref_sp(adobeRGB); | 134 return sk_ref_sp(adobeRGB); |
158 } | 135 } |
159 default: | 136 default: |
160 break; | 137 break; |
161 } | 138 } |
162 return nullptr; | 139 return nullptr; |
163 } | 140 } |
164 | 141 |
165 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | 142 ////////////////////////////////////////////////////////////////////////////////
/////////////////// |
166 | 143 |
167 #include "SkFixed.h" | |
168 #include "SkTemplates.h" | |
169 | |
170 #define return_if_false(pred, msg) \ | |
171 do { \ | |
172 if (!(pred)) { \ | |
173 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ | |
174 return false; \ | |
175 } \ | |
176 } while (0) | |
177 | |
178 #define return_null(msg) \ | |
179 do { \ | |
180 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ | |
181 return nullptr; \ | |
182 } while (0) | |
183 | |
184 static uint16_t read_big_endian_short(const uint8_t* ptr) { | |
185 return ptr[0] << 8 | ptr[1]; | |
186 } | |
187 | |
188 static uint32_t read_big_endian_uint(const uint8_t* ptr) { | |
189 return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; | |
190 } | |
191 | |
192 static int32_t read_big_endian_int(const uint8_t* ptr) { | |
193 return (int32_t) read_big_endian_uint(ptr); | |
194 } | |
195 | |
196 // This is equal to the header size according to the ICC specification (128) | |
197 // plus the size of the tag count (4). We include the tag count since we | |
198 // always require it to be present anyway. | |
199 static constexpr size_t kICCHeaderSize = 132; | |
200 | |
201 // Contains a signature (4), offset (4), and size (4). | |
202 static constexpr size_t kICCTagTableEntrySize = 12; | |
203 | |
204 static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '
); | |
205 static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'
); | |
206 static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'
); | |
207 static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'
); | |
208 static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '
); | |
209 static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p'
); | |
210 | |
211 struct ICCProfileHeader { | |
212 uint32_t fSize; | |
213 | |
214 // No reason to care about the preferred color management module (ex: Adobe,
Apple, etc.). | |
215 // We're always going to use this one. | |
216 uint32_t fCMMType_ignored; | |
217 | |
218 uint32_t fVersion; | |
219 uint32_t fProfileClass; | |
220 uint32_t fInputColorSpace; | |
221 uint32_t fPCS; | |
222 uint32_t fDateTime_ignored[3]; | |
223 uint32_t fSignature; | |
224 | |
225 // Indicates the platform that this profile was created for (ex: Apple, Micr
osoft). This | |
226 // doesn't really matter to us. | |
227 uint32_t fPlatformTarget_ignored; | |
228 | |
229 // Flags can indicate: | |
230 // (1) Whether this profile was embedded in a file. This flag is consistent
ly wrong. | |
231 // Ex: The profile came from a file but indicates that it did not. | |
232 // (2) Whether we are allowed to use the profile independently of the color
data. If set, | |
233 // this may allow us to use the embedded profile for testing separate fr
om the original | |
234 // image. | |
235 uint32_t fFlags_ignored; | |
236 | |
237 // We support many output devices. It doesn't make sense to think about the
attributes of | |
238 // the device in the context of the image profile. | |
239 uint32_t fDeviceManufacturer_ignored; | |
240 uint32_t fDeviceModel_ignored; | |
241 uint32_t fDeviceAttributes_ignored[2]; | |
242 | |
243 uint32_t fRenderingIntent; | |
244 int32_t fIlluminantXYZ[3]; | |
245 | |
246 // We don't care who created the profile. | |
247 uint32_t fCreator_ignored; | |
248 | |
249 // This is an MD5 checksum. Could be useful for checking if profiles are eq
ual. | |
250 uint32_t fProfileId_ignored[4]; | |
251 | |
252 // Reserved for future use. | |
253 uint32_t fReserved_ignored[7]; | |
254 | |
255 uint32_t fTagCount; | |
256 | |
257 void init(const uint8_t* src, size_t len) { | |
258 SkASSERT(kICCHeaderSize == sizeof(*this)); | |
259 | |
260 uint32_t* dst = (uint32_t*) this; | |
261 for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { | |
262 dst[i] = read_big_endian_uint(src); | |
263 } | |
264 } | |
265 | |
266 bool valid() const { | |
267 return_if_false(fSize >= kICCHeaderSize, "Size is too small"); | |
268 | |
269 uint8_t majorVersion = fVersion >> 24; | |
270 return_if_false(majorVersion <= 4, "Unsupported version"); | |
271 | |
272 // These are the three basic classes of profiles that we might expect to
see embedded | |
273 // in images. Four additional classes exist, but they generally are use
d as a convenient | |
274 // way for CMMs to store calculated transforms. | |
275 return_if_false(fProfileClass == kDisplay_Profile || | |
276 fProfileClass == kInput_Profile || | |
277 fProfileClass == kOutput_Profile, | |
278 "Unsupported profile"); | |
279 | |
280 // TODO (msarett): | |
281 // All the profiles we've tested so far use RGB as the input color space
. | |
282 return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color
space"); | |
283 | |
284 // TODO (msarett): | |
285 // All the profiles we've tested so far use XYZ as the profile connectio
n space. | |
286 return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space"); | |
287 | |
288 return_if_false(fSignature == kACSP_Signature, "Bad signature"); | |
289 | |
290 // TODO (msarett): | |
291 // Should we treat different rendering intents differently? | |
292 // Valid rendering intents include kPerceptual (0), kRelative (1), | |
293 // kSaturation (2), and kAbsolute (3). | |
294 return_if_false(fRenderingIntent <= 3, "Bad rendering intent"); | |
295 | |
296 return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0
]), 0.96420f) && | |
297 color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1
]), 1.00000f) && | |
298 color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2
]), 0.82491f), | |
299 "Illuminant must be D50"); | |
300 | |
301 return_if_false(fTagCount <= 100, "Too many tags"); | |
302 | |
303 return true; | |
304 } | |
305 }; | |
306 | |
307 template <class T> | |
308 static bool safe_add(T arg1, T arg2, size_t* result) { | |
309 SkASSERT(arg1 >= 0); | |
310 SkASSERT(arg2 >= 0); | |
311 if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) { | |
312 T sum = arg1 + arg2; | |
313 if (sum <= std::numeric_limits<size_t>::max()) { | |
314 *result = static_cast<size_t>(sum); | |
315 return true; | |
316 } | |
317 } | |
318 return false; | |
319 } | |
320 | |
321 static bool safe_mul(uint32_t arg1, uint32_t arg2, uint32_t* result) { | |
322 uint64_t product64 = (uint64_t) arg1 * (uint64_t) arg2; | |
323 uint32_t product32 = (uint32_t) product64; | |
324 if (product32 != product64) { | |
325 return false; | |
326 } | |
327 | |
328 *result = product32; | |
329 return true; | |
330 } | |
331 | |
332 struct ICCTag { | |
333 uint32_t fSignature; | |
334 uint32_t fOffset; | |
335 uint32_t fLength; | |
336 | |
337 const uint8_t* init(const uint8_t* src) { | |
338 fSignature = read_big_endian_uint(src); | |
339 fOffset = read_big_endian_uint(src + 4); | |
340 fLength = read_big_endian_uint(src + 8); | |
341 return src + 12; | |
342 } | |
343 | |
344 bool valid(size_t len) { | |
345 size_t tagEnd; | |
346 return_if_false(safe_add(fOffset, fLength, &tagEnd), | |
347 "Tag too large, overflows integer addition"); | |
348 return_if_false(tagEnd <= len, "Tag too large for ICC profile"); | |
349 return true; | |
350 } | |
351 | |
352 const uint8_t* addr(const uint8_t* src) const { | |
353 return src + fOffset; | |
354 } | |
355 | |
356 static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature
) { | |
357 for (int i = 0; i < count; ++i) { | |
358 if (tags[i].fSignature == signature) { | |
359 return &tags[i]; | |
360 } | |
361 } | |
362 return nullptr; | |
363 } | |
364 }; | |
365 | |
366 static constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z'); | |
367 static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z'); | |
368 static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z'); | |
369 static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C'); | |
370 static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C'); | |
371 static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C'); | |
372 static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0'); | |
373 | |
374 static bool load_xyz(float dst[3], const uint8_t* src, size_t len) { | |
375 if (len < 20) { | |
376 SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); | |
377 return false; | |
378 } | |
379 | |
380 dst[0] = SkFixedToFloat(read_big_endian_int(src + 8)); | |
381 dst[1] = SkFixedToFloat(read_big_endian_int(src + 12)); | |
382 dst[2] = SkFixedToFloat(read_big_endian_int(src + 16)); | |
383 SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); | |
384 return true; | |
385 } | |
386 | |
387 static constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', '
v'); | |
388 static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', '
a'); | |
389 | |
390 static bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t*
src, size_t len) { | |
391 for (uint32_t i = 0; i < numGammas; i++) { | |
392 if (len < 12) { | |
393 // FIXME (msarett): | |
394 // We could potentially return false here after correctly parsing *s
ome* of the | |
395 // gammas correctly. Should we somehow try to indicate a partial su
ccess? | |
396 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); | |
397 return false; | |
398 } | |
399 | |
400 // We need to count the number of bytes in the tag, so we are able to mo
ve to the | |
401 // next tag on the next loop iteration. | |
402 size_t tagBytes; | |
403 | |
404 uint32_t type = read_big_endian_uint(src); | |
405 switch (type) { | |
406 case kTAG_CurveType: { | |
407 uint32_t count = read_big_endian_uint(src + 8); | |
408 | |
409 // tagBytes = 12 + 2 * count | |
410 // We need to do safe addition here to avoid integer overflow. | |
411 if (!safe_add(count, count, &tagBytes) || | |
412 !safe_add((size_t) 12, tagBytes, &tagBytes)) | |
413 { | |
414 SkColorSpacePrintf("Invalid gamma count"); | |
415 return false; | |
416 } | |
417 | |
418 if (0 == count) { | |
419 // Some tags require a gamma curve, but the author doesn't a
ctually want | |
420 // to transform the data. In this case, it is common to see
a curve with | |
421 // a count of 0. | |
422 gammas[i].fNamed = SkColorSpace::kLinear_GammaNamed; | |
423 break; | |
424 } else if (len < tagBytes) { | |
425 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len)
; | |
426 return false; | |
427 } | |
428 | |
429 const uint16_t* table = (const uint16_t*) (src + 12); | |
430 if (1 == count) { | |
431 // The table entry is the gamma (with a bias of 256). | |
432 float value = (read_big_endian_short((const uint8_t*) table)
) / 256.0f; | |
433 set_gamma_value(&gammas[i], value); | |
434 SkColorSpacePrintf("gamma %g\n", value); | |
435 break; | |
436 } | |
437 | |
438 // Check for frequently occurring sRGB curves. | |
439 // We do this by sampling a few values and see if they match our
expectation. | |
440 // A more robust solution would be to compare each value in this
curve against | |
441 // an sRGB curve to see if we remain below an error threshold.
At this time, | |
442 // we haven't seen any images in the wild that make this kind of | |
443 // calculation necessary. We encounter identical gamma curves o
ver and | |
444 // over again, but relatively few variations. | |
445 if (1024 == count) { | |
446 // The magic values were chosen because they match a very co
mmon sRGB | |
447 // gamma table and the less common Canon sRGB gamma table (w
hich use | |
448 // different rounding rules). | |
449 if (0 == read_big_endian_short((const uint8_t*) &table[0]) &
& | |
450 3366 == read_big_endian_short((const uint8_t*) &tabl
e[257]) && | |
451 14116 == read_big_endian_short((const uint8_t*) &tab
le[513]) && | |
452 34318 == read_big_endian_short((const uint8_t*) &tab
le[768]) && | |
453 65535 == read_big_endian_short((const uint8_t*) &tab
le[1023])) { | |
454 gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
455 break; | |
456 } | |
457 } else if (26 == count) { | |
458 // The magic values were chosen because they match a very co
mmon sRGB | |
459 // gamma table. | |
460 if (0 == read_big_endian_short((const uint8_t*) &table[0]) &
& | |
461 3062 == read_big_endian_short((const uint8_t*) &tabl
e[6]) && | |
462 12824 == read_big_endian_short((const uint8_t*) &tab
le[12]) && | |
463 31237 == read_big_endian_short((const uint8_t*) &tab
le[18]) && | |
464 65535 == read_big_endian_short((const uint8_t*) &tab
le[25])) { | |
465 gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
466 break; | |
467 } | |
468 } else if (4096 == count) { | |
469 // The magic values were chosen because they match Nikon, Ep
son, and | |
470 // LCMS sRGB gamma tables (all of which use different roundi
ng rules). | |
471 if (0 == read_big_endian_short((const uint8_t*) &table[0]) &
& | |
472 950 == read_big_endian_short((const uint8_t*) &table
[515]) && | |
473 3342 == read_big_endian_short((const uint8_t*) &tabl
e[1025]) && | |
474 14079 == read_big_endian_short((const uint8_t*) &tab
le[2051]) && | |
475 65535 == read_big_endian_short((const uint8_t*) &tab
le[4095])) { | |
476 gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
477 break; | |
478 } | |
479 } | |
480 | |
481 // Otherwise, fill in the interpolation table. | |
482 gammas[i].fTableSize = count; | |
483 gammas[i].fTable = std::unique_ptr<float[]>(new float[count]); | |
484 for (uint32_t j = 0; j < count; j++) { | |
485 gammas[i].fTable[j] = | |
486 (read_big_endian_short((const uint8_t*) &table[j]))
/ 65535.0f; | |
487 } | |
488 break; | |
489 } | |
490 case kTAG_ParaCurveType: { | |
491 enum ParaCurveType { | |
492 kExponential_ParaCurveType = 0, | |
493 kGAB_ParaCurveType = 1, | |
494 kGABC_ParaCurveType = 2, | |
495 kGABDE_ParaCurveType = 3, | |
496 kGABCDEF_ParaCurveType = 4, | |
497 }; | |
498 | |
499 // Determine the format of the parametric curve tag. | |
500 uint16_t format = read_big_endian_short(src + 8); | |
501 if (kExponential_ParaCurveType == format) { | |
502 tagBytes = 12 + 4; | |
503 if (len < tagBytes) { | |
504 SkColorSpacePrintf("gamma tag is too small (%d bytes)",
len); | |
505 return false; | |
506 } | |
507 | |
508 // Y = X^g | |
509 int32_t g = read_big_endian_int(src + 12); | |
510 set_gamma_value(&gammas[i], SkFixedToFloat(g)); | |
511 } else { | |
512 // Here's where the real parametric gammas start. There are
many | |
513 // permutations of the same equations. | |
514 // | |
515 // Y = (aX + b)^g + c for X >= d | |
516 // Y = eX + f otherwise | |
517 // | |
518 // We will fill in with zeros as necessary to always match t
he above form. | |
519 float g = 0.0f, a = 0.0f, b = 0.0f, c = 0.0f, d = 0.0f, e =
0.0f, f = 0.0f; | |
520 switch(format) { | |
521 case kGAB_ParaCurveType: { | |
522 tagBytes = 12 + 12; | |
523 if (len < tagBytes) { | |
524 SkColorSpacePrintf("gamma tag is too small (%d b
ytes)", len); | |
525 return false; | |
526 } | |
527 | |
528 // Y = (aX + b)^g for X >= -b/a | |
529 // Y = 0 otherwise | |
530 g = SkFixedToFloat(read_big_endian_int(src + 12)); | |
531 a = SkFixedToFloat(read_big_endian_int(src + 16)); | |
532 if (0.0f == a) { | |
533 return false; | |
534 } | |
535 | |
536 b = SkFixedToFloat(read_big_endian_int(src + 20)); | |
537 d = -b / a; | |
538 break; | |
539 } | |
540 case kGABC_ParaCurveType: | |
541 tagBytes = 12 + 16; | |
542 if (len < tagBytes) { | |
543 SkColorSpacePrintf("gamma tag is too small (%d b
ytes)", len); | |
544 return false; | |
545 } | |
546 | |
547 // Y = (aX + b)^g + c for X >= -b/a | |
548 // Y = c otherwise | |
549 g = SkFixedToFloat(read_big_endian_int(src + 12)); | |
550 a = SkFixedToFloat(read_big_endian_int(src + 16)); | |
551 if (0.0f == a) { | |
552 return false; | |
553 } | |
554 | |
555 b = SkFixedToFloat(read_big_endian_int(src + 20)); | |
556 c = SkFixedToFloat(read_big_endian_int(src + 24)); | |
557 d = -b / a; | |
558 f = c; | |
559 break; | |
560 case kGABDE_ParaCurveType: | |
561 tagBytes = 12 + 20; | |
562 if (len < tagBytes) { | |
563 SkColorSpacePrintf("gamma tag is too small (%d b
ytes)", len); | |
564 return false; | |
565 } | |
566 | |
567 // Y = (aX + b)^g for X >= d | |
568 // Y = cX otherwise | |
569 g = SkFixedToFloat(read_big_endian_int(src + 12)); | |
570 a = SkFixedToFloat(read_big_endian_int(src + 16)); | |
571 b = SkFixedToFloat(read_big_endian_int(src + 20)); | |
572 d = SkFixedToFloat(read_big_endian_int(src + 28)); | |
573 e = SkFixedToFloat(read_big_endian_int(src + 24)); | |
574 break; | |
575 case kGABCDEF_ParaCurveType: | |
576 tagBytes = 12 + 28; | |
577 if (len < tagBytes) { | |
578 SkColorSpacePrintf("gamma tag is too small (%d b
ytes)", len); | |
579 return false; | |
580 } | |
581 | |
582 // Y = (aX + b)^g + c for X >= d | |
583 // Y = eX + f otherwise | |
584 // NOTE: The ICC spec writes "cX" in place of "eX" b
ut I think | |
585 // it's a typo. | |
586 g = SkFixedToFloat(read_big_endian_int(src + 12)); | |
587 a = SkFixedToFloat(read_big_endian_int(src + 16)); | |
588 b = SkFixedToFloat(read_big_endian_int(src + 20)); | |
589 c = SkFixedToFloat(read_big_endian_int(src + 24)); | |
590 d = SkFixedToFloat(read_big_endian_int(src + 28)); | |
591 e = SkFixedToFloat(read_big_endian_int(src + 32)); | |
592 f = SkFixedToFloat(read_big_endian_int(src + 36)); | |
593 break; | |
594 default: | |
595 SkColorSpacePrintf("Invalid parametric curve type\n"
); | |
596 return false; | |
597 } | |
598 | |
599 // Recognize and simplify a very common parametric represent
ation of sRGB gamma. | |
600 if (color_space_almost_equal(0.9479f, a) && | |
601 color_space_almost_equal(0.0521f, b) && | |
602 color_space_almost_equal(0.0000f, c) && | |
603 color_space_almost_equal(0.0405f, d) && | |
604 color_space_almost_equal(0.0774f, e) && | |
605 color_space_almost_equal(0.0000f, f) && | |
606 color_space_almost_equal(2.4000f, g)) { | |
607 gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
608 } else { | |
609 // Fail on invalid gammas. | |
610 if (d <= 0.0f) { | |
611 // Y = (aX + b)^g + c for always | |
612 if (0.0f == a || 0.0f == g) { | |
613 SkColorSpacePrintf("A or G is zero, constant gam
ma function " | |
614 "is nonsense"); | |
615 return false; | |
616 } | |
617 } else if (d >= 1.0f) { | |
618 // Y = eX + f for always | |
619 if (0.0f == e) { | |
620 SkColorSpacePrintf("E is zero, constant gamma fu
nction is " | |
621 "nonsense"); | |
622 return false; | |
623 } | |
624 } else if ((0.0f == a || 0.0f == g) && 0.0f == e) { | |
625 SkColorSpacePrintf("A or G, and E are zero, constant
gamma function " | |
626 "is nonsense"); | |
627 return false; | |
628 } | |
629 | |
630 gammas[i].fG = g; | |
631 gammas[i].fA = a; | |
632 gammas[i].fB = b; | |
633 gammas[i].fC = c; | |
634 gammas[i].fD = d; | |
635 gammas[i].fE = e; | |
636 gammas[i].fF = f; | |
637 } | |
638 } | |
639 | |
640 break; | |
641 } | |
642 default: | |
643 SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); | |
644 return false; | |
645 } | |
646 | |
647 // Ensure that we have successfully read a gamma representation. | |
648 SkASSERT(gammas[i].isNamed() || gammas[i].isValue() || gammas[i].isTable
() || | |
649 gammas[i].isParametric()); | |
650 | |
651 // Adjust src and len if there is another gamma curve to load. | |
652 if (i != numGammas - 1) { | |
653 // Each curve is padded to 4-byte alignment. | |
654 tagBytes = SkAlign4(tagBytes); | |
655 if (len < tagBytes) { | |
656 return false; | |
657 } | |
658 | |
659 src += tagBytes; | |
660 len -= tagBytes; | |
661 } | |
662 } | |
663 | |
664 return true; | |
665 } | |
666 | |
667 static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' '); | |
668 | |
669 bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels, uint32
_t outputChannels, | |
670 const uint8_t* src, size_t len) { | |
671 // 16 bytes reserved for grid points, 2 for precision, 2 for padding. | |
672 // The color LUT data follows after this header. | |
673 static constexpr uint32_t kColorLUTHeaderSize = 20; | |
674 if (len < kColorLUTHeaderSize) { | |
675 SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len); | |
676 return false; | |
677 } | |
678 size_t dataLen = len - kColorLUTHeaderSize; | |
679 | |
680 SkASSERT(3 == inputChannels && 3 == outputChannels); | |
681 colorLUT->fInputChannels = inputChannels; | |
682 colorLUT->fOutputChannels = outputChannels; | |
683 uint32_t numEntries = 1; | |
684 for (uint32_t i = 0; i < inputChannels; i++) { | |
685 colorLUT->fGridPoints[i] = src[i]; | |
686 if (0 == src[i]) { | |
687 SkColorSpacePrintf("Each input channel must have at least one grid p
oint."); | |
688 return false; | |
689 } | |
690 | |
691 if (!safe_mul(numEntries, src[i], &numEntries)) { | |
692 SkColorSpacePrintf("Too many entries in Color LUT."); | |
693 return false; | |
694 } | |
695 } | |
696 | |
697 if (!safe_mul(numEntries, outputChannels, &numEntries)) { | |
698 SkColorSpacePrintf("Too many entries in Color LUT."); | |
699 return false; | |
700 } | |
701 | |
702 // Space is provided for a maximum of the 16 input channels. Now we determi
ne the precision | |
703 // of the table values. | |
704 uint8_t precision = src[16]; | |
705 switch (precision) { | |
706 case 1: // 8-bit data | |
707 case 2: // 16-bit data | |
708 break; | |
709 default: | |
710 SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit.\n")
; | |
711 return false; | |
712 } | |
713 | |
714 uint32_t clutBytes; | |
715 if (!safe_mul(numEntries, precision, &clutBytes)) { | |
716 SkColorSpacePrintf("Too many entries in Color LUT."); | |
717 return false; | |
718 } | |
719 | |
720 if (dataLen < clutBytes) { | |
721 SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len); | |
722 return false; | |
723 } | |
724 | |
725 // Movable struct colorLUT has ownership of fTable. | |
726 colorLUT->fTable = std::unique_ptr<float[]>(new float[numEntries]); | |
727 const uint8_t* ptr = src + kColorLUTHeaderSize; | |
728 for (uint32_t i = 0; i < numEntries; i++, ptr += precision) { | |
729 if (1 == precision) { | |
730 colorLUT->fTable[i] = ((float) ptr[i]) / 255.0f; | |
731 } else { | |
732 colorLUT->fTable[i] = ((float) read_big_endian_short(ptr)) / 65535.0
f; | |
733 } | |
734 } | |
735 | |
736 return true; | |
737 } | |
738 | |
739 bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) { | |
740 if (len < 48) { | |
741 SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len); | |
742 return false; | |
743 } | |
744 | |
745 // For this matrix to behave like our "to XYZ D50" matrices, it needs to be
scaled. | |
746 constexpr float scale = 65535.0 / 32768.0; | |
747 float array[16]; | |
748 array[ 0] = scale * SkFixedToFloat(read_big_endian_int(src)); | |
749 array[ 1] = scale * SkFixedToFloat(read_big_endian_int(src + 4)); | |
750 array[ 2] = scale * SkFixedToFloat(read_big_endian_int(src + 8)); | |
751 array[ 3] = scale * SkFixedToFloat(read_big_endian_int(src + 36)); // transl
ate R | |
752 array[ 4] = scale * SkFixedToFloat(read_big_endian_int(src + 12)); | |
753 array[ 5] = scale * SkFixedToFloat(read_big_endian_int(src + 16)); | |
754 array[ 6] = scale * SkFixedToFloat(read_big_endian_int(src + 20)); | |
755 array[ 7] = scale * SkFixedToFloat(read_big_endian_int(src + 40)); // transl
ate G | |
756 array[ 8] = scale * SkFixedToFloat(read_big_endian_int(src + 24)); | |
757 array[ 9] = scale * SkFixedToFloat(read_big_endian_int(src + 28)); | |
758 array[10] = scale * SkFixedToFloat(read_big_endian_int(src + 32)); | |
759 array[11] = scale * SkFixedToFloat(read_big_endian_int(src + 44)); // transl
ate B | |
760 array[12] = 0.0f; | |
761 array[13] = 0.0f; | |
762 array[14] = 0.0f; | |
763 array[15] = 1.0f; | |
764 toXYZ->setColMajorf(array); | |
765 return true; | |
766 } | |
767 | |
768 bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* t
oXYZ, | |
769 const uint8_t* src, size_t len) { | |
770 if (len < 32) { | |
771 SkColorSpacePrintf("A to B tag is too small (%d bytes).", len); | |
772 return false; | |
773 } | |
774 | |
775 uint32_t type = read_big_endian_uint(src); | |
776 if (kTAG_AtoBType != type) { | |
777 // FIXME (msarett): Need to support lut8Type and lut16Type. | |
778 SkColorSpacePrintf("Unsupported A to B tag type.\n"); | |
779 return false; | |
780 } | |
781 | |
782 // Read the number of channels. The four bytes that we skipped are reserved
and | |
783 // must be zero. | |
784 uint8_t inputChannels = src[8]; | |
785 uint8_t outputChannels = src[9]; | |
786 if (3 != inputChannels || 3 != outputChannels) { | |
787 // We only handle (supposedly) RGB inputs and RGB outputs. The numbers
of input | |
788 // channels and output channels both must be 3. | |
789 SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag
.\n"); | |
790 return false; | |
791 } | |
792 | |
793 // Read the offsets of each element in the A to B tag. With the exception o
f A curves and | |
794 // B curves (which we do not yet support), we will handle these elements in
the order in | |
795 // which they should be applied (rather than the order in which they occur i
n the tag). | |
796 // If the offset is non-zero it indicates that the element is present. | |
797 uint32_t offsetToACurves = read_big_endian_int(src + 28); | |
798 uint32_t offsetToBCurves = read_big_endian_int(src + 12); | |
799 if ((0 != offsetToACurves) || (0 != offsetToBCurves)) { | |
800 // FIXME (msarett): Handle A and B curves. | |
801 // Note that the A curve is technically required in order to have a colo
r LUT. | |
802 // However, all the A curves I have seen so far have are just placeholde
rs that | |
803 // don't actually transform the data. | |
804 SkColorSpacePrintf("Ignoring A and/or B curve. Output may be wrong.\n")
; | |
805 } | |
806 | |
807 uint32_t offsetToColorLUT = read_big_endian_int(src + 24); | |
808 if (0 != offsetToColorLUT && offsetToColorLUT < len) { | |
809 if (!load_color_lut(colorLUT, inputChannels, outputChannels, src + offse
tToColorLUT, | |
810 len - offsetToColorLUT)) { | |
811 SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n"); | |
812 } | |
813 } | |
814 | |
815 uint32_t offsetToMCurves = read_big_endian_int(src + 20); | |
816 if (0 != offsetToMCurves && offsetToMCurves < len) { | |
817 if (!load_gammas(gammas, outputChannels, src + offsetToMCurves, len - of
fsetToMCurves)) { | |
818 SkColorSpacePrintf("Failed to read M curves from A to B tag. Using
linear gamma.\n"); | |
819 gammas[0].fNamed = SkColorSpace::kLinear_GammaNamed; | |
820 gammas[1].fNamed = SkColorSpace::kLinear_GammaNamed; | |
821 gammas[2].fNamed = SkColorSpace::kLinear_GammaNamed; | |
822 } | |
823 } | |
824 | |
825 uint32_t offsetToMatrix = read_big_endian_int(src + 16); | |
826 if (0 != offsetToMatrix && offsetToMatrix < len) { | |
827 if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) { | |
828 SkColorSpacePrintf("Failed to read matrix from A to B tag.\n"); | |
829 toXYZ->setIdentity(); | |
830 } | |
831 } | |
832 | |
833 return true; | |
834 } | |
835 | |
836 sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) { | |
837 if (!input || len < kICCHeaderSize) { | |
838 return_null("Data is null or not large enough to contain an ICC profile"
); | |
839 } | |
840 | |
841 // Create our own copy of the input. | |
842 void* memory = sk_malloc_throw(len); | |
843 memcpy(memory, input, len); | |
844 sk_sp<SkData> data = SkData::MakeFromMalloc(memory, len); | |
845 const void* base = data->data(); | |
846 const uint8_t* ptr = (const uint8_t*) base; | |
847 | |
848 // Read the ICC profile header and check to make sure that it is valid. | |
849 ICCProfileHeader header; | |
850 header.init(ptr, len); | |
851 if (!header.valid()) { | |
852 return nullptr; | |
853 } | |
854 | |
855 // Adjust ptr and len before reading the tags. | |
856 if (len < header.fSize) { | |
857 SkColorSpacePrintf("ICC profile might be truncated.\n"); | |
858 } else if (len > header.fSize) { | |
859 SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC
profile.\n"); | |
860 len = header.fSize; | |
861 } | |
862 ptr += kICCHeaderSize; | |
863 len -= kICCHeaderSize; | |
864 | |
865 // Parse tag headers. | |
866 uint32_t tagCount = header.fTagCount; | |
867 SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); | |
868 if (len < kICCTagTableEntrySize * tagCount) { | |
869 return_null("Not enough input data to read tag table entries"); | |
870 } | |
871 | |
872 SkAutoTArray<ICCTag> tags(tagCount); | |
873 for (uint32_t i = 0; i < tagCount; i++) { | |
874 ptr = tags[i].init(ptr); | |
875 SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24
) & 0xFF, | |
876 (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) &
0xFF, | |
877 (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLen
gth); | |
878 | |
879 if (!tags[i].valid(kICCHeaderSize + len)) { | |
880 return_null("Tag is too large to fit in ICC profile"); | |
881 } | |
882 } | |
883 | |
884 switch (header.fInputColorSpace) { | |
885 case kRGB_ColorSpace: { | |
886 // Recognize the rXYZ, gXYZ, and bXYZ tags. | |
887 const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); | |
888 const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); | |
889 const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); | |
890 if (r && g && b) { | |
891 float toXYZ[9]; | |
892 if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLen
gth) || | |
893 !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLen
gth) || | |
894 !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLen
gth)) | |
895 { | |
896 return_null("Need valid rgb tags for XYZ space"); | |
897 } | |
898 SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); | |
899 mat.set3x3RowMajorf(toXYZ); | |
900 | |
901 // It is not uncommon to see missing or empty gamma tags. This
indicates | |
902 // that we should use unit gamma. | |
903 SkGammaCurve curves[3]; | |
904 r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); | |
905 g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); | |
906 b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); | |
907 if (!r || !load_gammas(&curves[0], 1, r->addr((const uint8_t*) b
ase), r->fLength)) | |
908 { | |
909 SkColorSpacePrintf("Failed to read R gamma tag.\n"); | |
910 curves[0].fNamed = SkColorSpace::kLinear_GammaNamed; | |
911 } | |
912 if (!g || !load_gammas(&curves[1], 1, g->addr((const uint8_t*) b
ase), g->fLength)) | |
913 { | |
914 SkColorSpacePrintf("Failed to read G gamma tag.\n"); | |
915 curves[1].fNamed = SkColorSpace::kLinear_GammaNamed; | |
916 } | |
917 if (!b || !load_gammas(&curves[2], 1, b->addr((const uint8_t*) b
ase), b->fLength)) | |
918 { | |
919 SkColorSpacePrintf("Failed to read B gamma tag.\n"); | |
920 curves[2].fNamed = SkColorSpace::kLinear_GammaNamed; | |
921 } | |
922 | |
923 GammaNamed gammaNamed = SkGammas::Named(curves); | |
924 if (kNonStandard_GammaNamed == gammaNamed) { | |
925 sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curv
es[0]), | |
926 std::move(curv
es[1]), | |
927 std::move(curv
es[2])); | |
928 return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, st
d::move(gammas), | |
929 mat, std::m
ove(data))); | |
930 } else { | |
931 return SkColorSpace_Base::NewRGB(gammaNamed, mat); | |
932 } | |
933 } | |
934 | |
935 // Recognize color profile specified by A2B0 tag. | |
936 const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0); | |
937 if (a2b0) { | |
938 sk_sp<SkColorLookUpTable> colorLUT = sk_make_sp<SkColorLookUpTab
le>(); | |
939 SkGammaCurve curves[3]; | |
940 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); | |
941 if (!load_a2b0(colorLUT.get(), curves, &toXYZ, a2b0->addr((const
uint8_t*) base), | |
942 a2b0->fLength)) { | |
943 return_null("Failed to parse A2B0 tag"); | |
944 } | |
945 | |
946 GammaNamed gammaNamed = SkGammas::Named(curves); | |
947 colorLUT = colorLUT->fTable ? colorLUT : nullptr; | |
948 if (colorLUT || kNonStandard_GammaNamed == gammaNamed) { | |
949 sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curv
es[0]), | |
950 std::move(curv
es[1]), | |
951 std::move(curv
es[2])); | |
952 | |
953 return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(c
olorLUT), | |
954 std::move(g
ammas), toXYZ, | |
955 std::move(d
ata))); | |
956 } else { | |
957 return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ); | |
958 } | |
959 } | |
960 } | |
961 default: | |
962 break; | |
963 } | |
964 | |
965 return_null("ICC profile contains unsupported colorspace"); | |
966 } | |
967 | |
968 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | |
969 | |
970 // We will write a profile with the minimum nine required tags. | |
971 static constexpr uint32_t kICCNumEntries = 9; | |
972 | |
973 static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c'); | |
974 static constexpr uint32_t kTAG_desc_Bytes = 12; | |
975 static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + kICCNumEntries*kIC
CTagTableEntrySize; | |
976 | |
977 static constexpr uint32_t kTAG_XYZ_Bytes = 20; | |
978 static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes; | |
979 static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes; | |
980 static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes; | |
981 | |
982 static constexpr uint32_t kTAG_TRC_Bytes = 14; | |
983 static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes; | |
984 static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset + SkAlign4(kTAG_TR
C_Bytes); | |
985 static constexpr uint32_t kTAG_bTRC_Offset = kTAG_gTRC_Offset + SkAlign4(kTAG_TR
C_Bytes); | |
986 | |
987 static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't'); | |
988 static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + SkAlign4(kTAG_TR
C_Bytes); | |
989 | |
990 static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't'); | |
991 static constexpr uint32_t kTAG_cprt_Bytes = 12; | |
992 static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes; | |
993 | |
994 static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes; | |
995 | |
996 static constexpr uint32_t gICCHeader[kICCHeaderSize / 4] { | |
997 SkEndian_SwapBE32(kICCProfileSize), // Size of the profile | |
998 0, // Preferred CMM type (ignored) | |
999 SkEndian_SwapBE32(0x02100000), // Version 2.1 | |
1000 SkEndian_SwapBE32(kDisplay_Profile), // Display device profile | |
1001 SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space | |
1002 SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space | |
1003 0, 0, 0, // Date and time (ignored) | |
1004 SkEndian_SwapBE32(kACSP_Signature), // Profile signature | |
1005 0, // Platform target (ignored) | |
1006 0x00000000, // Flags: not embedded, can be used ind
ependently | |
1007 0, // Device manufacturer (ignored) | |
1008 0, // Device model (ignored) | |
1009 0, 0, // Device attributes (ignored) | |
1010 SkEndian_SwapBE32(1), // Relative colorimetric rendering inte
nt | |
1011 SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X) | |
1012 SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y) | |
1013 SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z) | |
1014 0, // Profile creator (ignored) | |
1015 0, 0, 0, 0, // Profile id checksum (ignored) | |
1016 0, 0, 0, 0, 0, 0, 0, // Reserved (ignored) | |
1017 SkEndian_SwapBE32(kICCNumEntries), // Number of tags | |
1018 }; | |
1019 | |
1020 static constexpr uint32_t gICCTagTable[3 * kICCNumEntries] { | |
1021 // Profile description | |
1022 SkEndian_SwapBE32(kTAG_desc), | |
1023 SkEndian_SwapBE32(kTAG_desc_Offset), | |
1024 SkEndian_SwapBE32(kTAG_desc_Bytes), | |
1025 | |
1026 // rXYZ | |
1027 SkEndian_SwapBE32(kTAG_rXYZ), | |
1028 SkEndian_SwapBE32(kTAG_rXYZ_Offset), | |
1029 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
1030 | |
1031 // gXYZ | |
1032 SkEndian_SwapBE32(kTAG_gXYZ), | |
1033 SkEndian_SwapBE32(kTAG_gXYZ_Offset), | |
1034 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
1035 | |
1036 // bXYZ | |
1037 SkEndian_SwapBE32(kTAG_bXYZ), | |
1038 SkEndian_SwapBE32(kTAG_bXYZ_Offset), | |
1039 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
1040 | |
1041 // rTRC | |
1042 SkEndian_SwapBE32(kTAG_rTRC), | |
1043 SkEndian_SwapBE32(kTAG_rTRC_Offset), | |
1044 SkEndian_SwapBE32(kTAG_TRC_Bytes), | |
1045 | |
1046 // gTRC | |
1047 SkEndian_SwapBE32(kTAG_gTRC), | |
1048 SkEndian_SwapBE32(kTAG_gTRC_Offset), | |
1049 SkEndian_SwapBE32(kTAG_TRC_Bytes), | |
1050 | |
1051 // bTRC | |
1052 SkEndian_SwapBE32(kTAG_bTRC), | |
1053 SkEndian_SwapBE32(kTAG_bTRC_Offset), | |
1054 SkEndian_SwapBE32(kTAG_TRC_Bytes), | |
1055 | |
1056 // White point | |
1057 SkEndian_SwapBE32(kTAG_wtpt), | |
1058 SkEndian_SwapBE32(kTAG_wtpt_Offset), | |
1059 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
1060 | |
1061 // Copyright | |
1062 SkEndian_SwapBE32(kTAG_cprt), | |
1063 SkEndian_SwapBE32(kTAG_cprt_Offset), | |
1064 SkEndian_SwapBE32(kTAG_cprt_Bytes), | |
1065 }; | |
1066 | |
1067 static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c'); | |
1068 static constexpr uint32_t gEmptyTextTag[3] { | |
1069 SkEndian_SwapBE32(kTAG_TextType), // Type signature | |
1070 0, // Reserved | |
1071 0, // Zero records | |
1072 }; | |
1073 | |
1074 static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int row) { | |
1075 ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); | |
1076 ptr[1] = 0; | |
1077 ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 0))); | |
1078 ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 1))); | |
1079 ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 2))); | |
1080 } | |
1081 | |
1082 static void write_trc_tag(uint32_t* ptr, float value) { | |
1083 ptr[0] = SkEndian_SwapBE32(kTAG_CurveType); | |
1084 ptr[1] = 0; | |
1085 | |
1086 // Gamma will be specified with a single value. | |
1087 ptr[2] = SkEndian_SwapBE32(1); | |
1088 | |
1089 // Convert gamma to 16-bit fixed point. | |
1090 uint16_t* ptr16 = (uint16_t*) (ptr + 3); | |
1091 ptr16[0] = SkEndian_SwapBE16((uint16_t) (value * 256.0f)); | |
1092 | |
1093 // Pad tag with zero. | |
1094 ptr16[1] = 0; | |
1095 } | |
1096 | |
1097 static float get_gamma_value(const SkGammaCurve* curve) { | |
1098 switch (curve->fNamed) { | |
1099 case SkColorSpace::kSRGB_GammaNamed: | |
1100 // FIXME (msarett): | |
1101 // kSRGB cannot be represented by a value. Here we fall through to
2.2f, | |
1102 // which is a close guess. To be more accurate, we need to represen
t sRGB | |
1103 // gamma with a parametric curve. | |
1104 case SkColorSpace::k2Dot2Curve_GammaNamed: | |
1105 return 2.2f; | |
1106 case SkColorSpace::kLinear_GammaNamed: | |
1107 return 1.0f; | |
1108 default: | |
1109 SkASSERT(curve->isValue()); | |
1110 return curve->fValue; | |
1111 } | |
1112 } | |
1113 | |
1114 sk_sp<SkData> SkColorSpace_Base::writeToICC() const { | |
1115 // Return if this object was created from a profile, or if we have already s
erialized | |
1116 // the profile. | |
1117 if (fProfileData) { | |
1118 return fProfileData; | |
1119 } | |
1120 | |
1121 // The client may create an SkColorSpace using an SkMatrix44, but currently
we only | |
1122 // support writing profiles with 3x3 matrices. | |
1123 // TODO (msarett): Fix this! | |
1124 if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) || | |
1125 0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) || | |
1126 0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3)) | |
1127 { | |
1128 return nullptr; | |
1129 } | |
1130 | |
1131 SkAutoMalloc profile(kICCProfileSize); | |
1132 uint8_t* ptr = (uint8_t*) profile.get(); | |
1133 | |
1134 // Write profile header | |
1135 memcpy(ptr, gICCHeader, sizeof(gICCHeader)); | |
1136 ptr += sizeof(gICCHeader); | |
1137 | |
1138 // Write tag table | |
1139 memcpy(ptr, gICCTagTable, sizeof(gICCTagTable)); | |
1140 ptr += sizeof(gICCTagTable); | |
1141 | |
1142 // Write profile description tag | |
1143 memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag)); | |
1144 ptr += sizeof(gEmptyTextTag); | |
1145 | |
1146 // Write XYZ tags | |
1147 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0); | |
1148 ptr += kTAG_XYZ_Bytes; | |
1149 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1); | |
1150 ptr += kTAG_XYZ_Bytes; | |
1151 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2); | |
1152 ptr += kTAG_XYZ_Bytes; | |
1153 | |
1154 // Write TRC tags | |
1155 GammaNamed gammaNamed = this->gammaNamed(); | |
1156 if (kNonStandard_GammaNamed == gammaNamed) { | |
1157 write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->f
Red)); | |
1158 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1159 write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->f
Green)); | |
1160 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1161 write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->f
Blue)); | |
1162 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1163 } else { | |
1164 switch (gammaNamed) { | |
1165 case SkColorSpace::kSRGB_GammaNamed: | |
1166 // FIXME (msarett): | |
1167 // kSRGB cannot be represented by a value. Here we fall through
to 2.2f, | |
1168 // which is a close guess. To be more accurate, we need to repr
esent sRGB | |
1169 // gamma with a parametric curve. | |
1170 case SkColorSpace::k2Dot2Curve_GammaNamed: | |
1171 write_trc_tag((uint32_t*) ptr, 2.2f); | |
1172 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1173 write_trc_tag((uint32_t*) ptr, 2.2f); | |
1174 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1175 write_trc_tag((uint32_t*) ptr, 2.2f); | |
1176 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1177 break; | |
1178 case SkColorSpace::kLinear_GammaNamed: | |
1179 write_trc_tag((uint32_t*) ptr, 1.0f); | |
1180 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1181 write_trc_tag((uint32_t*) ptr, 1.0f); | |
1182 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1183 write_trc_tag((uint32_t*) ptr, 1.0f); | |
1184 ptr += SkAlign4(kTAG_TRC_Bytes); | |
1185 break; | |
1186 default: | |
1187 SkASSERT(false); | |
1188 break; | |
1189 } | |
1190 } | |
1191 | |
1192 // Write white point tag | |
1193 uint32_t* ptr32 = (uint32_t*) ptr; | |
1194 ptr32[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); | |
1195 ptr32[1] = 0; | |
1196 // TODO (msarett): These values correspond to the D65 white point. This may
not always be | |
1197 // correct. | |
1198 ptr32[2] = SkEndian_SwapBE32(0x0000f351); | |
1199 ptr32[3] = SkEndian_SwapBE32(0x00010000); | |
1200 ptr32[4] = SkEndian_SwapBE32(0x000116cc); | |
1201 ptr += kTAG_XYZ_Bytes; | |
1202 | |
1203 // Write copyright tag | |
1204 memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag)); | |
1205 | |
1206 // TODO (msarett): Should we try to hold onto the data so we can return imme
diately if | |
1207 // the client calls again? | |
1208 return SkData::MakeFromMalloc(profile.release(), kICCProfileSize); | |
1209 } | |
1210 | |
1211 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | |
1212 | |
1213 enum Version { | 144 enum Version { |
1214 k0_Version, // Initial version, header + flags for matrix and profile | 145 k0_Version, // Initial version, header + flags for matrix and profile |
1215 }; | 146 }; |
1216 | 147 |
1217 struct ColorSpaceHeader { | 148 struct ColorSpaceHeader { |
1218 /** | 149 /** |
1219 * If kMatrix_Flag is set, we will write 12 floats after the header. | 150 * If kMatrix_Flag is set, we will write 12 floats after the header. |
1220 * Should not be set at the same time as the kICC_Flag. | 151 * Should not be set at the same time as the kICC_Flag. |
1221 */ | 152 */ |
1222 static constexpr uint8_t kMatrix_Flag = 1 << 0; | 153 static constexpr uint8_t kMatrix_Flag = 1 << 0; |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1350 | 281 |
1351 uint32_t profileSize = *((uint32_t*) data); | 282 uint32_t profileSize = *((uint32_t*) data); |
1352 data = SkTAddOffset<const void>(data, sizeof(uint32_t)); | 283 data = SkTAddOffset<const void>(data, sizeof(uint32_t)); |
1353 length -= sizeof(uint32_t); | 284 length -= sizeof(uint32_t); |
1354 if (length < profileSize) { | 285 if (length < profileSize) { |
1355 return nullptr; | 286 return nullptr; |
1356 } | 287 } |
1357 | 288 |
1358 return NewICC(data, profileSize); | 289 return NewICC(data, profileSize); |
1359 } | 290 } |
OLD | NEW |