blob: 549946965146f36893a23dd20195063e8b91b7a8 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkAtomics.h"
#include "SkColorSpace.h"
static inline bool SkFloatIsFinite(float x) { return 0 == x * 0; }
//
// SkFloat3x3
//
// In memory order, values are a, b, c, d, e, f, g, h, i
//
// When applied to a color component vector (e.g. [ r, r, r ] or [ g, g, g ] we do
//
// [ r r r ] * [ a b c ] + [ g g g ] * [ d e f ] + [ b b b ] * [ g h i ]
//
// Thus in our point-on-the-right notation, the matrix looks like
//
// [ a d g ] [ r ]
// [ b e h ] * [ g ]
// [ c f i ] [ b ]
//
static SkFloat3x3 concat(const SkFloat3x3& left, const SkFloat3x3& rite) {
SkFloat3x3 result;
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
double tmp = 0;
for (int i = 0; i < 3; ++i) {
tmp += (double)left.fMat[row + i * 3] * rite.fMat[i + col * 3];
}
result.fMat[row + col * 3] = (double)tmp;
}
}
return result;
}
static double det(const SkFloat3x3& m) {
return (double)m.fMat[0] * m.fMat[4] * m.fMat[8] +
(double)m.fMat[3] * m.fMat[7] * m.fMat[2] +
(double)m.fMat[6] * m.fMat[1] * m.fMat[5] -
(double)m.fMat[0] * m.fMat[7] * m.fMat[5] -
(double)m.fMat[3] * m.fMat[1] * m.fMat[8] -
(double)m.fMat[6] * m.fMat[4] * m.fMat[2];
}
static double det2x2(const SkFloat3x3& m, int a, int b, int c, int d) {
return (double)m.fMat[a] * m.fMat[b] - (double)m.fMat[c] * m.fMat[d];
}
static SkFloat3x3 invert(const SkFloat3x3& m) {
double d = det(m);
SkASSERT(SkFloatIsFinite((float)d));
double scale = 1 / d;
SkASSERT(SkFloatIsFinite((float)scale));
return {{
(float)(scale * det2x2(m, 4, 8, 5, 7)),
(float)(scale * det2x2(m, 7, 2, 8, 1)),
(float)(scale * det2x2(m, 1, 5, 2, 4)),
(float)(scale * det2x2(m, 6, 5, 8, 3)),
(float)(scale * det2x2(m, 0, 8, 2, 6)),
(float)(scale * det2x2(m, 3, 2, 5, 0)),
(float)(scale * det2x2(m, 3, 7, 4, 6)),
(float)(scale * det2x2(m, 6, 1, 7, 0)),
(float)(scale * det2x2(m, 0, 4, 1, 3)),
}};
}
void SkFloat3::dump() const {
SkDebugf("[%7.4f %7.4f %7.4f]\n", fVec[0], fVec[1], fVec[2]);
}
void SkFloat3x3::dump() const {
SkDebugf("[%7.4f %7.4f %7.4f] [%7.4f %7.4f %7.4f] [%7.4f %7.4f %7.4f]\n",
fMat[0], fMat[1], fMat[2],
fMat[3], fMat[4], fMat[5],
fMat[6], fMat[7], fMat[8]);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
static int32_t gUniqueColorSpaceID;
SkColorSpace::SkColorSpace(const SkFloat3x3& toXYZD50, const SkFloat3& gamma, Named named)
: fToXYZD50(toXYZD50)
, fGamma(gamma)
, fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID))
, fNamed(named)
{
for (int i = 0; i < 3; ++i) {
SkASSERT(SkFloatIsFinite(gamma.fVec[i]));
for (int j = 0; j < 3; ++j) {
SkASSERT(SkFloatIsFinite(toXYZD50.fMat[3*i + j]));
}
}
}
sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma) {
for (int i = 0; i < 3; ++i) {
if (!SkFloatIsFinite(gamma.fVec[i]) || gamma.fVec[i] < 0) {
return nullptr;
}
for (int j = 0; j < 3; ++j) {
if (!SkFloatIsFinite(toXYZD50.fMat[3*i + j])) {
return nullptr;
}
}
}
// check the matrix for invertibility
float d = det(toXYZD50);
if (!SkFloatIsFinite(d) || !SkFloatIsFinite(1 / d)) {
return nullptr;
}
return sk_sp<SkColorSpace>(new SkColorSpace(toXYZD50, gamma, kUnknown_Named));
}
void SkColorSpace::dump() const {
fToXYZD50.dump();
fGamma.dump();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
const SkFloat3 gDevice_gamma {{ 0, 0, 0 }};
const SkFloat3x3 gDevice_toXYZD50 {{
1, 0, 0,
0, 1, 0,
0, 0, 1
}};
const SkFloat3 gSRGB_gamma {{ 2.2f, 2.2f, 2.2f }};
const SkFloat3x3 gSRGB_toXYZD50 {{
0.4358f, 0.2224f, 0.0139f, // * R
0.3853f, 0.7170f, 0.0971f, // * G
0.1430f, 0.0606f, 0.7139f, // * B
}};
sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
switch (named) {
case kDevice_Named:
return sk_sp<SkColorSpace>(new SkColorSpace(gDevice_toXYZD50, gDevice_gamma,
kDevice_Named));
case kSRGB_Named:
return sk_sp<SkColorSpace>(new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named));
default:
break;
}
return nullptr;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "SkFixed.h"
#include "SkTemplates.h"
#define SkColorSpacePrintf(...)
#define return_if_false(pred, msg) \
do { \
if (!(pred)) { \
SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \
return false; \
} \
} while (0)
#define return_null(msg) \
do { \
SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \
return nullptr; \
} while (0)
static uint16_t read_big_endian_short(const uint8_t* ptr) {
return ptr[0] << 8 | ptr[1];
}
static uint32_t read_big_endian_int(const uint8_t* ptr) {
return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
}
// This is equal to the header size according to the ICC specification (128)
// plus the size of the tag count (4). We include the tag count since we
// always require it to be present anyway.
static const size_t kICCHeaderSize = 132;
// Contains a signature (4), offset (4), and size (4).
static const size_t kICCTagTableEntrySize = 12;
static const uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' ');
static const uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y');
struct ICCProfileHeader {
// TODO (msarett):
// Can we ignore less of these fields?
uint32_t fSize;
uint32_t fCMMType_ignored;
uint32_t fVersion;
uint32_t fClassProfile;
uint32_t fColorSpace;
uint32_t fPCS;
uint32_t fDateTime_ignored[3];
uint32_t fSignature;
uint32_t fPlatformTarget_ignored;
uint32_t fFlags_ignored;
uint32_t fManufacturer_ignored;
uint32_t fDeviceModel_ignored;
uint32_t fDeviceAttributes_ignored[2];
uint32_t fRenderingIntent;
uint32_t fIlluminantXYZ_ignored[3];
uint32_t fCreator_ignored;
uint32_t fProfileId_ignored[4];
uint32_t fReserved_ignored[7];
uint32_t fTagCount;
void init(const uint8_t* src, size_t len) {
SkASSERT(kICCHeaderSize == sizeof(*this));
uint32_t* dst = (uint32_t*) this;
for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) {
dst[i] = read_big_endian_int(src);
}
}
bool valid() const {
// TODO (msarett):
// For now it's nice to fail loudly on invalid inputs. But, can we
// recover from some of these errors?
return_if_false(fSize >= kICCHeaderSize, "Size is too small");
uint8_t majorVersion = fVersion >> 24;
return_if_false(majorVersion <= 4, "Unsupported version");
const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
const uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
const uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
// TODO (msarett):
// Should we also support DeviceLink, ColorSpace, Abstract, or NamedColor?
return_if_false(fClassProfile == kDisplay_Profile ||
fClassProfile == kInput_Profile ||
fClassProfile == kOutput_Profile,
"Unsupported class profile");
// TODO (msarett):
// There are many more color spaces that we could try to support.
return_if_false(fColorSpace == kRGB_ColorSpace || fColorSpace == kGray_ColorSpace,
"Unsupported color space");
const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
// TODO (msarett):
// Can we support PCS LAB as well?
return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad signature");
// TODO (msarett):
// Should we treat different rendering intents differently?
// Valid rendering intents include kPerceptual (0), kRelative (1),
// kSaturation (2), and kAbsolute (3).
return_if_false(fRenderingIntent <= 3, "Bad rendering intent");
return_if_false(fTagCount <= 100, "Too many tags");
return true;
}
};
struct ICCTag {
uint32_t fSignature;
uint32_t fOffset;
uint32_t fLength;
const uint8_t* init(const uint8_t* src) {
fSignature = read_big_endian_int(src);
fOffset = read_big_endian_int(src + 4);
fLength = read_big_endian_int(src + 8);
return src + 12;
}
bool valid(size_t len) {
return_if_false(fOffset + fLength <= len, "Tag too large for ICC profile");
return true;
}
const uint8_t* addr(const uint8_t* src) const {
return src + fOffset;
}
static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) {
for (int i = 0; i < count; ++i) {
if (tags[i].fSignature == signature) {
return &tags[i];
}
}
return nullptr;
}
};
// TODO (msarett):
// Should we recognize more tags?
static const uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
static const uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
static const uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
static const uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
static const uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
static const uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
bool load_xyz(float dst[3], const uint8_t* src, size_t len) {
if (len < 20) {
SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len);
return false;
}
dst[0] = SkFixedToFloat(read_big_endian_int(src + 8));
dst[1] = SkFixedToFloat(read_big_endian_int(src + 12));
dst[2] = SkFixedToFloat(read_big_endian_int(src + 16));
SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]);
return true;
}
static const uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v');
static const uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
static bool load_gamma(float* gamma, const uint8_t* src, size_t len) {
if (len < 14) {
SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
return false;
}
uint32_t type = read_big_endian_int(src);
switch (type) {
case kTAG_CurveType: {
uint32_t count = read_big_endian_int(src + 8);
if (0 == count) {
return false;
}
const uint16_t* table = (const uint16_t*) (src + 12);
if (1 == count) {
// Table entry is the exponent (bias 256).
uint16_t value = read_big_endian_short((const uint8_t*) table);
*gamma = value / 256.0f;
SkColorSpacePrintf("gamma %d %g\n", value, *gamma);
return true;
}
// Check length again if we have a table.
if (len < 12 + 2 * count) {
SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
return false;
}
// Print the interpolation table. For now, we ignore this and guess 2.2f.
for (uint32_t i = 0; i < count; i++) {
SkColorSpacePrintf("curve[%d] %d\n", i,
read_big_endian_short((const uint8_t*) &table[i]));
}
*gamma = 2.2f;
return true;
}
case kTAG_ParaCurveType:
// Guess 2.2f.
SkColorSpacePrintf("parametric curve\n");
*gamma = 2.2f;
return true;
default:
SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
return false;
}
}
sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
const uint8_t* ptr = (const uint8_t*) base;
if (len < kICCHeaderSize) {
return_null("Data is not large enough to contain an ICC profile");
}
// Read the ICC profile header and check to make sure that it is valid.
ICCProfileHeader header;
header.init(ptr, len);
if (!header.valid()) {
return nullptr;
}
// Adjust ptr and len before reading the tags.
if (len < header.fSize) {
SkColorSpacePrintf("ICC profile might be truncated.\n");
} else if (len > header.fSize) {
SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n");
len = header.fSize;
}
ptr += kICCHeaderSize;
len -= kICCHeaderSize;
// Parse tag headers.
uint32_t tagCount = header.fTagCount;
SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount);
if (len < kICCTagTableEntrySize * tagCount) {
return_null("Not enough input data to read tag table entries");
}
SkAutoTArray<ICCTag> tags(tagCount);
for (uint32_t i = 0; i < tagCount; i++) {
ptr = tags[i].init(ptr);
SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF,
(tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) & 0xFF,
(tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLength);
if (!tags[i].valid(kICCHeaderSize + len)) {
return_null("Tag is too large to fit in ICC profile");
}
}
// Load our XYZ and gamma matrices.
SkFloat3x3 toXYZ;
SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }};
switch (header.fColorSpace) {
case kRGB_ColorSpace: {
const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ);
const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
if (!r || !g || !b) {
return_null("Need rgb tags for XYZ space");
}
if (!load_xyz(&toXYZ.fMat[0], r->addr((const uint8_t*) base), r->fLength) ||
!load_xyz(&toXYZ.fMat[3], g->addr((const uint8_t*) base), g->fLength) ||
!load_xyz(&toXYZ.fMat[6], b->addr((const uint8_t*) base), b->fLength))
{
return_null("Need valid rgb tags for XYZ space");
}
r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
if (!r || !load_gamma(&gamma.fVec[0], r->addr((const uint8_t*) base), r->fLength)) {
SkColorSpacePrintf("Failed to read R gamma tag.\n");
}
if (!g || !load_gamma(&gamma.fVec[1], g->addr((const uint8_t*) base), g->fLength)) {
SkColorSpacePrintf("Failed to read G gamma tag.\n");
}
if (!b || !load_gamma(&gamma.fVec[2], b->addr((const uint8_t*) base), b->fLength)) {
SkColorSpacePrintf("Failed to read B gamma tag.\n");
}
return SkColorSpace::NewRGB(toXYZ, gamma);
}
default:
break;
}
return_null("ICC profile contains unsupported colorspace");
}
///////////////////////////////////////////////////////////////////////////////////////////////////
SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColorSpace* dst,
SkFloat3x3* result) {
if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst->named())) {
if (result) {
*result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }};
}
return kIdentity_Result;
}
if (result) {
*result = concat(src->fToXYZD50, invert(dst->fToXYZD50));
}
return kNormal_Result;
}
#include "SkColor.h"
#include "SkNx.h"
#include "SkPM4f.h"
void SkApply3x3ToPM4f(const SkFloat3x3& m, const SkPM4f src[], SkPM4f dst[], int count) {
SkASSERT(1 == SkPM4f::G);
SkASSERT(3 == SkPM4f::A);
Sk4f cr, cg, cb;
cg = Sk4f::Load(m.fMat + 3);
if (0 == SkPM4f::R) {
SkASSERT(2 == SkPM4f::B);
cr = Sk4f::Load(m.fMat + 0);
cb = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0);
} else {
SkASSERT(0 == SkPM4f::B);
SkASSERT(2 == SkPM4f::R);
cb = Sk4f::Load(m.fMat + 0);
cr = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0);
}
cr = cr * Sk4f(1, 1, 1, 0);
cg = cg * Sk4f(1, 1, 1, 0);
cb = cb * Sk4f(1, 1, 1, 0);
for (int i = 0; i < count; ++i) {
Sk4f r = Sk4f(src[i].fVec[SkPM4f::R]);
Sk4f g = Sk4f(src[i].fVec[SkPM4f::G]);
Sk4f b = Sk4f(src[i].fVec[SkPM4f::B]);
Sk4f a = Sk4f(0, 0, 0, src[i].fVec[SkPM4f::A]);
(cr * r + cg * g + cb * b + a).store(&dst[i]);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void SkColorSpace::Test() {
SkFloat3x3 mat {{ 2, 0, 0, 0, 3, 0, 0, 0, 4 }};
SkFloat3x3 inv = invert(mat);
mat.dump();
inv.dump();
concat(mat, inv).dump();
concat(inv, mat).dump();
SkDebugf("\n");
mat = gSRGB_toXYZD50;
inv = invert(mat);
mat.dump();
inv.dump();
concat(mat, inv).dump();
concat(inv, mat).dump();
SkDebugf("\n");
sk_sp<SkColorSpace> cs0(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named));
sk_sp<SkColorSpace> cs1(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named));
cs0->dump();
cs1->dump();
SkFloat3x3 xform;
(void)SkColorSpace::Concat(cs0.get(), cs1.get(), &xform);
xform.dump();
SkDebugf("\n");
}
// D65 white point of Rec. 709 [8] are:
//
// D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890
//
// R G B white
// x 0.640 0.300 0.150 0.3127
// y 0.330 0.600 0.060 0.3290
// z 0.030 0.100 0.790 0.3582