SkColorSpace.cpp revision 1cf632500a854d01b789ea22c1cf7967cfb6f570
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 SkFloat3x3& toXYZD50, const SkFloat3& gamma, Named named)
92    : fToXYZD50(toXYZD50)
93    , fGamma(gamma)
94    , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID))
95    , fNamed(named)
96{
97    for (int i = 0; i < 3; ++i) {
98        SkASSERT(SkFloatIsFinite(gamma.fVec[i]));
99        for (int j = 0; j < 3; ++j) {
100            SkASSERT(SkFloatIsFinite(toXYZD50.fMat[3*i + j]));
101        }
102    }
103}
104
105sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma) {
106    for (int i = 0; i < 3; ++i) {
107        if (!SkFloatIsFinite(gamma.fVec[i]) || gamma.fVec[i] < 0) {
108            return nullptr;
109        }
110        for (int j = 0; j < 3; ++j) {
111            if (!SkFloatIsFinite(toXYZD50.fMat[3*i + j])) {
112                return nullptr;
113            }
114        }
115    }
116
117    // check the matrix for invertibility
118    float d = det(toXYZD50);
119    if (!SkFloatIsFinite(d) || !SkFloatIsFinite(1 / d)) {
120        return nullptr;
121    }
122
123    return sk_sp<SkColorSpace>(new SkColorSpace(toXYZD50, gamma, kUnknown_Named));
124}
125
126void SkColorSpace::dump() const {
127    fToXYZD50.dump();
128    fGamma.dump();
129}
130
131//////////////////////////////////////////////////////////////////////////////////////////////////
132
133const SkFloat3   gDevice_gamma {{ 0, 0, 0 }};
134const SkFloat3x3 gDevice_toXYZD50 {{
135    1, 0, 0,
136    0, 1, 0,
137    0, 0, 1
138}};
139
140const SkFloat3 gSRGB_gamma {{ 2.2f, 2.2f, 2.2f }};
141const SkFloat3x3 gSRGB_toXYZD50 {{
142    0.4358f, 0.2224f, 0.0139f,    // * R
143    0.3853f, 0.7170f, 0.0971f,    // * G
144    0.1430f, 0.0606f, 0.7139f,    // * B
145}};
146
147sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
148    switch (named) {
149        case kDevice_Named:
150            return sk_sp<SkColorSpace>(new SkColorSpace(gDevice_toXYZD50, gDevice_gamma,
151                                                        kDevice_Named));
152        case kSRGB_Named:
153            return sk_sp<SkColorSpace>(new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named));
154        default:
155            break;
156    }
157    return nullptr;
158}
159
160///////////////////////////////////////////////////////////////////////////////////////////////////
161
162#include "SkFixed.h"
163#include "SkTemplates.h"
164
165#define SkColorSpacePrintf(...)
166
167#define return_if_false(pred, msg)                                   \
168    do {                                                             \
169        if (!(pred)) {                                               \
170            SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \
171            return false;                                            \
172        }                                                            \
173    } while (0)
174
175#define return_null(msg)                                             \
176    do {                                                             \
177        SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg));     \
178        return nullptr;                                              \
179    } while (0)
180
181static uint16_t read_big_endian_short(const uint8_t* ptr) {
182    return ptr[0] << 8 | ptr[1];
183}
184
185static uint32_t read_big_endian_int(const uint8_t* ptr) {
186    return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
187}
188
189// This is equal to the header size according to the ICC specification (128)
190// plus the size of the tag count (4).  We include the tag count since we
191// always require it to be present anyway.
192static const size_t kICCHeaderSize = 132;
193
194// Contains a signature (4), offset (4), and size (4).
195static const size_t kICCTagTableEntrySize = 12;
196
197static const uint32_t kRGB_ColorSpace  = SkSetFourByteTag('R', 'G', 'B', ' ');
198static const uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y');
199
200struct ICCProfileHeader {
201    // TODO (msarett):
202    // Can we ignore less of these fields?
203    uint32_t fSize;
204    uint32_t fCMMType_ignored;
205    uint32_t fVersion;
206    uint32_t fClassProfile;
207    uint32_t fColorSpace;
208    uint32_t fPCS;
209    uint32_t fDateTime_ignored[3];
210    uint32_t fSignature;
211    uint32_t fPlatformTarget_ignored;
212    uint32_t fFlags_ignored;
213    uint32_t fManufacturer_ignored;
214    uint32_t fDeviceModel_ignored;
215    uint32_t fDeviceAttributes_ignored[2];
216    uint32_t fRenderingIntent;
217    uint32_t fIlluminantXYZ_ignored[3];
218    uint32_t fCreator_ignored;
219    uint32_t fProfileId_ignored[4];
220    uint32_t fReserved_ignored[7];
221    uint32_t fTagCount;
222
223    void init(const uint8_t* src, size_t len) {
224        SkASSERT(kICCHeaderSize == sizeof(*this));
225
226        uint32_t* dst = (uint32_t*) this;
227        for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) {
228            dst[i] = read_big_endian_int(src);
229        }
230    }
231
232    bool valid() const {
233        // TODO (msarett):
234        // For now it's nice to fail loudly on invalid inputs.  But, can we
235        // recover from some of these errors?
236
237        return_if_false(fSize >= kICCHeaderSize, "Size is too small");
238
239        uint8_t majorVersion = fVersion >> 24;
240        return_if_false(majorVersion <= 4, "Unsupported version");
241
242        const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
243        const uint32_t kInput_Profile   = SkSetFourByteTag('s', 'c', 'n', 'r');
244        const uint32_t kOutput_Profile  = SkSetFourByteTag('p', 'r', 't', 'r');
245        // TODO (msarett):
246        // Should we also support DeviceLink, ColorSpace, Abstract, or NamedColor?
247        return_if_false(fClassProfile == kDisplay_Profile ||
248                        fClassProfile == kInput_Profile ||
249                        fClassProfile == kOutput_Profile,
250                        "Unsupported class profile");
251
252        // TODO (msarett):
253        // There are many more color spaces that we could try to support.
254        return_if_false(fColorSpace == kRGB_ColorSpace || fColorSpace == kGray_ColorSpace,
255                        "Unsupported color space");
256
257        const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
258        // TODO (msarett):
259        // Can we support PCS LAB as well?
260        return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
261
262        return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad signature");
263
264        // TODO (msarett):
265        // Should we treat different rendering intents differently?
266        // Valid rendering intents include kPerceptual (0), kRelative (1),
267        // kSaturation (2), and kAbsolute (3).
268        return_if_false(fRenderingIntent <= 3, "Bad rendering intent");
269
270        return_if_false(fTagCount <= 100, "Too many tags");
271
272        return true;
273    }
274};
275
276struct ICCTag {
277    uint32_t fSignature;
278    uint32_t fOffset;
279    uint32_t fLength;
280
281    const uint8_t* init(const uint8_t* src) {
282        fSignature = read_big_endian_int(src);
283        fOffset = read_big_endian_int(src + 4);
284        fLength = read_big_endian_int(src + 8);
285        return src + 12;
286    }
287
288    bool valid(size_t len) {
289        return_if_false(fOffset + fLength <= len, "Tag too large for ICC profile");
290        return true;
291    }
292
293    const uint8_t* addr(const uint8_t* src) const {
294        return src + fOffset;
295    }
296
297    static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) {
298        for (int i = 0; i < count; ++i) {
299            if (tags[i].fSignature == signature) {
300                return &tags[i];
301            }
302        }
303        return nullptr;
304    }
305};
306
307// TODO (msarett):
308// Should we recognize more tags?
309static const uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
310static const uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
311static const uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
312static const uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
313static const uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
314static const uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
315
316bool load_xyz(float dst[3], const uint8_t* src, size_t len) {
317    if (len < 20) {
318        SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len);
319        return false;
320    }
321
322    dst[0] = SkFixedToFloat(read_big_endian_int(src + 8));
323    dst[1] = SkFixedToFloat(read_big_endian_int(src + 12));
324    dst[2] = SkFixedToFloat(read_big_endian_int(src + 16));
325    SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]);
326    return true;
327}
328
329static const uint32_t kTAG_CurveType     = SkSetFourByteTag('c', 'u', 'r', 'v');
330static const uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
331
332static bool load_gamma(float* gamma, const uint8_t* src, size_t len) {
333    if (len < 14) {
334        SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
335        return false;
336    }
337
338    uint32_t type = read_big_endian_int(src);
339    switch (type) {
340        case kTAG_CurveType: {
341            uint32_t count = read_big_endian_int(src + 8);
342            if (0 == count) {
343                return false;
344            }
345
346            const uint16_t* table = (const uint16_t*) (src + 12);
347            if (1 == count) {
348                // Table entry is the exponent (bias 256).
349                uint16_t value = read_big_endian_short((const uint8_t*) table);
350                *gamma = value / 256.0f;
351                SkColorSpacePrintf("gamma %d %g\n", value, *gamma);
352                return true;
353            }
354
355            // Check length again if we have a table.
356            if (len < 12 + 2 * count) {
357                SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
358                return false;
359            }
360
361            // Print the interpolation table.  For now, we ignore this and guess 2.2f.
362            for (uint32_t i = 0; i < count; i++) {
363                SkColorSpacePrintf("curve[%d] %d\n", i,
364                        read_big_endian_short((const uint8_t*) &table[i]));
365            }
366
367            *gamma = 2.2f;
368            return true;
369        }
370        case kTAG_ParaCurveType:
371            // Guess 2.2f.
372            SkColorSpacePrintf("parametric curve\n");
373            *gamma = 2.2f;
374            return true;
375        default:
376            SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
377            return false;
378    }
379}
380
381sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
382    const uint8_t* ptr = (const uint8_t*) base;
383
384    if (len < kICCHeaderSize) {
385        return_null("Data is not large enough to contain an ICC profile");
386    }
387
388    // Read the ICC profile header and check to make sure that it is valid.
389    ICCProfileHeader header;
390    header.init(ptr, len);
391    if (!header.valid()) {
392        return nullptr;
393    }
394
395    // Adjust ptr and len before reading the tags.
396    if (len < header.fSize) {
397        SkColorSpacePrintf("ICC profile might be truncated.\n");
398    } else if (len > header.fSize) {
399        SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n");
400        len = header.fSize;
401    }
402    ptr += kICCHeaderSize;
403    len -= kICCHeaderSize;
404
405    // Parse tag headers.
406    uint32_t tagCount = header.fTagCount;
407    SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount);
408    if (len < kICCTagTableEntrySize * tagCount) {
409        return_null("Not enough input data to read tag table entries");
410    }
411
412    SkAutoTArray<ICCTag> tags(tagCount);
413    for (uint32_t i = 0; i < tagCount; i++) {
414        ptr = tags[i].init(ptr);
415        SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF,
416                (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >>  8) & 0xFF,
417                (tags[i].fSignature >>  0) & 0xFF, tags[i].fOffset, tags[i].fLength);
418
419        if (!tags[i].valid(kICCHeaderSize + len)) {
420            return_null("Tag is too large to fit in ICC profile");
421        }
422    }
423
424    // Load our XYZ and gamma matrices.
425    SkFloat3x3 toXYZ;
426    SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }};
427    switch (header.fColorSpace) {
428        case kRGB_ColorSpace: {
429            const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ);
430            const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
431            const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
432            if (!r || !g || !b) {
433                return_null("Need rgb tags for XYZ space");
434            }
435
436            if (!load_xyz(&toXYZ.fMat[0], r->addr((const uint8_t*) base), r->fLength) ||
437                !load_xyz(&toXYZ.fMat[3], g->addr((const uint8_t*) base), g->fLength) ||
438                !load_xyz(&toXYZ.fMat[6], b->addr((const uint8_t*) base), b->fLength))
439            {
440                return_null("Need valid rgb tags for XYZ space");
441            }
442
443            r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
444            g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
445            b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
446            if (!r || !load_gamma(&gamma.fVec[0], r->addr((const uint8_t*) base), r->fLength)) {
447                SkColorSpacePrintf("Failed to read R gamma tag.\n");
448            }
449            if (!g || !load_gamma(&gamma.fVec[1], g->addr((const uint8_t*) base), g->fLength)) {
450                SkColorSpacePrintf("Failed to read G gamma tag.\n");
451            }
452            if (!b || !load_gamma(&gamma.fVec[2], b->addr((const uint8_t*) base), b->fLength)) {
453                SkColorSpacePrintf("Failed to read B gamma tag.\n");
454            }
455            return SkColorSpace::NewRGB(toXYZ, gamma);
456        }
457        default:
458            break;
459    }
460
461    return_null("ICC profile contains unsupported colorspace");
462}
463
464///////////////////////////////////////////////////////////////////////////////////////////////////
465
466SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColorSpace* dst,
467                                          SkFloat3x3* result) {
468    if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst->named())) {
469        if (result) {
470            *result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }};
471        }
472        return kIdentity_Result;
473    }
474    if (result) {
475        *result = concat(src->fToXYZD50, invert(dst->fToXYZD50));
476    }
477    return kNormal_Result;
478}
479
480#include "SkColor.h"
481#include "SkNx.h"
482#include "SkPM4f.h"
483
484void SkApply3x3ToPM4f(const SkFloat3x3& m, const SkPM4f src[], SkPM4f dst[], int count) {
485    SkASSERT(1 == SkPM4f::G);
486    SkASSERT(3 == SkPM4f::A);
487
488    Sk4f cr, cg, cb;
489    cg = Sk4f::Load(m.fMat + 3);
490    if (0 == SkPM4f::R) {
491        SkASSERT(2 == SkPM4f::B);
492        cr = Sk4f::Load(m.fMat + 0);
493        cb = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0);
494    } else {
495        SkASSERT(0 == SkPM4f::B);
496        SkASSERT(2 == SkPM4f::R);
497        cb = Sk4f::Load(m.fMat + 0);
498        cr = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0);
499    }
500    cr = cr * Sk4f(1, 1, 1, 0);
501    cg = cg * Sk4f(1, 1, 1, 0);
502    cb = cb * Sk4f(1, 1, 1, 0);
503
504    for (int i = 0; i < count; ++i) {
505        Sk4f r = Sk4f(src[i].fVec[SkPM4f::R]);
506        Sk4f g = Sk4f(src[i].fVec[SkPM4f::G]);
507        Sk4f b = Sk4f(src[i].fVec[SkPM4f::B]);
508        Sk4f a = Sk4f(0, 0, 0, src[i].fVec[SkPM4f::A]);
509        (cr * r + cg * g + cb * b + a).store(&dst[i]);
510    }
511}
512
513///////////////////////////////////////////////////////////////////////////////////////////////////
514
515void SkColorSpace::Test() {
516    SkFloat3x3 mat {{ 2, 0, 0, 0, 3, 0, 0, 0, 4 }};
517    SkFloat3x3 inv = invert(mat);
518    mat.dump();
519    inv.dump();
520    concat(mat, inv).dump();
521    concat(inv, mat).dump();
522    SkDebugf("\n");
523
524    mat = gSRGB_toXYZD50;
525    inv = invert(mat);
526    mat.dump();
527    inv.dump();
528    concat(mat, inv).dump();
529    concat(inv, mat).dump();
530    SkDebugf("\n");
531
532    sk_sp<SkColorSpace> cs0(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named));
533    sk_sp<SkColorSpace> cs1(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named));
534
535    cs0->dump();
536    cs1->dump();
537    SkFloat3x3 xform;
538    (void)SkColorSpace::Concat(cs0.get(), cs1.get(), &xform);
539    xform.dump();
540    SkDebugf("\n");
541}
542
543// D65 white point of Rec.  709 [8] are:
544//
545// D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890
546//
547//          R           G           B           white
548//   x      0.640       0.300       0.150       0.3127
549//   y      0.330       0.600       0.060       0.3290
550//   z      0.030       0.100       0.790       0.3582
551