/* * 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 "SkColorSpace.h" #include "SkColorSpace_Base.h" #include "SkColorSpacePriv.h" #include "SkOnce.h" SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named) : fGammaNamed(gammaNamed) , fToXYZD50(toXYZD50) , fNamed(named) {} SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named) : INHERITED(gammaNamed, toXYZD50, named) , fGammas(nullptr) , fProfileData(nullptr) {} SkColorSpace_Base::SkColorSpace_Base(sk_sp colorLUT, GammaNamed gammaNamed, sk_sp gammas, const SkMatrix44& toXYZD50, sk_sp profileData) : INHERITED(gammaNamed, toXYZD50, kUnknown_Named) , fColorLUT(std::move(colorLUT)) , fGammas(std::move(gammas)) , fProfileData(std::move(profileData)) {} static constexpr float gSRGB_toXYZD50[] { 0.4358f, 0.2224f, 0.0139f, // * R 0.3853f, 0.7170f, 0.0971f, // * G 0.1430f, 0.0606f, 0.7139f, // * B }; static constexpr float gAdobeRGB_toXYZD50[] { 0.6098f, 0.3111f, 0.0195f, // * R 0.2052f, 0.6257f, 0.0609f, // * G 0.1492f, 0.0632f, 0.7448f, // * B }; /** * Checks if our toXYZ matrix is a close match to a known color gamut. * * @param toXYZD50 transformation matrix deduced from profile data * @param standard 3x3 canonical transformation matrix */ static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) { return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) && color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) && color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) && color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) && color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) && color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) && color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) && color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) && color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); } sk_sp SkColorSpace_Base::NewRGB(const float values[3], const SkMatrix44& toXYZD50) { if (0.0f > values[0] || 0.0f > values[1] || 0.0f > values[2]) { return nullptr; } GammaNamed gammaNamed = kNonStandard_GammaNamed; if (color_space_almost_equal(2.2f, values[0]) && color_space_almost_equal(2.2f, values[1]) && color_space_almost_equal(2.2f, values[2])) { gammaNamed = k2Dot2Curve_GammaNamed; } else if (color_space_almost_equal(1.0f, values[0]) && color_space_almost_equal(1.0f, values[1]) && color_space_almost_equal(1.0f, values[2])) { gammaNamed = kLinear_GammaNamed; } if (kNonStandard_GammaNamed == gammaNamed) { sk_sp gammas = sk_sp(new SkGammas()); gammas->fRedType = SkGammas::Type::kValue_Type; gammas->fGreenType = SkGammas::Type::kValue_Type; gammas->fBlueType = SkGammas::Type::kValue_Type; gammas->fRedData.fValue = values[0]; gammas->fGreenData.fValue = values[1]; gammas->fBlueData.fValue = values[2]; return sk_sp(new SkColorSpace_Base(nullptr, kNonStandard_GammaNamed, gammas, toXYZD50, nullptr)); } return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50); } sk_sp SkColorSpace_Base::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50) { switch (gammaNamed) { case kSRGB_GammaNamed: if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) { return SkColorSpace::NewNamed(kSRGB_Named); } break; case k2Dot2Curve_GammaNamed: if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) { return SkColorSpace::NewNamed(kAdobeRGB_Named); } break; case kNonStandard_GammaNamed: // This is not allowed. return nullptr; default: break; } return sk_sp(new SkColorSpace_Base(gammaNamed, toXYZD50, kUnknown_Named)); } sk_sp SkColorSpace::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50) { return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50); } sk_sp SkColorSpace::NewNamed(Named named) { static SkOnce sRGBOnce; static sk_sp sRGB; static SkOnce adobeRGBOnce; static sk_sp adobeRGB; switch (named) { case kSRGB_Named: { sRGBOnce([] { SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50); // Force the mutable type mask to be computed. This avoids races. (void)srgbToxyzD50.getType(); sRGB.reset(new SkColorSpace_Base(kSRGB_GammaNamed, srgbToxyzD50, kSRGB_Named)); }); return sRGB; } case kAdobeRGB_Named: { adobeRGBOnce([] { SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor); adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50); // Force the mutable type mask to be computed. This avoids races. (void)adobergbToxyzD50.getType(); adobeRGB.reset(new SkColorSpace_Base(k2Dot2Curve_GammaNamed, adobergbToxyzD50, kAdobeRGB_Named)); }); return adobeRGB; } default: break; } return nullptr; } sk_sp SkColorSpace::makeLinearGamma() { return SkColorSpace_Base::NewRGB(kLinear_GammaNamed, fToXYZD50); } /////////////////////////////////////////////////////////////////////////////////////////////////// enum Version { k0_Version, // Initial version, header + flags for matrix and profile }; struct ColorSpaceHeader { /** * If kMatrix_Flag is set, we will write 12 floats after the header. * Should not be set at the same time as the kICC_Flag or kFloatGamma_Flag. */ static constexpr uint8_t kMatrix_Flag = 1 << 0; /** * If kICC_Flag is set, we will write an ICC profile after the header. * The ICC profile will be written as a uint32 size, followed immediately * by the data (padded to 4 bytes). * Should not be set at the same time as the kMatrix_Flag or kFloatGamma_Flag. */ static constexpr uint8_t kICC_Flag = 1 << 1; /** * If kFloatGamma_Flag is set, we will write 15 floats after the header. * The first three are the gamma values, and the next twelve are the * matrix. * Should not be set at the same time as the kICC_Flag or kMatrix_Flag. */ static constexpr uint8_t kFloatGamma_Flag = 1 << 2; static ColorSpaceHeader Pack(Version version, SkColorSpace::Named named, SkColorSpace::GammaNamed gammaNamed, uint8_t flags) { ColorSpaceHeader header; SkASSERT(k0_Version == version); header.fVersion = (uint8_t) version; SkASSERT(named <= SkColorSpace::kAdobeRGB_Named); header.fNamed = (uint8_t) named; SkASSERT(gammaNamed <= SkColorSpace::kNonStandard_GammaNamed); header.fGammaNamed = (uint8_t) gammaNamed; SkASSERT(flags <= kFloatGamma_Flag); header.fFlags = flags; return header; } uint8_t fVersion; // Always zero uint8_t fNamed; // Must be a SkColorSpace::Named uint8_t fGammaNamed; // Must be a SkColorSpace::GammaNamed uint8_t fFlags; // Some combination of the flags listed above }; size_t SkColorSpace::writeToMemory(void* memory) const { // Start by trying the serialization fast path. If we haven't saved ICC profile data, // we must have a profile that we can serialize easily. if (!as_CSB(this)->fProfileData) { // If we have a named profile, only write the enum. switch (fNamed) { case kSRGB_Named: case kAdobeRGB_Named: { if (memory) { *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, 0); } return sizeof(ColorSpaceHeader); } default: break; } // If we have a named gamma, write the enum and the matrix. switch (fGammaNamed) { case kSRGB_GammaNamed: case k2Dot2Curve_GammaNamed: case kLinear_GammaNamed: { if (memory) { *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, ColorSpaceHeader::kMatrix_Flag); memory = SkTAddOffset(memory, sizeof(ColorSpaceHeader)); fToXYZD50.as4x3ColMajorf((float*) memory); } return sizeof(ColorSpaceHeader) + 12 * sizeof(float); } default: // Otherwise, write the gamma values and the matrix. if (memory) { *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, ColorSpaceHeader::kFloatGamma_Flag); memory = SkTAddOffset(memory, sizeof(ColorSpaceHeader)); const SkGammas* gammas = as_CSB(this)->gammas(); SkASSERT(gammas); SkASSERT(SkGammas::Type::kValue_Type == gammas->fRedType && SkGammas::Type::kValue_Type == gammas->fGreenType && SkGammas::Type::kValue_Type == gammas->fBlueType); *(((float*) memory) + 0) = gammas->fRedData.fValue; *(((float*) memory) + 1) = gammas->fGreenData.fValue; *(((float*) memory) + 2) = gammas->fBlueData.fValue; memory = SkTAddOffset(memory, 3 * sizeof(float)); fToXYZD50.as4x3ColMajorf((float*) memory); } return sizeof(ColorSpaceHeader) + 15 * sizeof(float); } } // Otherwise, serialize the ICC data. size_t profileSize = as_CSB(this)->fProfileData->size(); if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) { return 0; } if (memory) { *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, kUnknown_Named, kNonStandard_GammaNamed, ColorSpaceHeader::kICC_Flag); memory = SkTAddOffset(memory, sizeof(ColorSpaceHeader)); *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize); memory = SkTAddOffset(memory, sizeof(uint32_t)); memcpy(memory, as_CSB(this)->fProfileData->data(), profileSize); memset(SkTAddOffset(memory, profileSize), 0, SkAlign4(profileSize) - profileSize); } return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize); } sk_sp SkColorSpace::serialize() const { size_t size = this->writeToMemory(nullptr); if (0 == size) { return nullptr; } sk_sp data = SkData::MakeUninitialized(size); this->writeToMemory(data->writable_data()); return data; } sk_sp SkColorSpace::Deserialize(const void* data, size_t length) { if (length < sizeof(ColorSpaceHeader)) { return nullptr; } ColorSpaceHeader header = *((const ColorSpaceHeader*) data); data = SkTAddOffset(data, sizeof(ColorSpaceHeader)); length -= sizeof(ColorSpaceHeader); switch ((Named) header.fNamed) { case kSRGB_Named: case kAdobeRGB_Named: return NewNamed((Named) header.fNamed); default: break; } switch ((GammaNamed) header.fGammaNamed) { case kSRGB_GammaNamed: case k2Dot2Curve_GammaNamed: case kLinear_GammaNamed: { if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) { return nullptr; } SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); toXYZ.set4x3ColMajorf((const float*) data); return NewRGB((GammaNamed) header.fGammaNamed, toXYZ); } default: break; } switch (header.fFlags) { case ColorSpaceHeader::kICC_Flag: { if (length < sizeof(uint32_t)) { return nullptr; } uint32_t profileSize = *((uint32_t*) data); data = SkTAddOffset(data, sizeof(uint32_t)); length -= sizeof(uint32_t); if (length < profileSize) { return nullptr; } return NewICC(data, profileSize); } case ColorSpaceHeader::kFloatGamma_Flag: { if (length < 15 * sizeof(float)) { return nullptr; } float gammas[3]; gammas[0] = *(((const float*) data) + 0); gammas[1] = *(((const float*) data) + 1); gammas[2] = *(((const float*) data) + 2); data = SkTAddOffset(data, 3 * sizeof(float)); SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); toXYZ.set4x3ColMajorf((const float*) data); return SkColorSpace_Base::NewRGB(gammas, toXYZ); } default: return nullptr; } } bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) { if (src == dst) { return true; } if (!src || !dst) { return false; } switch (src->fNamed) { case kSRGB_Named: case kAdobeRGB_Named: return src->fNamed == dst->fNamed; case kUnknown_Named: if (kUnknown_Named != dst->fNamed) { return false; } break; } SkData* srcData = as_CSB(src)->fProfileData.get(); SkData* dstData = as_CSB(dst)->fProfileData.get(); if (srcData || dstData) { if (srcData && dstData) { return srcData->size() == dstData->size() && 0 == memcmp(srcData->data(), dstData->data(), srcData->size()); } return false; } // It's important to check fProfileData before named gammas. Some profiles may have named // gammas, but also include other wacky features that cause us to save the data. switch (src->fGammaNamed) { case kSRGB_GammaNamed: case k2Dot2Curve_GammaNamed: case kLinear_GammaNamed: return (src->fGammaNamed == dst->fGammaNamed) && (src->fToXYZD50 == dst->fToXYZD50); default: if (src->fGammaNamed != dst->fGammaNamed) { return false; } // It is unlikely that we will reach this case. sk_sp srcData = src->serialize(); sk_sp dstData = dst->serialize(); return srcData->size() == dstData->size() && 0 == memcmp(srcData->data(), dstData->data(), srcData->size()); } } bool SkColorSpace::gammasAreMatching() const { const SkGammas* gammas = as_CSB(this)->gammas(); SkASSERT(gammas); return gammas->fRedType == gammas->fGreenType && gammas->fGreenType == gammas->fBlueType && gammas->fRedData == gammas->fGreenData && gammas->fGreenData == gammas->fBlueData; } bool SkColorSpace::gammasAreNamed() const { const SkGammas* gammas = as_CSB(this)->gammas(); SkASSERT(gammas); return gammas->fRedType == SkGammas::Type::kNamed_Type && gammas->fGreenType == SkGammas::Type::kNamed_Type && gammas->fBlueType == SkGammas::Type::kNamed_Type; } bool SkColorSpace::gammasAreValues() const { const SkGammas* gammas = as_CSB(this)->gammas(); SkASSERT(gammas); return gammas->fRedType == SkGammas::Type::kValue_Type && gammas->fGreenType == SkGammas::Type::kValue_Type && gammas->fBlueType == SkGammas::Type::kValue_Type; } bool SkColorSpace::gammasAreTables() const { const SkGammas* gammas = as_CSB(this)->gammas(); SkASSERT(gammas); return gammas->fRedType == SkGammas::Type::kTable_Type && gammas->fGreenType == SkGammas::Type::kTable_Type && gammas->fBlueType == SkGammas::Type::kTable_Type; } bool SkColorSpace::gammasAreParams() const { const SkGammas* gammas = as_CSB(this)->gammas(); SkASSERT(gammas); return gammas->fRedType == SkGammas::Type::kParam_Type && gammas->fGreenType == SkGammas::Type::kParam_Type && gammas->fBlueType == SkGammas::Type::kParam_Type; }