From 6f67fc29eddfb6f415f7e6a86197db5d8c8539ed Mon Sep 17 00:00:00 2001 From: Matt Sarett Date: Thu, 26 Jan 2017 13:10:49 -0500 Subject: [PATCH] Add SkICC::rawTransferFnData() BUG=skia: Change-Id: I912b044a0091a4d396c954d1ad1d84f5f8d50f56 Reviewed-on: https://skia-review.googlesource.com/7366 Reviewed-by: Mike Reed Commit-Queue: Matt Sarett --- gm/gradients.cpp | 8 +- include/core/SkICC.h | 51 +++++++++-- src/core/SkColorSpaceXformPriv.h | 1 + src/core/SkICC.cpp | 96 ++++++++++++++++++++ tests/ICCTest.cpp | 151 +++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+), 11 deletions(-) diff --git a/gm/gradients.cpp b/gm/gradients.cpp index 8ae1dd7901..b909b50e1d 100644 --- a/gm/gradients.cpp +++ b/gm/gradients.cpp @@ -170,7 +170,7 @@ constexpr GradMaker gGradMakers4f[] ={ class GradientsGM : public GM { public: GradientsGM(bool dither) : fDither(dither) { - this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD)); + //this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD)); } protected: @@ -185,10 +185,10 @@ protected: SkPoint pts[2] = { { 0, 0 }, - { SkIntToScalar(100), SkIntToScalar(100) } + { SkIntToScalar(500), SkIntToScalar(500) } }; SkShader::TileMode tm = SkShader::kClamp_TileMode; - SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) }; + SkRect r = { 0, 0, SkIntToScalar(500), SkIntToScalar(500) }; SkPaint paint; paint.setAntiAlias(true); paint.setDither(fDither); @@ -207,8 +207,10 @@ protected: paint.setShader(gGradMakers[j](pts, gGradData[i], tm, scale)); canvas->drawRect(r, paint); canvas->translate(0, SkIntToScalar(120)); + break; } canvas->restore(); + break; canvas->translate(SkIntToScalar(120), 0); } } diff --git a/include/core/SkICC.h b/include/core/SkICC.h index 3780498df9..04fa1dafab 100644 --- a/include/core/SkICC.h +++ b/include/core/SkICC.h @@ -8,6 +8,7 @@ #ifndef SkICC_DEFINED #define SkICC_DEFINED +#include "SkData.h" #include "SkRefCnt.h" struct SkColorSpaceTransferFn; @@ -48,16 +49,48 @@ public: bool isNumericalTransferFn(SkColorSpaceTransferFn* fn) const; /** - * If the transfer function can be approximated as coefficients to the standard - * equation, returns true and sets |fn| to the proper values. + * Please do not call this unless isNumericalTransferFn() has been called and it + * fails. SkColorSpaceTransferFn is the preferred representation. * - * 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 is not increasing with - * end points at zero and one. - * (3) The ICC data is too complex to isolate a single transfer function. + * 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. + * + * 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. */ - bool approximateNumericalTransferFn(SkColorSpaceTransferFn* fn) const; + 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); + } + + sk_sp fStorage; + }; + bool rawTransferFnData(Tables* tables) const; /** * Write an ICC profile with transfer function |fn| and gamut |toXYZD50|. @@ -68,6 +101,8 @@ private: SkICC(sk_sp colorSpace); sk_sp fColorSpace; + + friend class ICCTest; }; #endif diff --git a/src/core/SkColorSpaceXformPriv.h b/src/core/SkColorSpaceXformPriv.h index 5a2bb97edc..1f55891c07 100644 --- a/src/core/SkColorSpaceXformPriv.h +++ b/src/core/SkColorSpaceXformPriv.h @@ -9,6 +9,7 @@ #define SkColorSpaceXformPriv_DEFINED #include "SkColorSpace_Base.h" +#include "SkColorSpaceXform.h" #include "SkHalf.h" #include "SkSRGB.h" diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp index 6b09f29dae..40a733f872 100644 --- a/src/core/SkICC.cpp +++ b/src/core/SkICC.cpp @@ -9,6 +9,7 @@ #include "SkColorSpace_Base.h" #include "SkColorSpace_XYZ.h" #include "SkColorSpacePriv.h" +#include "SkColorSpaceXformPriv.h" #include "SkEndian.h" #include "SkFixed.h" #include "SkICC.h" @@ -41,6 +42,101 @@ bool SkICC::isNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const { return as_CSB(fColorSpace)->onIsNumericalTransferFn(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 (SkColorSpace_Base::Type::kA2B == as_CSB(fColorSpace)->type()) { + return false; + } + 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->data(0) == gammas->data(1) && gammas->data(0) == gammas->data(2)) { + 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; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// // Google Skia (UTF-16) diff --git a/tests/ICCTest.cpp b/tests/ICCTest.cpp index 4bc296d140..794483c682 100644 --- a/tests/ICCTest.cpp +++ b/tests/ICCTest.cpp @@ -8,6 +8,7 @@ #include "Resources.h" #include "SkColorSpace.h" #include "SkColorSpacePriv.h" +#include "SkColorSpace_XYZ.h" #include "SkData.h" #include "SkICC.h" #include "SkMatrix44.h" @@ -138,3 +139,153 @@ DEF_TEST(ICC_WriteICC, r) { test_write_icc(r, srgbFn, srgbMatrix, SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named).get(), false); } + +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::MakeNamed(SkColorSpace::kSRGB_Named)); + test_raw_transfer_fn(r, srgb.get()); + + sk_sp adobe = + ICCTest::MakeICC(SkColorSpace::MakeNamed(SkColorSpace::kAdobeRGB_Named)); + test_raw_transfer_fn(r, adobe.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()); +}