SkColorSpace.cpp revision 2c3eca38c9f74be9d91f467540feb586a0e941ca
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 "SkAtomics.h"
9#include "SkColorSpace.h"
10#include "SkOnce.h"
11
12static bool color_space_almost_equal(float a, float b) {
13    return SkTAbs(a - b) < 0.01f;
14}
15
16void SkFloat3::dump() const {
17    SkDebugf("[%7.4f %7.4f %7.4f]\n", fVec[0], fVec[1], fVec[2]);
18}
19
20//////////////////////////////////////////////////////////////////////////////////////////////////
21
22static int32_t gUniqueColorSpaceID;
23
24SkColorSpace::SkColorSpace(SkGammas gammas, const SkMatrix44& toXYZD50, Named named)
25    : fGammas(std::move(gammas))
26    , fToXYZD50(toXYZD50)
27    , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID))
28    , fNamed(named)
29{}
30
31SkColorSpace::SkColorSpace(SkColorLookUpTable colorLUT, SkGammas gammas,
32                           const SkMatrix44& toXYZD50)
33    : fColorLUT(std::move(colorLUT))
34    , fGammas(std::move(gammas))
35    , fToXYZD50(toXYZD50)
36    , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID))
37    , fNamed(kUnknown_Named)
38{}
39
40const float gSRGB_toXYZD50[] {
41    0.4358f, 0.2224f, 0.0139f,    // * R
42    0.3853f, 0.7170f, 0.0971f,    // * G
43    0.1430f, 0.0606f, 0.7139f,    // * B
44};
45
46sk_sp<SkColorSpace> SkColorSpace::NewRGB(SkGammas gammas, const SkMatrix44& toXYZD50) {
47    // Check if we really have sRGB
48    if (color_space_almost_equal(2.2f, gammas.fRed.fValue) &&
49        color_space_almost_equal(2.2f, gammas.fGreen.fValue) &&
50        color_space_almost_equal(2.2f, gammas.fBlue.fValue) &&
51        color_space_almost_equal(toXYZD50.getFloat(0, 0), gSRGB_toXYZD50[0]) &&
52        color_space_almost_equal(toXYZD50.getFloat(0, 1), gSRGB_toXYZD50[1]) &&
53        color_space_almost_equal(toXYZD50.getFloat(0, 2), gSRGB_toXYZD50[2]) &&
54        color_space_almost_equal(toXYZD50.getFloat(1, 0), gSRGB_toXYZD50[3]) &&
55        color_space_almost_equal(toXYZD50.getFloat(1, 1), gSRGB_toXYZD50[4]) &&
56        color_space_almost_equal(toXYZD50.getFloat(1, 2), gSRGB_toXYZD50[5]) &&
57        color_space_almost_equal(toXYZD50.getFloat(2, 0), gSRGB_toXYZD50[6]) &&
58        color_space_almost_equal(toXYZD50.getFloat(2, 1), gSRGB_toXYZD50[7]) &&
59        color_space_almost_equal(toXYZD50.getFloat(2, 2), gSRGB_toXYZD50[8]) &&
60        color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) &&
61        color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) &&
62        color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) &&
63        color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) &&
64        color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) &&
65        color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) &&
66        color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f))
67    {
68        return SkColorSpace::NewNamed(kSRGB_Named);
69    }
70
71    return sk_sp<SkColorSpace>(new SkColorSpace(std::move(gammas), toXYZD50, kUnknown_Named));
72}
73
74sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
75    static SkOnce once;
76    static SkColorSpace* sRGB;
77
78    switch (named) {
79        case kSRGB_Named: {
80            once([] {
81                SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
82                srgbToxyzD50.set3x3ColMajorf(gSRGB_toXYZD50);
83                sRGB = new SkColorSpace(SkGammas(2.2f, 2.2f, 2.2f), srgbToxyzD50, kSRGB_Named);
84            });
85            return sk_ref_sp(sRGB);
86        }
87        default:
88            break;
89    }
90    return nullptr;
91}
92
93///////////////////////////////////////////////////////////////////////////////////////////////////
94
95#include "SkFixed.h"
96#include "SkTemplates.h"
97
98#define SkColorSpacePrintf(...)
99
100#define return_if_false(pred, msg)                                   \
101    do {                                                             \
102        if (!(pred)) {                                               \
103            SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \
104            return false;                                            \
105        }                                                            \
106    } while (0)
107
108#define return_null(msg)                                             \
109    do {                                                             \
110        SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg));     \
111        return nullptr;                                              \
112    } while (0)
113
114static uint16_t read_big_endian_short(const uint8_t* ptr) {
115    return ptr[0] << 8 | ptr[1];
116}
117
118static uint32_t read_big_endian_uint(const uint8_t* ptr) {
119    return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
120}
121
122static int32_t read_big_endian_int(const uint8_t* ptr) {
123    return (int32_t) read_big_endian_uint(ptr);
124}
125
126// This is equal to the header size according to the ICC specification (128)
127// plus the size of the tag count (4).  We include the tag count since we
128// always require it to be present anyway.
129static const size_t kICCHeaderSize = 132;
130
131// Contains a signature (4), offset (4), and size (4).
132static const size_t kICCTagTableEntrySize = 12;
133
134static const uint32_t kRGB_ColorSpace  = SkSetFourByteTag('R', 'G', 'B', ' ');
135
136struct ICCProfileHeader {
137    uint32_t fSize;
138
139    // No reason to care about the preferred color management module (ex: Adobe, Apple, etc.).
140    // We're always going to use this one.
141    uint32_t fCMMType_ignored;
142
143    uint32_t fVersion;
144    uint32_t fProfileClass;
145    uint32_t fInputColorSpace;
146    uint32_t fPCS;
147    uint32_t fDateTime_ignored[3];
148    uint32_t fSignature;
149
150    // Indicates the platform that this profile was created for (ex: Apple, Microsoft).  This
151    // doesn't really matter to us.
152    uint32_t fPlatformTarget_ignored;
153
154    // Flags can indicate:
155    // (1) Whether this profile was embedded in a file.  This flag is consistently wrong.
156    //     Ex: The profile came from a file but indicates that it did not.
157    // (2) Whether we are allowed to use the profile independently of the color data.  If set,
158    //     this may allow us to use the embedded profile for testing separate from the original
159    //     image.
160    uint32_t fFlags_ignored;
161
162    // We support many output devices.  It doesn't make sense to think about the attributes of
163    // the device in the context of the image profile.
164    uint32_t fDeviceManufacturer_ignored;
165    uint32_t fDeviceModel_ignored;
166    uint32_t fDeviceAttributes_ignored[2];
167
168    uint32_t fRenderingIntent;
169    int32_t  fIlluminantXYZ[3];
170
171    // We don't care who created the profile.
172    uint32_t fCreator_ignored;
173
174    // This is an MD5 checksum.  Could be useful for checking if profiles are equal.
175    uint32_t fProfileId_ignored[4];
176
177    // Reserved for future use.
178    uint32_t fReserved_ignored[7];
179
180    uint32_t fTagCount;
181
182    void init(const uint8_t* src, size_t len) {
183        SkASSERT(kICCHeaderSize == sizeof(*this));
184
185        uint32_t* dst = (uint32_t*) this;
186        for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) {
187            dst[i] = read_big_endian_uint(src);
188        }
189    }
190
191    bool valid() const {
192        return_if_false(fSize >= kICCHeaderSize, "Size is too small");
193
194        uint8_t majorVersion = fVersion >> 24;
195        return_if_false(majorVersion <= 4, "Unsupported version");
196
197        // These are the three basic classes of profiles that we might expect to see embedded
198        // in images.  Four additional classes exist, but they generally are used as a convenient
199        // way for CMMs to store calculated transforms.
200        const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
201        const uint32_t kInput_Profile   = SkSetFourByteTag('s', 'c', 'n', 'r');
202        const uint32_t kOutput_Profile  = SkSetFourByteTag('p', 'r', 't', 'r');
203        return_if_false(fProfileClass == kDisplay_Profile ||
204                        fProfileClass == kInput_Profile ||
205                        fProfileClass == kOutput_Profile,
206                        "Unsupported profile");
207
208        // TODO (msarett):
209        // All the profiles we've tested so far use RGB as the input color space.
210        return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color space");
211
212        // TODO (msarett):
213        // All the profiles we've tested so far use XYZ as the profile connection space.
214        const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
215        return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
216
217        return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad signature");
218
219        // TODO (msarett):
220        // Should we treat different rendering intents differently?
221        // Valid rendering intents include kPerceptual (0), kRelative (1),
222        // kSaturation (2), and kAbsolute (3).
223        return_if_false(fRenderingIntent <= 3, "Bad rendering intent");
224
225        return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), 0.96420f) &&
226                        color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), 1.00000f) &&
227                        color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), 0.82491f),
228                        "Illuminant must be D50");
229
230        return_if_false(fTagCount <= 100, "Too many tags");
231
232        return true;
233    }
234};
235
236struct ICCTag {
237    uint32_t fSignature;
238    uint32_t fOffset;
239    uint32_t fLength;
240
241    const uint8_t* init(const uint8_t* src) {
242        fSignature = read_big_endian_uint(src);
243        fOffset = read_big_endian_uint(src + 4);
244        fLength = read_big_endian_uint(src + 8);
245        return src + 12;
246    }
247
248    bool valid(size_t len) {
249        return_if_false(fOffset + fLength <= len, "Tag too large for ICC profile");
250        return true;
251    }
252
253    const uint8_t* addr(const uint8_t* src) const {
254        return src + fOffset;
255    }
256
257    static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) {
258        for (int i = 0; i < count; ++i) {
259            if (tags[i].fSignature == signature) {
260                return &tags[i];
261            }
262        }
263        return nullptr;
264    }
265};
266
267static const uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
268static const uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
269static const uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
270static const uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
271static const uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
272static const uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
273static const uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0');
274
275bool load_xyz(float dst[3], const uint8_t* src, size_t len) {
276    if (len < 20) {
277        SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len);
278        return false;
279    }
280
281    dst[0] = SkFixedToFloat(read_big_endian_int(src + 8));
282    dst[1] = SkFixedToFloat(read_big_endian_int(src + 12));
283    dst[2] = SkFixedToFloat(read_big_endian_int(src + 16));
284    SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]);
285    return true;
286}
287
288static const uint32_t kTAG_CurveType     = SkSetFourByteTag('c', 'u', 'r', 'v');
289static const uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
290
291bool SkColorSpace::LoadGammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t* src,
292                              size_t len) {
293    for (uint32_t i = 0; i < numGammas; i++) {
294        if (len < 12) {
295            // FIXME (msarett):
296            // We could potentially return false here after correctly parsing *some* of the
297            // gammas correctly.  Should we somehow try to indicate a partial success?
298            SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
299            return false;
300        }
301
302        // We need to count the number of bytes in the tag, so we are able to move to the
303        // next tag on the next loop iteration.
304        size_t tagBytes;
305
306        uint32_t type = read_big_endian_uint(src);
307        switch (type) {
308            case kTAG_CurveType: {
309                uint32_t count = read_big_endian_uint(src + 8);
310                tagBytes = 12 + count * 2;
311                if (0 == count) {
312                    // Some tags require a gamma curve, but the author doesn't actually want
313                    // to transform the data.  In this case, it is common to see a curve with
314                    // a count of 0.
315                    gammas[i].fValue = 1.0f;
316                    break;
317                } else if (len < 12 + 2 * count) {
318                    SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
319                    return false;
320                }
321
322                const uint16_t* table = (const uint16_t*) (src + 12);
323                if (1 == count) {
324                    // The table entry is the gamma (with a bias of 256).
325                    uint16_t value = read_big_endian_short((const uint8_t*) table);
326                    gammas[i].fValue = value / 256.0f;
327                    SkColorSpacePrintf("gamma %d %g\n", value, gammas[i].fValue);
328                    break;
329                }
330
331                // Check for frequently occurring curves and use a fast approximation.
332                // We do this by sampling a few values and see if they match our expectation.
333                // A more robust solution would be to compare each value in this curve against
334                // a 2.2f curve see if we remain below an error threshold.  At this time,
335                // we haven't seen any images in the wild that make this kind of
336                // calculation necessary.  We encounter identical gamma curves over and
337                // over again, but relatively few variations.
338                if (1024 == count) {
339                    if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
340                            3341 == read_big_endian_short((const uint8_t*) &table[256]) &&
341                            14057 == read_big_endian_short((const uint8_t*) &table[512]) &&
342                            34318 == read_big_endian_short((const uint8_t*) &table[768]) &&
343                            65535 == read_big_endian_short((const uint8_t*) &table[1023])) {
344                        gammas[i].fValue = 2.2f;
345                        break;
346                    }
347                } else if (26 == count) {
348                    if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
349                            3062 == read_big_endian_short((const uint8_t*) &table[6]) &&
350                            12824 == read_big_endian_short((const uint8_t*) &table[12]) &&
351                            31237 == read_big_endian_short((const uint8_t*) &table[18]) &&
352                            65535 == read_big_endian_short((const uint8_t*) &table[25])) {
353                        gammas[i].fValue = 2.2f;
354                        break;
355                    }
356                }
357
358                // Otherwise, fill in the interpolation table.
359                gammas[i].fTableSize = count;
360                gammas[i].fTable = std::unique_ptr<float[]>(new float[count]);
361                for (uint32_t j = 0; j < count; j++) {
362                    gammas[i].fTable[j] =
363                            (read_big_endian_short((const uint8_t*) &table[j])) / 65535.0f;
364                }
365                break;
366            }
367            case kTAG_ParaCurveType:
368                // Guess 2.2f.
369                // FIXME (msarett): Handle parametric curves.
370                SkColorSpacePrintf("parametric curve\n");
371                gammas[i].fValue = 2.2f;
372
373                // Determine the size of the parametric curve tag.
374                switch(read_big_endian_short(src + 8)) {
375                    case 0:
376                        tagBytes = 12 + 4;
377                        break;
378                    case 1:
379                        tagBytes = 12 + 12;
380                        break;
381                    case 2:
382                        tagBytes = 12 + 16;
383                        break;
384                    case 3:
385                        tagBytes = 12 + 20;
386                        break;
387                    case 4:
388                        tagBytes = 12 + 28;
389                        break;
390                    default:
391                        SkColorSpacePrintf("Invalid parametric curve type\n");
392                        return false;
393                }
394                break;
395            default:
396                SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
397                return false;
398        }
399
400        // Adjust src and len if there is another gamma curve to load.
401        if (i != numGammas - 1) {
402            // Each curve is padded to 4-byte alignment.
403            tagBytes = SkAlign4(tagBytes);
404            if (len < tagBytes) {
405                return false;
406            }
407
408            src += tagBytes;
409            len -= tagBytes;
410        }
411    }
412
413    return true;
414}
415
416static const uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' ');
417
418bool SkColorSpace::LoadColorLUT(SkColorLookUpTable* colorLUT, uint32_t inputChannels,
419                                uint32_t outputChannels, const uint8_t* src, size_t len) {
420    if (len < 20) {
421        SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
422        return false;
423    }
424
425    SkASSERT(inputChannels <= SkColorLookUpTable::kMaxChannels && 3 == outputChannels);
426    colorLUT->fInputChannels = inputChannels;
427    colorLUT->fOutputChannels = outputChannels;
428    uint32_t numEntries = 1;
429    for (uint32_t i = 0; i < inputChannels; i++) {
430        colorLUT->fGridPoints[i] = src[i];
431        numEntries *= src[i];
432    }
433    numEntries *= outputChannels;
434
435    // Space is provided for a maximum of the 16 input channels.  Now we determine the precision
436    // of the table values.
437    uint8_t precision = src[16];
438    switch (precision) {
439        case 1: // 8-bit data
440        case 2: // 16-bit data
441            break;
442        default:
443            SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit.\n", len);
444            return false;
445    }
446
447    if (len < 20 + numEntries * precision) {
448        SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
449        return false;
450    }
451
452    // Movable struct colorLUT has ownership of fTable.
453    colorLUT->fTable = std::unique_ptr<float[]>(new float[numEntries]);
454    const uint8_t* ptr = src + 20;
455    for (uint32_t i = 0; i < numEntries; i++, ptr += precision) {
456        if (1 == precision) {
457            colorLUT->fTable[i] = ((float) ptr[i]) / 255.0f;
458        } else {
459            colorLUT->fTable[i] = ((float) read_big_endian_short(ptr)) / 65535.0f;
460        }
461    }
462
463    return true;
464}
465
466bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
467    if (len < 48) {
468        SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len);
469        return false;
470    }
471
472    float array[16];
473    array[ 0] = SkFixedToFloat(read_big_endian_int(src));
474    array[ 1] = SkFixedToFloat(read_big_endian_int(src + 4));
475    array[ 2] = SkFixedToFloat(read_big_endian_int(src + 8));
476    array[ 3] = 0;
477    array[ 4] = SkFixedToFloat(read_big_endian_int(src + 12));
478    array[ 5] = SkFixedToFloat(read_big_endian_int(src + 16));
479    array[ 6] = SkFixedToFloat(read_big_endian_int(src + 20));
480    array[ 7] = 0;
481    array[ 8] = SkFixedToFloat(read_big_endian_int(src + 24));
482    array[ 9] = SkFixedToFloat(read_big_endian_int(src + 28));
483    array[10] = SkFixedToFloat(read_big_endian_int(src + 32));
484    array[11] = 0;
485    array[12] = SkFixedToFloat(read_big_endian_int(src + 36));  // translate R
486    array[13] = SkFixedToFloat(read_big_endian_int(src + 40));  // translate G
487    array[14] = SkFixedToFloat(read_big_endian_int(src + 44));
488    array[15] = 1;
489    toXYZ->setColMajorf(array);
490    return true;
491}
492
493bool SkColorSpace::LoadA2B0(SkColorLookUpTable* colorLUT, SkGammas* gammas, SkMatrix44* toXYZ,
494                            const uint8_t* src, size_t len) {
495    if (len < 32) {
496        SkColorSpacePrintf("A to B tag is too small (%d bytes).", len);
497        return false;
498    }
499
500    uint32_t type = read_big_endian_uint(src);
501    if (kTAG_AtoBType != type) {
502        // FIXME (msarett): Need to support lut8Type and lut16Type.
503        SkColorSpacePrintf("Unsupported A to B tag type.\n");
504        return false;
505    }
506
507    // Read the number of channels.  The four bytes that we skipped are reserved and
508    // must be zero.
509    uint8_t inputChannels = src[8];
510    uint8_t outputChannels = src[9];
511    if (0 == inputChannels || inputChannels > SkColorLookUpTable::kMaxChannels ||
512            3 != outputChannels) {
513        // The color LUT assumes that there are at most 16 input channels.  For RGB
514        // profiles, output channels should be 3.
515        SkColorSpacePrintf("Too many input or output channels in A to B tag.\n");
516        return false;
517    }
518
519    // Read the offsets of each element in the A to B tag.  With the exception of A curves and
520    // B curves (which we do not yet support), we will handle these elements in the order in
521    // which they should be applied (rather than the order in which they occur in the tag).
522    // If the offset is non-zero it indicates that the element is present.
523    uint32_t offsetToACurves = read_big_endian_int(src + 28);
524    uint32_t offsetToBCurves = read_big_endian_int(src + 12);
525    if ((0 != offsetToACurves) || (0 != offsetToBCurves)) {
526        // FIXME (msarett): Handle A and B curves.
527        // Note that the A curve is technically required in order to have a color LUT.
528        // However, all the A curves I have seen so far have are just placeholders that
529        // don't actually transform the data.
530        SkColorSpacePrintf("Ignoring A and/or B curve.  Output may be wrong.\n");
531    }
532
533    uint32_t offsetToColorLUT = read_big_endian_int(src + 24);
534    if (0 != offsetToColorLUT && offsetToColorLUT < len) {
535        if (!SkColorSpace::LoadColorLUT(colorLUT, inputChannels, outputChannels,
536                                        src + offsetToColorLUT, len - offsetToColorLUT)) {
537            SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n");
538        }
539    }
540
541    uint32_t offsetToMCurves = read_big_endian_int(src + 20);
542    if (0 != offsetToMCurves && offsetToMCurves < len) {
543        if (!SkColorSpace::LoadGammas(&gammas->fRed, outputChannels, src + offsetToMCurves,
544                                      len - offsetToMCurves)) {
545            SkColorSpacePrintf("Failed to read M curves from A to B tag.\n");
546        }
547    }
548
549    uint32_t offsetToMatrix = read_big_endian_int(src + 16);
550    if (0 != offsetToMatrix && offsetToMatrix < len) {
551        if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) {
552            SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
553        }
554    }
555
556    return true;
557}
558
559sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
560    const uint8_t* ptr = (const uint8_t*) base;
561
562    if (len < kICCHeaderSize) {
563        return_null("Data is not large enough to contain an ICC profile");
564    }
565
566    // Read the ICC profile header and check to make sure that it is valid.
567    ICCProfileHeader header;
568    header.init(ptr, len);
569    if (!header.valid()) {
570        return nullptr;
571    }
572
573    // Adjust ptr and len before reading the tags.
574    if (len < header.fSize) {
575        SkColorSpacePrintf("ICC profile might be truncated.\n");
576    } else if (len > header.fSize) {
577        SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n");
578        len = header.fSize;
579    }
580    ptr += kICCHeaderSize;
581    len -= kICCHeaderSize;
582
583    // Parse tag headers.
584    uint32_t tagCount = header.fTagCount;
585    SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount);
586    if (len < kICCTagTableEntrySize * tagCount) {
587        return_null("Not enough input data to read tag table entries");
588    }
589
590    SkAutoTArray<ICCTag> tags(tagCount);
591    for (uint32_t i = 0; i < tagCount; i++) {
592        ptr = tags[i].init(ptr);
593        SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF,
594                (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >>  8) & 0xFF,
595                (tags[i].fSignature >>  0) & 0xFF, tags[i].fOffset, tags[i].fLength);
596
597        if (!tags[i].valid(kICCHeaderSize + len)) {
598            return_null("Tag is too large to fit in ICC profile");
599        }
600    }
601
602    switch (header.fInputColorSpace) {
603        case kRGB_ColorSpace: {
604            // Recognize the rXYZ, gXYZ, and bXYZ tags.
605            const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ);
606            const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
607            const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
608            if (r && g && b) {
609                float toXYZ[9];
610                if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLength) ||
611                    !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLength) ||
612                    !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLength))
613                {
614                    return_null("Need valid rgb tags for XYZ space");
615                }
616
617                // It is not uncommon to see missing or empty gamma tags.  This indicates
618                // that we should use unit gamma.
619                SkGammas gammas;
620                r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
621                g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
622                b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
623                if (!r || !SkColorSpace::LoadGammas(&gammas.fRed, 1,
624                                                    r->addr((const uint8_t*) base), r->fLength)) {
625                    SkColorSpacePrintf("Failed to read R gamma tag.\n");
626                }
627                if (!g || !SkColorSpace::LoadGammas(&gammas.fGreen, 1,
628                                                    g->addr((const uint8_t*) base), g->fLength)) {
629                    SkColorSpacePrintf("Failed to read G gamma tag.\n");
630                }
631                if (!b || !SkColorSpace::LoadGammas(&gammas.fBlue, 1,
632                                                    b->addr((const uint8_t*) base), b->fLength)) {
633                    SkColorSpacePrintf("Failed to read B gamma tag.\n");
634                }
635
636                SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
637                mat.set3x3ColMajorf(toXYZ);
638                return SkColorSpace::NewRGB(std::move(gammas), mat);
639            }
640
641            // Recognize color profile specified by A2B0 tag.
642            const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
643            if (a2b0) {
644                SkColorLookUpTable colorLUT;
645                SkGammas gammas;
646                SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
647                if (!SkColorSpace::LoadA2B0(&colorLUT, &gammas, &toXYZ,
648                                            a2b0->addr((const uint8_t*) base), a2b0->fLength)) {
649                    return_null("Failed to parse A2B0 tag");
650                }
651
652                // If there is no colorLUT, use NewRGB.  This allows us to check if the
653                // profile is sRGB.
654                if (!colorLUT.fTable) {
655                    return SkColorSpace::NewRGB(std::move(gammas), toXYZ);
656                }
657
658                return sk_sp<SkColorSpace>(new SkColorSpace(std::move(colorLUT), std::move(gammas),
659                                                            toXYZ));
660            }
661
662        }
663        default:
664            break;
665    }
666
667    return_null("ICC profile contains unsupported colorspace");
668}
669