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