123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- /*
- * 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 (!is_valid_transfer_fn(transferFn)) {
- 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 {
- this->transferFn(&coeffs->g);
- return true;
- }
- 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;
- }
|