| /* |
| * 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 "include/core/SkColorSpace.h" |
| #include "include/core/SkData.h" |
| #include "include/third_party/skcms/skcms.h" |
| #include "src/core/SkColorSpacePriv.h" |
| #include "src/core/SkOpts.h" |
| |
| bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const { |
| return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50); |
| } |
| |
| SkColorSpace::SkColorSpace(const float transferFn[7], |
| const skcms_Matrix3x3& toXYZD50) { |
| memcpy(fToXYZD50_3x3, &toXYZD50.vals[0][0], 9*sizeof(float)); |
| fToXYZD50Hash = SkOpts::hash_fn(fToXYZD50_3x3, 9*sizeof(float), 0); |
| |
| memcpy(fTransferFn, transferFn, 7*sizeof(float)); |
| fTransferFnHash = SkOpts::hash_fn(fTransferFn, 7*sizeof(float), 0); |
| } |
| |
| static bool xyz_almost_equal(const skcms_Matrix3x3& mA, const skcms_Matrix3x3& mB) { |
| for (int r = 0; r < 3; ++r) { |
| for (int c = 0; c < 3; ++c) { |
| if (!color_space_almost_equal(mA.vals[r][c], mB.vals[r][c])) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn, |
| const skcms_Matrix3x3& toXYZ) { |
| if (classify_transfer_fn(transferFn) == Bad_TF) { |
| return nullptr; |
| } |
| |
| const float* tf = &transferFn.g; |
| |
| if (is_almost_srgb(transferFn)) { |
| if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) { |
| return SkColorSpace::MakeSRGB(); |
| } |
| tf = &SkNamedTransferFn::kSRGB.g; |
| } else if (is_almost_2dot2(transferFn)) { |
| tf = &SkNamedTransferFn::k2Dot2.g; |
| } else if (is_almost_linear(transferFn)) { |
| if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) { |
| return SkColorSpace::MakeSRGBLinear(); |
| } |
| tf = &SkNamedTransferFn::kLinear.g; |
| } |
| |
| return sk_sp<SkColorSpace>(new SkColorSpace(tf, toXYZ)); |
| } |
| |
| class SkColorSpaceSingletonFactory { |
| public: |
| static SkColorSpace* Make(const skcms_TransferFunction& transferFn, |
| const skcms_Matrix3x3& to_xyz) { |
| return new SkColorSpace(&transferFn.g, to_xyz); |
| } |
| }; |
| |
| SkColorSpace* sk_srgb_singleton() { |
| static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kSRGB, |
| SkNamedGamut::kSRGB); |
| return cs; |
| } |
| |
| SkColorSpace* sk_srgb_linear_singleton() { |
| static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kLinear, |
| SkNamedGamut::kSRGB); |
| return cs; |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() { |
| return sk_ref_sp(sk_srgb_singleton()); |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() { |
| return sk_ref_sp(sk_srgb_linear_singleton()); |
| } |
| |
| void SkColorSpace::computeLazyDstFields() const { |
| fLazyDstFieldsOnce([this] { |
| |
| // Invert 3x3 gamut, defaulting to sRGB if we can't. |
| { |
| skcms_Matrix3x3 fwd, inv; |
| memcpy(&fwd, fToXYZD50_3x3, 9*sizeof(float)); |
| if (!skcms_Matrix3x3_invert(&fwd, &inv)) { |
| SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50, &inv)); |
| } |
| memcpy(fFromXYZD50_3x3, &inv, 9*sizeof(float)); |
| } |
| |
| // Invert transfer function, defaulting to sRGB if we can't. |
| { |
| skcms_TransferFunction fwd, inv; |
| this->transferFn(&fwd.g); |
| if (!skcms_TransferFunction_invert(&fwd, &inv)) { |
| inv = *skcms_sRGB_Inverse_TransferFunction(); |
| } |
| memcpy(fInvTransferFn, &inv, 7*sizeof(float)); |
| } |
| |
| }); |
| } |
| |
| bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const { |
| // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers |
| // already pass pointers to an skcms struct). Then remove this function, and update the two |
| // remaining callers to do the right thing with transferFn and classify. |
| this->transferFn(&coeffs->g); |
| return classify_transfer_fn(*coeffs) == sRGBish_TF; |
| } |
| |
| void SkColorSpace::transferFn(float gabcdef[7]) const { |
| memcpy(gabcdef, &fTransferFn, 7*sizeof(float)); |
| } |
| |
| void SkColorSpace::invTransferFn(float gabcdef[7]) const { |
| this->computeLazyDstFields(); |
| memcpy(gabcdef, &fInvTransferFn, 7*sizeof(float)); |
| } |
| |
| bool SkColorSpace::toXYZD50(SkMatrix44* toXYZD50) const { |
| toXYZD50->set3x3RowMajorf(fToXYZD50_3x3); |
| return true; |
| } |
| |
| bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const { |
| memcpy(toXYZD50, fToXYZD50_3x3, 9*sizeof(float)); |
| return true; |
| } |
| |
| void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, float src_to_dst[9]) const { |
| dst->computeLazyDstFields(); |
| |
| skcms_Matrix3x3 toXYZD50, |
| fromXYZD50; |
| |
| memcpy(& toXYZD50, this-> fToXYZD50_3x3, 9*sizeof(float)); |
| memcpy(&fromXYZD50, dst ->fFromXYZD50_3x3, 9*sizeof(float)); |
| |
| skcms_Matrix3x3 srcToDst = skcms_Matrix3x3_concat(&fromXYZD50, &toXYZD50); |
| memcpy(src_to_dst, &srcToDst, 9*sizeof(float)); |
| } |
| |
| bool SkColorSpace::isSRGB() const { |
| return sk_srgb_singleton() == this; |
| } |
| |
| bool SkColorSpace::gammaCloseToSRGB() const { |
| // Nearly-equal transfer functions were snapped at construction time, so just do an exact test |
| return memcmp(fTransferFn, &SkNamedTransferFn::kSRGB.g, 7*sizeof(float)) == 0; |
| } |
| |
| bool SkColorSpace::gammaIsLinear() const { |
| // Nearly-equal transfer functions were snapped at construction time, so just do an exact test |
| return memcmp(fTransferFn, &SkNamedTransferFn::kLinear.g, 7*sizeof(float)) == 0; |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const { |
| if (this->gammaIsLinear()) { |
| return sk_ref_sp(const_cast<SkColorSpace*>(this)); |
| } |
| skcms_Matrix3x3 gamut; |
| this->toXYZD50(&gamut); |
| return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut); |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const { |
| if (this->gammaCloseToSRGB()) { |
| return sk_ref_sp(const_cast<SkColorSpace*>(this)); |
| } |
| skcms_Matrix3x3 gamut; |
| this->toXYZD50(&gamut); |
| return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut); |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const { |
| skcms_Matrix3x3 spin = {{ |
| { 0, 0, 1 }, |
| { 1, 0, 0 }, |
| { 0, 1, 0 }, |
| }}; |
| |
| skcms_Matrix3x3 toXYZ; |
| this->toXYZD50(&toXYZ); |
| |
| skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&toXYZ, &spin); |
| |
| return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun)); |
| } |
| |
| void SkColorSpace::toProfile(skcms_ICCProfile* profile) const { |
| skcms_TransferFunction tf; |
| skcms_Matrix3x3 toXYZD50; |
| |
| memcpy(&tf, fTransferFn, 7*sizeof(float)); |
| memcpy(&toXYZD50, fToXYZD50_3x3, 9*sizeof(float)); |
| |
| skcms_Init (profile); |
| skcms_SetTransferFunction(profile, &tf); |
| skcms_SetXYZD50 (profile, &toXYZD50); |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) { |
| // TODO: move below ≈sRGB test? |
| if (!profile.has_toXYZD50 || !profile.has_trc) { |
| return nullptr; |
| } |
| |
| if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) { |
| return SkColorSpace::MakeSRGB(); |
| } |
| |
| // TODO: can we save this work and skip lazily inverting the matrix later? |
| skcms_Matrix3x3 inv; |
| if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) { |
| return nullptr; |
| } |
| |
| // We can't work with tables or mismatched parametric curves, |
| // but if they all look close enough to sRGB, that's fine. |
| // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB? |
| const skcms_Curve* trc = profile.trc; |
| if (trc[0].table_entries != 0 || |
| trc[1].table_entries != 0 || |
| trc[2].table_entries != 0 || |
| 0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) || |
| 0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric))) |
| { |
| if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) { |
| return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50); |
| } |
| return nullptr; |
| } |
| |
| return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| enum Version { |
| k0_Version, // Initial version, header + flags for matrix and profile |
| k1_Version, // Simple header (version tag) + 16 floats |
| |
| kCurrent_Version = k1_Version, |
| }; |
| |
| enum NamedColorSpace { |
| kSRGB_NamedColorSpace, |
| kAdobeRGB_NamedColorSpace, |
| kSRGBLinear_NamedColorSpace, |
| }; |
| |
| enum NamedGamma { |
| kLinear_NamedGamma, |
| kSRGB_NamedGamma, |
| k2Dot2_NamedGamma, |
| }; |
| |
| struct ColorSpaceHeader { |
| // Flag values, only used by old (k0_Version) serialization |
| static constexpr uint8_t kMatrix_Flag = 1 << 0; |
| static constexpr uint8_t kICC_Flag = 1 << 1; |
| static constexpr uint8_t kTransferFn_Flag = 1 << 3; |
| |
| uint8_t fVersion = kCurrent_Version; |
| |
| // Other fields are only used by k0_Version. Could be re-purposed in future versions. |
| uint8_t fNamed = 0; |
| uint8_t fGammaNamed = 0; |
| uint8_t fFlags = 0; |
| }; |
| |
| size_t SkColorSpace::writeToMemory(void* memory) const { |
| if (memory) { |
| *((ColorSpaceHeader*) memory) = ColorSpaceHeader(); |
| memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader)); |
| |
| memcpy(memory, fTransferFn, 7 * sizeof(float)); |
| memory = SkTAddOffset<void>(memory, 7 * sizeof(float)); |
| |
| memcpy(memory, fToXYZD50_3x3, 9 * sizeof(float)); |
| } |
| |
| return sizeof(ColorSpaceHeader) + 16 * sizeof(float); |
| } |
| |
| sk_sp<SkData> SkColorSpace::serialize() const { |
| sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr)); |
| this->writeToMemory(data->writable_data()); |
| return data; |
| } |
| |
| sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) { |
| if (length < sizeof(ColorSpaceHeader)) { |
| return nullptr; |
| } |
| |
| ColorSpaceHeader header = *((const ColorSpaceHeader*) data); |
| data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader)); |
| length -= sizeof(ColorSpaceHeader); |
| if (k1_Version == header.fVersion) { |
| if (length < 16 * sizeof(float)) { |
| return nullptr; |
| } |
| |
| skcms_TransferFunction transferFn; |
| memcpy(&transferFn, data, 7 * sizeof(float)); |
| data = SkTAddOffset<const void>(data, 7 * sizeof(float)); |
| |
| skcms_Matrix3x3 toXYZ; |
| memcpy(&toXYZ, data, 9 * sizeof(float)); |
| return SkColorSpace::MakeRGB(transferFn, toXYZ); |
| } else if (k0_Version == header.fVersion) { |
| if (0 == header.fFlags) { |
| switch ((NamedColorSpace)header.fNamed) { |
| case kSRGB_NamedColorSpace: |
| return SkColorSpace::MakeSRGB(); |
| case kSRGBLinear_NamedColorSpace: |
| return SkColorSpace::MakeSRGBLinear(); |
| case kAdobeRGB_NamedColorSpace: |
| return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, |
| SkNamedGamut::kAdobeRGB); |
| } |
| } |
| |
| auto make_named_tf = [=](const skcms_TransferFunction& tf) { |
| if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) { |
| return sk_sp<SkColorSpace>(nullptr); |
| } |
| |
| // Version 0 matrix is row-major 3x4 |
| skcms_Matrix3x3 toXYZ; |
| memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float)); |
| memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float)); |
| memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float)); |
| return SkColorSpace::MakeRGB(tf, toXYZ); |
| }; |
| |
| switch ((NamedGamma) header.fGammaNamed) { |
| case kSRGB_NamedGamma: |
| return make_named_tf(SkNamedTransferFn::kSRGB); |
| case k2Dot2_NamedGamma: |
| return make_named_tf(SkNamedTransferFn::k2Dot2); |
| case kLinear_NamedGamma: |
| return make_named_tf(SkNamedTransferFn::kLinear); |
| default: |
| break; |
| } |
| |
| switch (header.fFlags) { |
| case ColorSpaceHeader::kICC_Flag: { |
| // Deprecated and unsupported code path |
| return nullptr; |
| } |
| case ColorSpaceHeader::kTransferFn_Flag: { |
| if (length < 19 * sizeof(float)) { |
| return nullptr; |
| } |
| |
| // Version 0 TF is in abcdefg order |
| skcms_TransferFunction transferFn; |
| transferFn.a = *(((const float*) data) + 0); |
| transferFn.b = *(((const float*) data) + 1); |
| transferFn.c = *(((const float*) data) + 2); |
| transferFn.d = *(((const float*) data) + 3); |
| transferFn.e = *(((const float*) data) + 4); |
| transferFn.f = *(((const float*) data) + 5); |
| transferFn.g = *(((const float*) data) + 6); |
| data = SkTAddOffset<const void>(data, 7 * sizeof(float)); |
| |
| // Version 0 matrix is row-major 3x4 |
| skcms_Matrix3x3 toXYZ; |
| memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float)); |
| memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float)); |
| memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float)); |
| return SkColorSpace::MakeRGB(transferFn, toXYZ); |
| } |
| default: |
| return nullptr; |
| } |
| } else { |
| return nullptr; |
| } |
| } |
| |
| bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) { |
| if (x == y) { |
| return true; |
| } |
| |
| if (!x || !y) { |
| return false; |
| } |
| |
| if (x->hash() == y->hash()) { |
| for (int i = 0; i < 7; i++) { |
| SkASSERT(x-> fTransferFn[i] == y-> fTransferFn[i] && "Hash collsion"); |
| } |
| for (int i = 0; i < 9; i++) { |
| SkASSERT(x->fToXYZD50_3x3[i] == y->fToXYZD50_3x3[i] && "Hash collsion"); |
| } |
| return true; |
| } |
| return false; |
| } |