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 "SkAtomics.h" | 8 #include "SkAtomics.h" |
9 #include "SkColorSpace.h" | 9 #include "SkColorSpace.h" |
10 | 10 |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
151 case kSRGB_Named: | 151 case kSRGB_Named: |
152 return new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named); | 152 return new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named); |
153 default: | 153 default: |
154 break; | 154 break; |
155 } | 155 } |
156 return nullptr; | 156 return nullptr; |
157 } | 157 } |
158 | 158 |
159 //////////////////////////////////////////////////////////////////////////////// /////////////////// | 159 //////////////////////////////////////////////////////////////////////////////// /////////////////// |
160 | 160 |
161 #include "SkFixed.h" | |
162 #include "SkTemplates.h" | |
163 | |
164 #define SkColorSpacePrintf(...) | |
165 | |
166 #define return_if_false(pred, msg) \ | |
167 do { \ | |
168 if (!(pred)) { \ | |
169 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", msg); \ | |
170 return false; \ | |
171 } \ | |
172 } while (0) | |
173 | |
174 #define return_null(msg) \ | |
175 do { \ | |
176 SkDebugf("ICC Profile: %s\n", (msg)); \ | |
177 return nullptr; \ | |
178 } while (0) | |
179 | |
180 static uint16_t read_big_endian_short(const uint8_t* ptr) { | |
181 return ptr[0] << 8 | ptr[1]; | |
182 } | |
183 | |
184 static uint32_t read_big_endian_int(const uint8_t* ptr) { | |
185 return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; | |
186 } | |
187 | |
188 // This is equal to the header size according to the ICC specification (128) | |
189 // plus the size of the tag count (4). We include the tag count since we | |
190 // always require it to be present anyway. | |
191 static const size_t kICCHeaderSize = 132; | |
192 | |
193 // Contains a signature (4), offset (4), and size (4). | |
194 static const size_t kICCTagTableEntrySize = 12; | |
195 | |
196 static const uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '); | |
197 static const uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y'); | |
198 | |
199 struct ICCProfileHeader { | |
200 // TODO (msarett): | |
201 // Can we ignore less of these fields? | |
202 uint32_t fSize; | |
203 uint32_t fCMMType_ignored; | |
204 uint32_t fVersion; | |
205 uint32_t fClassProfile; | |
206 uint32_t fColorSpace; | |
207 uint32_t fPCS; | |
208 uint32_t fDateTime_ignored[3]; | |
209 uint32_t fSignature; | |
210 uint32_t fPlatformTarget_ignored; | |
211 uint32_t fFlags_ignored; | |
212 uint32_t fManufacturer_ignored; | |
213 uint32_t fDeviceModel_ignored; | |
214 uint32_t fDeviceAttributes_ignored[2]; | |
215 uint32_t fRenderingIntent; | |
216 uint32_t fIlluminantXYZ_ignored[3]; | |
217 uint32_t fCreator_ignored; | |
218 uint32_t fProfileId_ignored[4]; | |
219 uint32_t fReserved_ignored[7]; | |
220 uint32_t fTagCount; | |
221 | |
222 void init(const uint8_t* src, size_t len) { | |
223 SkASSERT(kICCHeaderSize == sizeof(*this)); | |
224 | |
225 uint32_t* dst = (uint32_t*) this; | |
226 for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { | |
227 dst[i] = read_big_endian_int(src); | |
228 } | |
229 } | |
230 | |
231 bool valid() const { | |
232 // TODO (msarett): | |
233 // For now it's nice to fail loudly on invalid inputs. But, can we | |
234 // recover from some of these errors? | |
235 | |
236 return_if_false(fSize >= kICCHeaderSize, "Size is too small"); | |
237 | |
238 uint8_t majorVersion = fVersion >> 24; | |
239 return_if_false(majorVersion <= 4, "Unsupported version"); | |
240 | |
241 const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'); | |
242 const uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'); | |
243 const uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'); | |
244 // TODO (msarett): | |
245 // Should we also support DeviceLink, ColorSpace, Abstract, or NamedColo r? | |
246 return_if_false(fClassProfile == kDisplay_Profile || | |
247 fClassProfile == kInput_Profile || | |
248 fClassProfile == kOutput_Profile, | |
249 "Unsupported class profile"); | |
250 | |
251 // TODO (msarett): | |
252 // There are many more color spaces that we could try to support. | |
253 return_if_false(fColorSpace == kRGB_ColorSpace || fColorSpace == kGray_C olorSpace, | |
254 "Unsupported color space"); | |
255 | |
256 const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '); | |
257 // TODO (msarett): | |
258 // Can we support PCS LAB as well? | |
259 return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space"); | |
260 | |
261 return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad signature"); | |
262 | |
263 // TODO (msarett): | |
264 // Share this with sRGB? | |
265 enum ICCIntents { | |
scroggo
2016/03/01 21:29:57
Looks like this is never used?
msarett
2016/03/02 00:22:54
Yes we don't use this...
I'll delete it and add a
| |
266 kPerceptual = 0, | |
267 kRelativeColorimetric = 1, | |
268 kSaturation = 2, | |
269 kAbsoluteColorimetric = 3, | |
270 }; | |
271 return_if_false(fRenderingIntent <= 3, "Bad rendering intent"); | |
272 | |
273 return_if_false(fTagCount <= 100, "Too many tags"); | |
274 | |
275 return true; | |
276 } | |
277 }; | |
278 | |
279 struct ICCTag { | |
280 uint32_t fSignature; | |
281 uint32_t fOffset; | |
282 uint32_t fLength; | |
283 | |
284 const uint8_t* init(const uint8_t* src) { | |
285 fSignature = read_big_endian_int(src); | |
286 fOffset = read_big_endian_int(src + 4); | |
287 fLength = read_big_endian_int(src + 8); | |
288 return src + 12; | |
289 } | |
290 | |
291 bool valid(size_t len) { | |
292 return_if_false(fOffset + fLength <= len, "Tag too large for ICC profile .\n"); | |
293 return true; | |
294 } | |
295 | |
296 const uint8_t* addr(const uint8_t* src) const { | |
297 return src + fOffset; | |
298 } | |
299 | |
300 static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature ) { | |
301 for (int i = 0; i < count; ++i) { | |
302 if (tags[i].fSignature == signature) { | |
303 return &tags[i]; | |
304 } | |
305 } | |
306 return nullptr; | |
307 } | |
308 }; | |
309 | |
310 // TODO (msarett): | |
311 // Should we recognize more tags? | |
312 static const uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z'); | |
313 static const uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z'); | |
314 static const uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z'); | |
315 static const uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C'); | |
316 static const uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C'); | |
317 static const uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C'); | |
318 | |
319 bool load_xyz(float dst[3], const uint8_t* src, size_t len) { | |
320 if (len < 20) { | |
321 SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); | |
322 return false; | |
323 } | |
324 | |
325 dst[0] = SkFixedToFloat(read_big_endian_int(src + 8)); | |
326 dst[1] = SkFixedToFloat(read_big_endian_int(src + 12)); | |
327 dst[2] = SkFixedToFloat(read_big_endian_int(src + 16)); | |
328 SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); | |
329 return true; | |
330 } | |
331 | |
332 static const uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v'); | |
333 static const uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a'); | |
334 | |
335 static bool load_gamma(float* gamma, const uint8_t* src, size_t len) { | |
scroggo
2016/03/01 21:29:56
This returns a bool, but the result is never used.
msarett
2016/03/02 00:22:54
I think you could argue this should be void, since
| |
336 if (len < 14) { | |
337 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); | |
338 return false; | |
339 } | |
340 | |
341 uint32_t type = read_big_endian_int(src); | |
342 switch (type) { | |
343 case kTAG_CurveType: { | |
344 uint32_t count = read_big_endian_int(src + 8); | |
345 if (0 == count) { | |
346 return false; | |
347 } | |
348 | |
349 const uint16_t* table = (const uint16_t*) (src + 12); | |
350 if (1 == count) { | |
351 // Table entry is the exponent (bias 256). | |
352 uint16_t value = read_big_endian_short((const uint8_t*) table); | |
353 *gamma = value / 256.0f; | |
354 SkColorSpacePrintf("gamma %d %g\n", value, *gamma); | |
355 return true; | |
356 } | |
357 | |
358 // Check length again if we have a table. | |
359 if (len < 12 + 2 * count) { | |
360 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); | |
361 return false; | |
362 } | |
363 | |
364 // Print the interpolation table. For now, we ignore this and guess 2.2f. | |
365 for (uint32_t i = 0; i < count; i++) { | |
366 SkColorSpacePrintf("curve[%d] %d\n", i, | |
367 read_big_endian_short((const uint8_t*) &table[i])); | |
368 } | |
369 | |
370 *gamma = 2.2f; | |
371 return true; | |
372 } break; | |
373 case kTAG_ParaCurveType: { | |
374 // Guess 2.2f. | |
375 SkColorSpacePrintf("parametric curve\n"); | |
376 *gamma = 2.2f; | |
377 return true; | |
378 } break; | |
379 default: | |
380 SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); | |
381 return false; | |
382 } | |
383 } | |
384 | |
385 SkColorSpace* SkColorSpace::NewICC(const void* base, size_t len) { | |
386 const uint8_t* ptr = (const uint8_t*) base; | |
387 | |
388 if (len < kICCHeaderSize) { | |
389 SkColorSpacePrintf("Data is not large enough to contain an ICC profile.\ n"); | |
390 return nullptr; | |
391 } | |
392 | |
393 // Read the ICC profile header and check to make sure that it is valid. | |
394 ICCProfileHeader header; | |
395 header.init(ptr, len); | |
396 if (!header.valid()) { | |
397 return nullptr; | |
398 } | |
399 | |
400 // Adjust ptr and len before reading the tags. | |
401 if (len < header.fSize) { | |
402 SkColorSpacePrintf("ICC profile might be truncated.\n"); | |
403 } else if (len > header.fSize) { | |
404 SkColorSpacePrintf("Caller provided extra data beyonf the end of the ICC profile.\n"); | |
405 len = header.fSize; | |
406 } | |
407 ptr += kICCHeaderSize; | |
408 len -= kICCHeaderSize; | |
409 | |
410 // Parse tag headers. | |
411 uint32_t tagCount = header.fTagCount; | |
412 SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); | |
413 if (len < kICCTagTableEntrySize * tagCount) { | |
414 SkColorSpacePrintf("Not enough input data to read tag table entries.\n") ; | |
scroggo
2016/03/01 21:29:56
It seems like this should return null? (Otherwise
msarett
2016/03/02 00:22:54
Yes thanks.
| |
415 } | |
416 | |
417 SkAutoTArray<ICCTag> tags(tagCount); | |
418 for (uint32_t i = 0; i < tagCount; i++) { | |
419 ptr = tags[i].init(ptr); | |
420 SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24 ) & 0xFF, | |
421 (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) & 0xFF, | |
422 (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLen gth); | |
423 | |
424 if (!tags[i].valid(kICCHeaderSize + len)) { | |
425 return nullptr; | |
scroggo
2016/03/01 21:29:57
failure message?
msarett
2016/03/02 00:22:54
Done.
| |
426 } | |
427 } | |
428 | |
429 // Load our XYZ and gamma matrices. | |
430 SkFloat3x3 toXYZ; | |
431 SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }}; | |
432 switch (header.fColorSpace) { | |
433 case kRGB_ColorSpace: { | |
434 const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); | |
435 const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); | |
436 const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); | |
437 if (!r || !g || !b) { | |
438 return_null("Need rgb tags for XYZ space"); | |
439 } | |
440 | |
441 if (!load_xyz(&toXYZ.fMat[0], r->addr((const uint8_t*) base), r->fLe ngth) || | |
442 !load_xyz(&toXYZ.fMat[3], g->addr((const uint8_t*) base), g->fLe ngth) || | |
443 !load_xyz(&toXYZ.fMat[6], b->addr((const uint8_t*) base), b->fLe ngth)) { | |
scroggo
2016/03/01 21:29:57
nit: hard to read this line as is. I find putting
msarett
2016/03/02 00:22:54
Done.
| |
444 return_null("Need valid rgb tags for XYZ space"); | |
445 } | |
446 | |
447 r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); | |
448 g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); | |
449 b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); | |
450 if (r) { | |
451 load_gamma(&gamma.fVec[0], r->addr((const uint8_t*) base), r->fL ength); | |
452 } | |
453 if (g) { | |
454 load_gamma(&gamma.fVec[1], g->addr((const uint8_t*) base), g->fL ength); | |
455 } | |
456 if (b) { | |
457 load_gamma(&gamma.fVec[2], b->addr((const uint8_t*) base), b->fL ength); | |
458 } | |
459 return SkColorSpace::NewRGB(toXYZ, gamma); | |
460 } | |
461 default: | |
462 break; | |
463 } | |
464 | |
465 return_null("ICC profile contains unsupported colorspace.\n"); | |
466 } | |
467 | |
468 //////////////////////////////////////////////////////////////////////////////// /////////////////// | |
469 | |
161 SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColor Space* dst, | 470 SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColor Space* dst, |
162 SkFloat3x3* result) { | 471 SkFloat3x3* result) { |
163 if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst- >named())) { | 472 if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst- >named())) { |
164 if (result) { | 473 if (result) { |
165 *result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }}; | 474 *result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }}; |
166 } | 475 } |
167 return kIdentity_Result; | 476 return kIdentity_Result; |
168 } | 477 } |
169 if (result) { | 478 if (result) { |
170 *result = concat(src->fToXYZD50, invert(dst->fToXYZD50)); | 479 *result = concat(src->fToXYZD50, invert(dst->fToXYZD50)); |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
236 } | 545 } |
237 | 546 |
238 // D65 white point of Rec. 709 [8] are: | 547 // D65 white point of Rec. 709 [8] are: |
239 // | 548 // |
240 // D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890 | 549 // D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890 |
241 // | 550 // |
242 // R G B white | 551 // R G B white |
243 // x 0.640 0.300 0.150 0.3127 | 552 // x 0.640 0.300 0.150 0.3127 |
244 // y 0.330 0.600 0.060 0.3290 | 553 // y 0.330 0.600 0.060 0.3290 |
245 // z 0.030 0.100 0.790 0.3582 | 554 // z 0.030 0.100 0.790 0.3582 |
OLD | NEW |