diff --git a/include/core/SkICC.h b/include/core/SkICC.h index 8c5ec566b7..9d2ada347e 100644 --- a/include/core/SkICC.h +++ b/include/core/SkICC.h @@ -12,98 +12,26 @@ #include "SkRefCnt.h" struct SkColorSpaceTransferFn; -class SkColorSpace; -class SkData; -class SkMatrix44; -class SK_API SkICC : public SkRefCnt { -public: +SK_API sk_sp SkWriteICCProfile(const SkColorSpaceTransferFn&, const float toXYZD50[9]); - /** - * Parse an ICC profile. - * - * Returns nullptr if the data is not a valid ICC profile or if the profile - * input space is not RGB. - */ - static sk_sp Make(const void*, size_t); +namespace SkICC { + static inline sk_sp WriteToICC(const SkColorSpaceTransferFn& fn, + const SkMatrix44& toXYZD50) { + if (toXYZD50.get(3,0) == 0 && toXYZD50.get(3,1) == 0 && toXYZD50.get(3,2) == 0 && + toXYZD50.get(3,3) == 1 && + toXYZD50.get(0,3) == 0 && toXYZD50.get(1,3) == 0 && toXYZD50.get(2,3) == 0) { - /** - * If the gamut can be represented as transformation into XYZ D50, returns - * true and sets the proper values in |toXYZD50|. - * - * If not, returns false. This indicates that the ICC data is too complex - * to isolate a simple gamut transformation. - */ - bool toXYZD50(SkMatrix44* toXYZD50) const; + float m33[9]; + for (int r = 0; r < 3; r++) + for (int c = 0; c < 3; c++) { + m33[3*r+c] = toXYZD50.get(r,c); + } + return SkWriteICCProfile(fn, m33); - /** - * If the transfer function can be represented as coefficients to the standard - * equation, returns true and sets |fn| to the proper values. - * - * If not, returns false. This indicates one of the following: - * (1) The R, G, and B transfer functions are not the same. - * (2) The transfer function is represented as a table that we have not managed - * to match to a standard curve. - * (3) The ICC data is too complex to isolate a single transfer function. - */ - bool isNumericalTransferFn(SkColorSpaceTransferFn* fn) const; - - /** - * Please do not call this unless isNumericalTransferFn() has been called and it - * fails. SkColorSpaceTransferFn is the preferred representation. - * - * If it is not possible to represent the R, G, and B transfer functions numerically - * and it is still necessary to get the transfer function, this will return the - * transfer functions as three tables (R, G, and B). - * - * If possible, this will return tables of the same length as they were specified in - * the ICC profile. This means that the lengths of the three tables are not - * guaranteed to be the same. If the ICC representation was not a table, the length - * will be chosen arbitrarily. - * - * The lengths of the tables are all guaranteed to be at least 2. Entries in the - * tables are guaranteed to be in [0, 1]. - * - * This API may be deleted in favor of a numerical approximation of the raw data. - * - * This function may fail, indicating that the ICC profile does not have transfer - * functions. - */ - struct Channel { - // Byte offset of the start of the table in |fStorage| - size_t fOffset; - int fCount; - }; - struct Tables { - Channel fRed; - Channel fGreen; - Channel fBlue; - - const float* red() { - return (const float*) (fStorage->bytes() + fRed.fOffset); - } - const float* green() { - return (const float*) (fStorage->bytes() + fGreen.fOffset); - } - const float* blue() { - return (const float*) (fStorage->bytes() + fBlue.fOffset); } + return nullptr; + } +} - sk_sp fStorage; - }; - bool rawTransferFnData(Tables* tables) const; - - /** - * Write an ICC profile with transfer function |fn| and gamut |toXYZD50|. - */ - static sk_sp WriteToICC(const SkColorSpaceTransferFn& fn, const SkMatrix44& toXYZD50); - -private: - SkICC(sk_sp colorSpace); - - sk_sp fColorSpace; - - friend class ICCTest; -}; - -#endif +#endif//SkICC_DEFINED diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp index 3a77dfc200..aa9c45e50b 100644 --- a/src/core/SkICC.cpp +++ b/src/core/SkICC.cpp @@ -7,134 +7,13 @@ #include "SkAutoMalloc.h" #include "SkColorSpacePriv.h" -#include "SkColorSpaceXformPriv.h" -#include "SkColorSpace_XYZ.h" #include "SkEndian.h" #include "SkFixed.h" #include "SkICC.h" #include "SkICCPriv.h" #include "SkMD5.h" -#include "SkString.h" #include "SkUtils.h" -SkICC::SkICC(sk_sp colorSpace) - : fColorSpace(std::move(colorSpace)) -{} - -sk_sp SkICC::Make(const void* ptr, size_t len) { - sk_sp colorSpace = SkColorSpace::MakeICC(ptr, len); - if (!colorSpace) { - return nullptr; - } - - return sk_sp(new SkICC(std::move(colorSpace))); -} - -bool SkICC::toXYZD50(SkMatrix44* toXYZD50) const { - return fColorSpace->toXYZD50(toXYZD50); -} - -bool SkICC::isNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const { - return fColorSpace->isNumericalTransferFn(coeffs); -} - -static const int kDefaultTableSize = 512; // Arbitrary - -void fn_to_table(float* tablePtr, const SkColorSpaceTransferFn& fn) { - // Y = (aX + b)^g + e for X >= d - // Y = cX + f otherwise - for (int i = 0; i < kDefaultTableSize; i++) { - float x = ((float) i) / ((float) (kDefaultTableSize - 1)); - if (x >= fn.fD) { - tablePtr[i] = clamp_0_1(powf(fn.fA * x + fn.fB, fn.fG) + fn.fE); - } else { - tablePtr[i] = clamp_0_1(fn.fC * x + fn.fF); - } - } -} - -void copy_to_table(float* tablePtr, const SkGammas* gammas, int index) { - SkASSERT(gammas->isTable(index)); - const float* ptr = gammas->table(index); - const size_t bytes = gammas->tableSize(index) * sizeof(float); - memcpy(tablePtr, ptr, bytes); -} - -bool SkICC::rawTransferFnData(Tables* tables) const { - if (!fColorSpace->toXYZD50()) { - return false; // Can't even dream of handling A2B here... - } - SkColorSpace_XYZ* colorSpace = (SkColorSpace_XYZ*) fColorSpace.get(); - - SkColorSpaceTransferFn fn; - if (this->isNumericalTransferFn(&fn)) { - tables->fStorage = SkData::MakeUninitialized(kDefaultTableSize * sizeof(float)); - fn_to_table((float*) tables->fStorage->writable_data(), fn); - tables->fRed.fOffset = tables->fGreen.fOffset = tables->fBlue.fOffset = 0; - tables->fRed.fCount = tables->fGreen.fCount = tables->fBlue.fCount = kDefaultTableSize; - return true; - } - - const SkGammas* gammas = colorSpace->gammas(); - SkASSERT(gammas); - if (gammas->allChannelsSame()) { - SkASSERT(gammas->isTable(0)); - tables->fStorage = SkData::MakeUninitialized(gammas->tableSize(0) * sizeof(float)); - copy_to_table((float*) tables->fStorage->writable_data(), gammas, 0); - tables->fRed.fOffset = tables->fGreen.fOffset = tables->fBlue.fOffset = 0; - tables->fRed.fCount = tables->fGreen.fCount = tables->fBlue.fCount = gammas->tableSize(0); - return true; - } - - // Determine the storage size. - size_t storageSize = 0; - for (int i = 0; i < 3; i++) { - if (gammas->isTable(i)) { - storageSize += gammas->tableSize(i) * sizeof(float); - } else { - storageSize += kDefaultTableSize * sizeof(float); - } - } - - // Fill in the tables. - tables->fStorage = SkData::MakeUninitialized(storageSize); - float* ptr = (float*) tables->fStorage->writable_data(); - size_t offset = 0; - Channel rgb[3]; - for (int i = 0; i < 3; i++) { - if (gammas->isTable(i)) { - copy_to_table(ptr, gammas, i); - rgb[i].fOffset = offset; - rgb[i].fCount = gammas->tableSize(i); - offset += rgb[i].fCount * sizeof(float); - ptr += rgb[i].fCount; - continue; - } - - if (gammas->isNamed(i)) { - SkAssertResult(named_to_parametric(&fn, gammas->data(i).fNamed)); - } else if (gammas->isValue(i)) { - value_to_parametric(&fn, gammas->data(i).fValue); - } else { - SkASSERT(gammas->isParametric(i)); - fn = gammas->params(i); - } - - fn_to_table(ptr, fn); - rgb[i].fOffset = offset; - rgb[i].fCount = kDefaultTableSize; - offset += kDefaultTableSize * sizeof(float); - ptr += kDefaultTableSize; - } - - tables->fRed = rgb[0]; - tables->fGreen = rgb[1]; - tables->fBlue = rgb[2]; - return true; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - static constexpr char kDescriptionTagBodyPrefix[12] = { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'}; @@ -165,9 +44,14 @@ static constexpr uint32_t kWhitePointTag[5] { // Google Inc. 2016 (UTF-16) static constexpr uint8_t kCopyrightTagBody[] = { - 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x67, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, - 0x49, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, - 0x00, 0x36, + 0x00, 0x47, 0x00, 0x6f, + 0x00, 0x6f, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x65, + 0x00, 0x20, 0x00, 0x49, + 0x00, 0x6e, 0x00, 0x63, + 0x00, 0x2e, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x30, + 0x00, 0x31, 0x00, 0x36, }; static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes."); static constexpr uint32_t kCopyrightTagHeader[7] { @@ -288,12 +172,12 @@ static SkFixed float_round_to_fixed(float x) { return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5)); } -static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int col) { +static void write_xyz_tag(uint32_t* ptr, const float toXYZD50[9], int col) { ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); ptr[1] = 0; - ptr[2] = SkEndian_SwapBE32(float_round_to_fixed(toXYZ.getFloat(0, col))); - ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(toXYZ.getFloat(1, col))); - ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(toXYZ.getFloat(2, col))); + ptr[2] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50[0*3 + col])); + ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50[1*3 + col])); + ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50[2*3 + col])); } static void write_trc_tag(uint32_t* ptr, const SkColorSpaceTransferFn& fn) { @@ -309,12 +193,6 @@ static void write_trc_tag(uint32_t* ptr, const SkColorSpaceTransferFn& fn) { ptr[9] = SkEndian_SwapBE32(float_round_to_fixed(fn.fF)); } -static bool is_3x3(const SkMatrix44& toXYZD50) { - return 0.0f == toXYZD50.get(3, 0) && 0.0f == toXYZD50.get(3, 1) && 0.0f == toXYZD50.get(3, 2) && - 0.0f == toXYZD50.get(0, 3) && 0.0f == toXYZD50.get(1, 3) && 0.0f == toXYZD50.get(2, 3) && - 1.0f == toXYZD50.get(3, 3); -} - static bool nearly_equal(float x, float y) { // A note on why I chose this tolerance: transfer_fn_almost_equal() uses a // tolerance of 0.001f, which doesn't seem to be enough to distinguish @@ -338,28 +216,18 @@ static bool nearly_equal(const SkColorSpaceTransferFn& u, && nearly_equal(u.fF, v.fF); } -static bool nearly_equal(const SkMatrix44& toXYZD50, const float standard[9]) { - return nearly_equal(toXYZD50.getFloat(0, 0), standard[0]) - && nearly_equal(toXYZD50.getFloat(0, 1), standard[1]) - && nearly_equal(toXYZD50.getFloat(0, 2), standard[2]) - && nearly_equal(toXYZD50.getFloat(1, 0), standard[3]) - && nearly_equal(toXYZD50.getFloat(1, 1), standard[4]) - && nearly_equal(toXYZD50.getFloat(1, 2), standard[5]) - && nearly_equal(toXYZD50.getFloat(2, 0), standard[6]) - && nearly_equal(toXYZD50.getFloat(2, 1), standard[7]) - && nearly_equal(toXYZD50.getFloat(2, 2), standard[8]) - && nearly_equal(toXYZD50.getFloat(0, 3), 0.0f) - && nearly_equal(toXYZD50.getFloat(1, 3), 0.0f) - && nearly_equal(toXYZD50.getFloat(2, 3), 0.0f) - && nearly_equal(toXYZD50.getFloat(3, 0), 0.0f) - && nearly_equal(toXYZD50.getFloat(3, 1), 0.0f) - && nearly_equal(toXYZD50.getFloat(3, 2), 0.0f) - && nearly_equal(toXYZD50.getFloat(3, 3), 1.0f); +static bool nearly_equal(const float u[9], const float v[9]) { + for (int i = 0; i < 9; i++) { + if (!nearly_equal(u[i], v[i])) { + return false; + } + } + return true; } // Return nullptr if the color profile doen't have a special name. const char* get_color_profile_description(const SkColorSpaceTransferFn& fn, - const SkMatrix44& toXYZD50) { + const float toXYZD50[9]) { bool srgb_xfer = nearly_equal(fn, gSRGB_TransferFn); bool srgb_gamut = nearly_equal(toXYZD50, gSRGB_toXYZD50); if (srgb_xfer && srgb_gamut) { @@ -400,7 +268,7 @@ const char* get_color_profile_description(const SkColorSpaceTransferFn& fn, static void get_color_profile_tag(char dst[kICCDescriptionTagSize], const SkColorSpaceTransferFn& fn, - const SkMatrix44& toXYZD50) { + const float toXYZD50[9]) { SkASSERT(dst); if (const char* description = get_color_profile_description(fn, toXYZD50)) { SkASSERT(strlen(description) < kICCDescriptionTagSize); @@ -410,12 +278,7 @@ static void get_color_profile_tag(char dst[kICCDescriptionTagSize], } else { strncpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix)); SkMD5 md5; - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 3; ++j) { - float value = toXYZD50.getFloat(i,j); - md5.write(&value, sizeof(value)); - } - } + md5.write(toXYZD50, 9*sizeof(float)); static_assert(sizeof(fn) == sizeof(float) * 7, "packed"); md5.write(&fn, sizeof(fn)); SkMD5::Digest digest; @@ -430,29 +293,8 @@ static void get_color_profile_tag(char dst[kICCDescriptionTagSize], } } -SkString SkICCGetColorProfileTag(const SkColorSpaceTransferFn& fn, - const SkMatrix44& toXYZD50) { - char tag[kICCDescriptionTagSize]; - get_color_profile_tag(tag, fn, toXYZD50); - size_t len = kICCDescriptionTagSize; - while (len > 0 && tag[len - 1] == '\0') { - --len; // tag is padded out with zeros - } - SkASSERT(len != 0); - return SkString(tag, len); -} - -// returns pointer just beyond where we just wrote. -static uint8_t* string_copy_ascii_to_utf16be(uint8_t* dst, const char* src, size_t count) { - while (count-- > 0) { - *dst++ = 0; - *dst++ = (uint8_t)(*src++); - } - return dst; -} - -sk_sp SkICC::WriteToICC(const SkColorSpaceTransferFn& fn, const SkMatrix44& toXYZD50) { - if (!is_3x3(toXYZD50) || !is_valid_transfer_fn(fn)) { +sk_sp SkWriteICCProfile(const SkColorSpaceTransferFn& fn, const float toXYZD50[9]) { + if (!is_valid_transfer_fn(fn)) { return nullptr; } @@ -473,7 +315,12 @@ sk_sp SkICC::WriteToICC(const SkColorSpaceTransferFn& fn, const SkMatrix { char colorProfileTag[kICCDescriptionTagSize]; get_color_profile_tag(colorProfileTag, fn, toXYZD50); - ptr = string_copy_ascii_to_utf16be(ptr, colorProfileTag, kICCDescriptionTagSize); + + // ASCII --> big-endian UTF-16. + for (size_t i = 0; i < kICCDescriptionTagSize; i++) { + *ptr++ = 0; + *ptr++ = colorProfileTag[i]; + } } // Write XYZ tags diff --git a/src/core/SkICCPriv.h b/src/core/SkICCPriv.h index a50d362a55..3d2b8965a9 100644 --- a/src/core/SkICCPriv.h +++ b/src/core/SkICCPriv.h @@ -51,11 +51,4 @@ enum ParaCurveType { kGABCDEF_ParaCurveType = 4, }; -/* - * Given fn and toXYZD50, generate a decription tag that either includes a hash - * of the function and gamut or is a special name. - * Exposed for unit testing and tools. - */ -SkString SkICCGetColorProfileTag(const SkColorSpaceTransferFn& fn, - const SkMatrix44& toXYZD50); #endif // SkICCPriv_DEFINED diff --git a/src/images/SkImageEncoderFns.h b/src/images/SkImageEncoderFns.h index 8637541720..d8d1c64bb8 100644 --- a/src/images/SkImageEncoderFns.h +++ b/src/images/SkImageEncoderFns.h @@ -427,8 +427,6 @@ static inline sk_sp icc_from_color_space(const SkImageInfo& info) { if (cs->isNumericalTransferFn(&fn) && cs->toXYZD50(&toXYZD50)) { return SkICC::WriteToICC(fn, toXYZD50); } - - // TODO: Should we support writing ICC profiles for additional color spaces? return nullptr; } diff --git a/tests/ICCTest.cpp b/tests/ICCTest.cpp index 34ed52984c..e88255b2cd 100644 --- a/tests/ICCTest.cpp +++ b/tests/ICCTest.cpp @@ -5,296 +5,69 @@ * found in the LICENSE file. */ -#include "Resources.h" -#include "SkColorSpace.h" -#include "SkColorSpacePriv.h" -#include "SkColorSpace_XYZ.h" -#include "SkData.h" -#include "SkICC.h" -#include "SkICCPriv.h" -#include "SkMatrix44.h" -#include "SkStream.h" -#include "Test.h" +#include "SkTypes.h" -static bool almost_equal(float a, float b) { - return SkTAbs(a - b) < 0.001f; -} +#if defined(SK_USE_SKCMS) -static inline void test_to_xyz_d50(skiatest::Reporter* r, SkICC* icc, bool shouldSucceed, - const float* reference) { - SkMatrix44 result(SkMatrix44::kUninitialized_Constructor); - REPORTER_ASSERT(r, shouldSucceed == icc->toXYZD50(&result)); - if (shouldSucceed) { - float resultVals[16]; - result.asColMajorf(resultVals); - for (int i = 0; i < 16; i++) { - REPORTER_ASSERT(r, almost_equal(resultVals[i], reference[i])); + #include "Resources.h" + #include "SkColorSpacePriv.h" + #include "SkICC.h" + #include "SkString.h" + #include "Test.h" + #include "skcms.h" + + DEF_TEST(WriteICCProfile, r) { + auto adobeRGB = SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut); + + struct { + SkColorSpaceTransferFn fn; + const float* toXYZD50; + const char* desc; + sk_sp want; + } tests[] = { + {g2Dot2_TransferFn, gAdobeRGB_toXYZD50, "AdobeRGB", adobeRGB}, + { gSRGB_TransferFn, gSRGB_toXYZD50, "sRGB", SkColorSpace::MakeSRGB()}, + }; + + for (auto test : tests) { + sk_sp profile = SkWriteICCProfile(test.fn, test.toXYZD50); + REPORTER_ASSERT(r, profile); + + skcms_ICCProfile parsed; + REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed)); + + sk_sp got = SkColorSpace::Make(parsed); + REPORTER_ASSERT(r, got); + REPORTER_ASSERT(r, SkColorSpace::Equals(got.get(), test.want.get())); + + skcms_ICCTag desc; + REPORTER_ASSERT(r, skcms_GetTagBySignature(&parsed, + SkSetFourByteTag('d','e','s','c'), + &desc)); + + // Rather than really carefully break down the 'desc' tag, + // just check our expected description is somewhere in there (as big-endian UTF-16). + uint8_t big_endian_utf16[16]; + for (size_t i = 0; i < strlen(test.desc); i++) { + big_endian_utf16[2*i+0] = 0; + big_endian_utf16[2*i+1] = test.desc[i]; + } + + SkString haystack((const char*)desc.buf, desc.size), + needle ((const char*)big_endian_utf16, 2*strlen(test.desc)); + REPORTER_ASSERT(r, haystack.contains(needle.c_str())); } } -} -DEF_TEST(ICC_ToXYZD50, r) { - const float z30Reference[16] = { - 0.59825f, 0.27103f, 0.00603f, 0.0f, 0.22243f, 0.67447f, 0.07368f, 0.0f, 0.14352f, 0.05449f, - 0.74519f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, - }; + DEF_TEST(AdobeRGB, r) { + if (sk_sp profile = GetResourceAsData("icc_profiles/AdobeRGB1998.icc")) { + skcms_ICCProfile parsed; + REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed)); - sk_sp data = GetResourceAsData("icc_profiles/HP_ZR30w.icc"); - sk_sp z30 = SkICC::Make(data->data(), data->size()); - test_to_xyz_d50(r, z30.get(), true, z30Reference); - - const float z32Reference[16] = { - 0.61583f, 0.28789f, 0.00513f, 0.0f, 0.20428f, 0.66972f, 0.06609f, 0.0f, 0.14409f, 0.04237f, - 0.75368f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, - }; - - data = GetResourceAsData("icc_profiles/HP_Z32x.icc"); - sk_sp z32 = SkICC::Make(data->data(), data->size()); - test_to_xyz_d50(r, z32.get(), true, z32Reference); - - data = GetResourceAsData("icc_profiles/upperLeft.icc"); - sk_sp upperLeft = SkICC::Make(data->data(), data->size()); - test_to_xyz_d50(r, upperLeft.get(), false, z32Reference); - - data = GetResourceAsData("icc_profiles/upperRight.icc"); - sk_sp upperRight = SkICC::Make(data->data(), data->size()); - test_to_xyz_d50(r, upperRight.get(), false, z32Reference); -} - -static inline void test_is_numerical_transfer_fn(skiatest::Reporter* r, SkICC* icc, - bool shouldSucceed, - const SkColorSpaceTransferFn& reference) { - SkColorSpaceTransferFn result; - REPORTER_ASSERT(r, shouldSucceed == icc->isNumericalTransferFn(&result)); - if (shouldSucceed) { - REPORTER_ASSERT(r, 0 == memcmp(&result, &reference, sizeof(SkColorSpaceTransferFn))); - } -} - -DEF_TEST(ICC_IsNumericalTransferFn, r) { - SkColorSpaceTransferFn referenceFn; - referenceFn.fA = 1.0f; - referenceFn.fB = 0.0f; - referenceFn.fC = 0.0f; - referenceFn.fD = 0.0f; - referenceFn.fE = 0.0f; - referenceFn.fF = 0.0f; - referenceFn.fG = 2.2f; - - sk_sp data = GetResourceAsData("icc_profiles/HP_ZR30w.icc"); - sk_sp z30 = SkICC::Make(data->data(), data->size()); - test_is_numerical_transfer_fn(r, z30.get(), true, referenceFn); - - data = GetResourceAsData("icc_profiles/HP_Z32x.icc"); - sk_sp z32 = SkICC::Make(data->data(), data->size()); - test_is_numerical_transfer_fn(r, z32.get(), true, referenceFn); - - data = GetResourceAsData("icc_profiles/upperLeft.icc"); - sk_sp upperLeft = SkICC::Make(data->data(), data->size()); - test_is_numerical_transfer_fn(r, upperLeft.get(), false, referenceFn); - - data = GetResourceAsData("icc_profiles/upperRight.icc"); - sk_sp upperRight = SkICC::Make(data->data(), data->size()); - test_is_numerical_transfer_fn(r, upperRight.get(), false, referenceFn); -} - -DEF_TEST(ICC_Adobe, r) { - // Test that the color spaces produced by our procedural Adobe factory, and the official - // Adobe ICC profile match exactly. - sk_sp data = GetResourceAsData("icc_profiles/AdobeRGB1998.icc"); - sk_sp fromIcc = SkColorSpace::MakeICC(data->data(), data->size()); - sk_sp procedural = SkColorSpace::MakeRGB(g2Dot2_TransferFn, - SkColorSpace::kAdobeRGB_Gamut); - REPORTER_ASSERT(r, SkColorSpace::Equals(fromIcc.get(), procedural.get())); -} - -static inline void test_write_icc(skiatest::Reporter* r, const SkColorSpaceTransferFn& fn, - const SkMatrix44& toXYZD50, bool writeToFile) { - sk_sp profile = SkICC::WriteToICC(fn, toXYZD50); - if (writeToFile) { - SkFILEWStream stream("out.icc"); - stream.write(profile->data(), profile->size()); + auto got = SkColorSpace::Make(parsed); + auto want = SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut); + REPORTER_ASSERT(r, SkColorSpace::Equals(got.get(), want.get())); + } } - sk_sp colorSpace = SkColorSpace::MakeICC(profile->data(), profile->size()); - sk_sp reference = SkColorSpace::MakeRGB(fn, toXYZD50); - REPORTER_ASSERT(r, SkColorSpace::Equals(reference.get(), colorSpace.get())); -} - -DEF_TEST(ICC_WriteICC, r) { - SkColorSpaceTransferFn adobeFn; - adobeFn.fA = 1.0f; - adobeFn.fB = 0.0f; - adobeFn.fC = 0.0f; - adobeFn.fD = 0.0f; - adobeFn.fE = 0.0f; - adobeFn.fF = 0.0f; - adobeFn.fG = 2.2f; - SkMatrix44 adobeMatrix(SkMatrix44::kUninitialized_Constructor); - adobeMatrix.set3x3RowMajorf(gAdobeRGB_toXYZD50); - test_write_icc(r, adobeFn, adobeMatrix, false); - - SkColorSpaceTransferFn srgbFn; - srgbFn.fA = 1.0f / 1.055f; - srgbFn.fB = 0.055f / 1.055f; - srgbFn.fC = 1.0f / 12.92f; - srgbFn.fD = 0.04045f; - srgbFn.fE = 0.0f; - srgbFn.fF = 0.0f; - srgbFn.fG = 2.4f; - SkMatrix44 srgbMatrix(SkMatrix44::kUninitialized_Constructor); - srgbMatrix.set3x3RowMajorf(gSRGB_toXYZD50); - test_write_icc(r, srgbFn, srgbMatrix, false); - - SkString adobeTag = SkICCGetColorProfileTag(adobeFn, adobeMatrix); - SkString srgbTag = SkICCGetColorProfileTag(srgbFn, srgbMatrix); - REPORTER_ASSERT(r, adobeTag != srgbTag); - REPORTER_ASSERT(r, srgbTag.equals("sRGB")); - REPORTER_ASSERT(r, adobeTag.equals("AdobeRGB")); -} - -static inline void test_raw_transfer_fn(skiatest::Reporter* r, SkICC* icc) { - SkICC::Tables tables; - bool result = icc->rawTransferFnData(&tables); - REPORTER_ASSERT(r, result); - - REPORTER_ASSERT(r, 0.0f == tables.red()[0]); - REPORTER_ASSERT(r, 0.0f == tables.green()[0]); - REPORTER_ASSERT(r, 0.0f == tables.blue()[0]); - REPORTER_ASSERT(r, 1.0f == tables.red()[tables.fRed.fCount - 1]); - REPORTER_ASSERT(r, 1.0f == tables.green()[tables.fGreen.fCount - 1]); - REPORTER_ASSERT(r, 1.0f == tables.blue()[tables.fBlue.fCount - 1]); -} - -class ICCTest { -public: - static sk_sp MakeICC(sk_sp space) { - return sk_sp(new SkICC(std::move(space))); - } - static sk_sp MakeICC(sk_sp gammas) { - return MakeICC(sk_sp(new SkColorSpace_XYZ( - kNonStandard_SkGammaNamed, std::move(gammas), - SkMatrix44(SkMatrix44::kIdentity_Constructor), nullptr))); - } -}; - -DEF_TEST(ICC_RawTransferFns, r) { - sk_sp srgb = ICCTest::MakeICC(SkColorSpace::MakeSRGB()); - test_raw_transfer_fn(r, srgb.get()); - - // Lookup-table based gamma curves - constexpr size_t tableSize = 10; - void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize); - sk_sp gammas = sk_sp(new (memory) SkGammas(3)); - for (int i = 0; i < 3; ++i) { - gammas->fType[i] = SkGammas::Type::kTable_Type; - gammas->fData[i].fTable.fSize = tableSize; - gammas->fData[i].fTable.fOffset = 0; - } - - float* table = SkTAddOffset(memory, sizeof(SkGammas)); - table[0] = 0.00f; - table[1] = 0.05f; - table[2] = 0.10f; - table[3] = 0.15f; - table[4] = 0.25f; - table[5] = 0.35f; - table[6] = 0.45f; - table[7] = 0.60f; - table[8] = 0.75f; - table[9] = 1.00f; - sk_sp tbl = ICCTest::MakeICC(gammas); - test_raw_transfer_fn(r, tbl.get()); - - // Parametric gamma curves - memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn)); - gammas = sk_sp(new (memory) SkGammas(3)); - for (int i = 0; i < 3; ++i) { - gammas->fType[i] = SkGammas::Type::kParam_Type; - gammas->fData[i].fParamOffset = 0; - } - - SkColorSpaceTransferFn* params = SkTAddOffset - (memory, sizeof(SkGammas)); - - // Interval. - params->fD = 0.04045f; - - // First equation: - params->fC = 1.0f / 12.92f; - params->fF = 0.0f; - - // Second equation: - // Note that the function is continuous (it's actually sRGB). - params->fA = 1.0f / 1.055f; - params->fB = 0.055f / 1.055f; - params->fE = 0.0f; - params->fG = 2.4f; - sk_sp param = ICCTest::MakeICC(gammas); - test_raw_transfer_fn(r, param.get()); - - // Exponential gamma curves - gammas = sk_sp(new SkGammas(3)); - for (int i = 0; i < 3; ++i) { - gammas->fType[i] = SkGammas::Type::kValue_Type; - gammas->fData[i].fValue = 1.4f; - } - sk_sp exp = ICCTest::MakeICC(gammas); - test_raw_transfer_fn(r, exp.get()); - - gammas = sk_sp(new SkGammas(3)); - gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type; - gammas->fData[0].fNamed = kSRGB_SkGammaNamed; - gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed; - gammas->fData[2].fNamed = kLinear_SkGammaNamed; - sk_sp named = ICCTest::MakeICC(gammas); - test_raw_transfer_fn(r, named.get()); - - memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize + - sizeof(SkColorSpaceTransferFn)); - gammas = sk_sp(new (memory) SkGammas(3)); - - table = SkTAddOffset(memory, sizeof(SkGammas)); - table[0] = 0.00f; - table[1] = 0.15f; - table[2] = 0.20f; - table[3] = 0.25f; - table[4] = 0.35f; - table[5] = 0.45f; - table[6] = 0.55f; - table[7] = 0.70f; - table[8] = 0.85f; - table[9] = 1.00f; - - params = SkTAddOffset(memory, - sizeof(SkGammas) + sizeof(float) * tableSize); - params->fA = 1.0f / 1.055f; - params->fB = 0.055f / 1.055f; - params->fC = 1.0f / 12.92f; - params->fD = 0.04045f; - params->fE = 0.0f; - params->fF = 0.0f; - params->fG = 2.4f; - - gammas->fType[0] = SkGammas::Type::kValue_Type; - gammas->fData[0].fValue = 1.2f; - - gammas->fType[1] = SkGammas::Type::kTable_Type; - gammas->fData[1].fTable.fSize = tableSize; - gammas->fData[1].fTable.fOffset = 0; - - gammas->fType[2] = SkGammas::Type::kParam_Type; - gammas->fData[2].fParamOffset = sizeof(float) * tableSize; - sk_sp nonstd = ICCTest::MakeICC(gammas); - test_raw_transfer_fn(r, nonstd.get()); - - // Reverse order of table and exponent - gammas->fType[1] = SkGammas::Type::kValue_Type; - gammas->fData[1].fValue = 1.2f; - - gammas->fType[0] = SkGammas::Type::kTable_Type; - gammas->fData[0].fTable.fSize = tableSize; - gammas->fData[0].fTable.fOffset = 0; - sk_sp nonstd2 = ICCTest::MakeICC(gammas); - test_raw_transfer_fn(r, nonstd2.get()); -} +#endif