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