}
}
-static void build_table_linear_to_gamma(uint8_t* outTable, int outTableSize, float exponent) {
+static void build_table_linear_to_gamma(uint8_t* outTable, float exponent) {
float toGammaExp = 1.0f / exponent;
- for (int i = 0; i < outTableSize; i++) {
- float x = ((float) i) * (1.0f / ((float) (outTableSize - 1)));
+ for (int i = 0; i < SkDefaultXform::kDstGammaTableSize; i++) {
+ float x = ((float) i) * (1.0f / ((float) (SkDefaultXform::kDstGammaTableSize - 1)));
outTable[i] = clamp_normalized_float_to_byte(powf(x, toGammaExp));
}
}
// Inverse table lookup. Ex: what index corresponds to the input value? This will
// have strange results when the table is non-increasing. But any sane gamma
// function will be increasing.
-static float inverse_interp_lut(float input, float* table, int tableSize) {
+static float inverse_interp_lut(float input, const float* table, int tableSize) {
if (input <= table[0]) {
return table[0];
} else if (input >= table[tableSize - 1]) {
return 0.0f;
}
-static void build_table_linear_to_gamma(uint8_t* outTable, int outTableSize, float* inTable,
+static void build_table_linear_to_gamma(uint8_t* outTable, const float* inTable,
int inTableSize) {
- for (int i = 0; i < outTableSize; i++) {
- float x = ((float) i) * (1.0f / ((float) (outTableSize - 1)));
+ for (int i = 0; i < SkDefaultXform::kDstGammaTableSize; i++) {
+ float x = ((float) i) * (1.0f / ((float) (SkDefaultXform::kDstGammaTableSize - 1)));
float y = inverse_interp_lut(x, inTable, inTableSize);
outTable[i] = clamp_normalized_float_to_byte(y);
}
return (powf(x - c, 1.0f / g) - b) / a;
}
-static void build_table_linear_to_gamma(uint8_t* outTable, int outTableSize, float g, float a,
+static void build_table_linear_to_gamma(uint8_t* outTable, float g, float a,
float b, float c, float d, float e, float f) {
- for (int i = 0; i < outTableSize; i++) {
- float x = ((float) i) * (1.0f / ((float) (outTableSize - 1)));
+ for (int i = 0; i < SkDefaultXform::kDstGammaTableSize; i++) {
+ float x = ((float) i) * (1.0f / ((float) (SkDefaultXform::kDstGammaTableSize - 1)));
float y = inverse_parametric(x, g, a, b, c, d, e, f);
outTable[i] = clamp_normalized_float_to_byte(y);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
+template <typename T>
+struct GammaFns {
+ const T* fSRGBTable;
+ const T* f2Dot2Table;
+
+ void (*fBuildFromValue)(T*, float);
+ void (*fBuildFromTable)(T*, const float*, int);
+ void (*fBuildFromParam)(T*, float, float, float, float, float, float, float);
+};
+
+static const GammaFns<float> kToLinear {
+ sk_linear_from_srgb,
+ sk_linear_from_2dot2,
+ &build_table_linear_from_gamma,
+ &build_table_linear_from_gamma,
+ &build_table_linear_from_gamma,
+};
+
+static const GammaFns<uint8_t> kFromLinear {
+ linear_to_srgb,
+ linear_to_2dot2,
+ &build_table_linear_to_gamma,
+ &build_table_linear_to_gamma,
+ &build_table_linear_to_gamma,
+};
+
+// Build tables to transform src gamma to linear.
+template <typename T>
+static void build_gamma_tables(const T* outGammaTables[3], T* gammaTableStorage, int gammaTableSize,
+ const sk_sp<SkColorSpace>& space, const GammaFns<T>& fns) {
+ switch (space->gammaNamed()) {
+ case SkColorSpace::kSRGB_GammaNamed:
+ outGammaTables[0] = outGammaTables[1] = outGammaTables[2] = fns.fSRGBTable;
+ break;
+ case SkColorSpace::k2Dot2Curve_GammaNamed:
+ outGammaTables[0] = outGammaTables[1] = outGammaTables[2] = fns.f2Dot2Table;
+ break;
+ case SkColorSpace::kLinear_GammaNamed:
+ (*fns.fBuildFromValue)(gammaTableStorage, 1.0f);
+ outGammaTables[0] = outGammaTables[1] = outGammaTables[2] = gammaTableStorage;
+ break;
+ default: {
+ const SkGammas* gammas = as_CSB(space)->gammas();
+ SkASSERT(gammas);
+
+ for (int i = 0; i < 3; i++) {
+ if (i > 0) {
+ // Check if this curve matches the first curve. In this case, we can
+ // share the same table pointer. This should almost always be true.
+ // I've never seen a profile where all three gamma curves didn't match.
+ // But it is possible that they won't.
+ if (0 == memcmp(&gammas->data(0), &gammas->data(i), sizeof(SkGammas::Data))) {
+ outGammaTables[i] = outGammaTables[0];
+ continue;
+ }
+ }
+
+ if (gammas->isNamed(i)) {
+ switch (gammas->data(i).fNamed) {
+ case SkColorSpace::kSRGB_GammaNamed:
+ outGammaTables[i] = fns.fSRGBTable;
+ break;
+ case SkColorSpace::k2Dot2Curve_GammaNamed:
+ outGammaTables[i] = fns.f2Dot2Table;
+ break;
+ case SkColorSpace::kLinear_GammaNamed:
+ (*fns.fBuildFromValue)(&gammaTableStorage[i * gammaTableSize], 1.0f);
+ outGammaTables[i] = &gammaTableStorage[i * gammaTableSize];
+ break;
+ default:
+ SkASSERT(false);
+ break;
+ }
+ } else if (gammas->isValue(i)) {
+ (*fns.fBuildFromValue)(&gammaTableStorage[i * gammaTableSize],
+ gammas->data(i).fValue);
+ outGammaTables[i] = &gammaTableStorage[i * gammaTableSize];
+ } else if (gammas->isTable(i)) {
+ (*fns.fBuildFromTable)(&gammaTableStorage[i * gammaTableSize], gammas->table(i),
+ gammas->data(i).fTable.fSize);
+ outGammaTables[i] = &gammaTableStorage[i * gammaTableSize];
+ } else {
+ SkASSERT(gammas->isParametric(i));
+ const SkGammas::Params& params = gammas->params(i);
+ (*fns.fBuildFromParam)(&gammaTableStorage[i * gammaTableSize], params.fG,
+ params.fA, params.fB, params.fC, params.fD, params.fE,
+ params.fF);
+ outGammaTables[i] = &gammaTableStorage[i * gammaTableSize];
+ }
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform::New(const sk_sp<SkColorSpace>& srcSpace,
const sk_sp<SkColorSpace>& dstSpace) {
if (!srcSpace || !dstSpace) {
const sk_sp<SkColorSpace>& dstSpace)
{
build_src_to_dst(fSrcToDst, srcToDst);
-
- // Build tables to transform src gamma to linear.
- switch (srcSpace->gammaNamed()) {
- case SkColorSpace::kSRGB_GammaNamed:
- fSrcGammaTables[0] = fSrcGammaTables[1] = fSrcGammaTables[2] = sk_linear_from_srgb;
- break;
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- fSrcGammaTables[0] = fSrcGammaTables[1] = fSrcGammaTables[2] = sk_linear_from_2dot2;
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_from_gamma(fSrcGammaTableStorage, 1.0f);
- fSrcGammaTables[0] = fSrcGammaTables[1] = fSrcGammaTables[2] = fSrcGammaTableStorage;
- break;
- default: {
- const SkGammas* gammas = as_CSB(srcSpace)->gammas();
- SkASSERT(gammas);
-
- for (int i = 0; i < 3; i++) {
- const SkGammaCurve& curve = (*gammas)[i];
-
- if (i > 0) {
- // Check if this curve matches the first curve. In this case, we can
- // share the same table pointer. Logically, this should almost always
- // be true. I've never seen a profile where all three gamma curves
- // didn't match. But it is possible that they won't.
- // TODO (msarett):
- // This comparison won't catch the case where each gamma curve has a
- // pointer to its own look-up table, but the tables actually match.
- // Should we perform a deep compare of gamma tables here? Or should
- // we catch this when parsing the profile? Or should we not worry
- // about a bit of redundant work?
- if (curve.quickEquals((*gammas)[0])) {
- fSrcGammaTables[i] = fSrcGammaTables[0];
- continue;
- }
- }
-
- if (curve.isNamed()) {
- switch (curve.fNamed) {
- case SkColorSpace::kSRGB_GammaNamed:
- fSrcGammaTables[i] = sk_linear_from_srgb;
- break;
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- fSrcGammaTables[i] = sk_linear_from_2dot2;
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256], 1.0f);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- break;
- default:
- SkASSERT(false);
- break;
- }
- } else if (curve.isValue()) {
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256], curve.fValue);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- } else if (curve.isTable()) {
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256],
- curve.fTable.get(), curve.fTableSize);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- } else {
- SkASSERT(curve.isParametric());
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256], curve.fG,
- curve.fA, curve.fB, curve.fC, curve.fD, curve.fE,
- curve.fF);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- }
- }
- }
- }
-
- // Build tables to transform linear to dst gamma.
- // FIXME (msarett):
- // Should we spend all of this time bulding the dst gamma tables when the client only
- // wants to convert to F16?
- switch (dstSpace->gammaNamed()) {
- case SkColorSpace::kSRGB_GammaNamed:
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_to_gamma(fDstGammaTableStorage, kDstGammaTableSize, 1.0f);
- fDstGammaTables[0] = fDstGammaTables[1] = fDstGammaTables[2] = fDstGammaTableStorage;
- break;
- default: {
- const SkGammas* gammas = as_CSB(dstSpace)->gammas();
- SkASSERT(gammas);
-
- for (int i = 0; i < 3; i++) {
- const SkGammaCurve& curve = (*gammas)[i];
-
- if (i > 0) {
- // Check if this curve matches the first curve. In this case, we can
- // share the same table pointer. Logically, this should almost always
- // be true. I've never seen a profile where all three gamma curves
- // didn't match. But it is possible that they won't.
- // TODO (msarett):
- // This comparison won't catch the case where each gamma curve has a
- // pointer to its own look-up table (but the tables actually match).
- // Should we perform a deep compare of gamma tables here? Or should
- // we catch this when parsing the profile? Or should we not worry
- // about a bit of redundant work?
- if (curve.quickEquals((*gammas)[0])) {
- fDstGammaTables[i] = fDstGammaTables[0];
- continue;
- }
- }
-
- if (curve.isNamed()) {
- switch (curve.fNamed) {
- case SkColorSpace::kSRGB_GammaNamed:
- fDstGammaTables[i] = linear_to_srgb;
- break;
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- fDstGammaTables[i] = linear_to_2dot2;
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_to_gamma(
- &fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, 1.0f);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- break;
- default:
- SkASSERT(false);
- break;
- }
- } else if (curve.isValue()) {
- build_table_linear_to_gamma(&fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, curve.fValue);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- } else if (curve.isTable()) {
- build_table_linear_to_gamma(&fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, curve.fTable.get(),
- curve.fTableSize);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- } else {
- SkASSERT(curve.isParametric());
- build_table_linear_to_gamma(&fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, curve.fG, curve.fA, curve.fB,
- curve.fC, curve.fD, curve.fE, curve.fF);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- }
- }
- }
- }
+ build_gamma_tables(fSrcGammaTables, fSrcGammaTableStorage, 256, srcSpace, kToLinear);
+ build_gamma_tables(fDstGammaTables, fDstGammaTableStorage, SkDefaultXform::kDstGammaTableSize,
+ dstSpace, kFromLinear);
}
template <>
: fColorLUT(sk_ref_sp((SkColorLookUpTable*) as_CSB(srcSpace)->colorLUT()))
, fSrcToDst(srcToDst)
{
- // Build tables to transform src gamma to linear.
- switch (srcSpace->gammaNamed()) {
- case SkColorSpace::kSRGB_GammaNamed:
- fSrcGammaTables[0] = fSrcGammaTables[1] = fSrcGammaTables[2] = sk_linear_from_srgb;
- break;
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- fSrcGammaTables[0] = fSrcGammaTables[1] = fSrcGammaTables[2] = sk_linear_from_2dot2;
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_from_gamma(fSrcGammaTableStorage, 1.0f);
- fSrcGammaTables[0] = fSrcGammaTables[1] = fSrcGammaTables[2] = fSrcGammaTableStorage;
- break;
- default: {
- const SkGammas* gammas = as_CSB(srcSpace)->gammas();
- SkASSERT(gammas);
-
- for (int i = 0; i < 3; i++) {
- const SkGammaCurve& curve = (*gammas)[i];
-
- if (i > 0) {
- // Check if this curve matches the first curve. In this case, we can
- // share the same table pointer. Logically, this should almost always
- // be true. I've never seen a profile where all three gamma curves
- // didn't match. But it is possible that they won't.
- // TODO (msarett):
- // This comparison won't catch the case where each gamma curve has a
- // pointer to its own look-up table, but the tables actually match.
- // Should we perform a deep compare of gamma tables here? Or should
- // we catch this when parsing the profile? Or should we not worry
- // about a bit of redundant work?
- if (curve.quickEquals((*gammas)[0])) {
- fSrcGammaTables[i] = fSrcGammaTables[0];
- continue;
- }
- }
-
- if (curve.isNamed()) {
- switch (curve.fNamed) {
- case SkColorSpace::kSRGB_GammaNamed:
- fSrcGammaTables[i] = sk_linear_from_srgb;
- break;
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- fSrcGammaTables[i] = sk_linear_from_2dot2;
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256], 1.0f);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- break;
- default:
- SkASSERT(false);
- break;
- }
- } else if (curve.isValue()) {
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256], curve.fValue);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- } else if (curve.isTable()) {
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256],
- curve.fTable.get(), curve.fTableSize);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- } else {
- SkASSERT(curve.isParametric());
- build_table_linear_from_gamma(&fSrcGammaTableStorage[i * 256], curve.fG,
- curve.fA, curve.fB, curve.fC, curve.fD, curve.fE,
- curve.fF);
- fSrcGammaTables[i] = &fSrcGammaTableStorage[i * 256];
- }
- }
- }
- }
-
- // Build tables to transform linear to dst gamma.
- switch (dstSpace->gammaNamed()) {
- case SkColorSpace::kSRGB_GammaNamed:
- fDstGammaTables[0] = fDstGammaTables[1] = fDstGammaTables[2] = linear_to_srgb;
- break;
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- fDstGammaTables[0] = fDstGammaTables[1] = fDstGammaTables[2] = linear_to_2dot2;
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_to_gamma(fDstGammaTableStorage, kDstGammaTableSize, 1.0f);
- fDstGammaTables[0] = fDstGammaTables[1] = fDstGammaTables[2] = fDstGammaTableStorage;
- break;
- default: {
- const SkGammas* gammas = as_CSB(dstSpace)->gammas();
- SkASSERT(gammas);
-
- for (int i = 0; i < 3; i++) {
- const SkGammaCurve& curve = (*gammas)[i];
-
- if (i > 0) {
- // Check if this curve matches the first curve. In this case, we can
- // share the same table pointer. Logically, this should almost always
- // be true. I've never seen a profile where all three gamma curves
- // didn't match. But it is possible that they won't.
- // TODO (msarett):
- // This comparison won't catch the case where each gamma curve has a
- // pointer to its own look-up table (but the tables actually match).
- // Should we perform a deep compare of gamma tables here? Or should
- // we catch this when parsing the profile? Or should we not worry
- // about a bit of redundant work?
- if (curve.quickEquals((*gammas)[0])) {
- fDstGammaTables[i] = fDstGammaTables[0];
- continue;
- }
- }
-
- if (curve.isNamed()) {
- switch (curve.fNamed) {
- case SkColorSpace::kSRGB_GammaNamed:
- fDstGammaTables[i] = linear_to_srgb;
- break;
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- fDstGammaTables[i] = linear_to_2dot2;
- break;
- case SkColorSpace::kLinear_GammaNamed:
- build_table_linear_to_gamma(
- &fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, 1.0f);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- break;
- default:
- SkASSERT(false);
- break;
- }
- } else if (curve.isValue()) {
- build_table_linear_to_gamma(&fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, curve.fValue);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- } else if (curve.isTable()) {
- build_table_linear_to_gamma(&fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, curve.fTable.get(),
- curve.fTableSize);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- } else {
- SkASSERT(curve.isParametric());
- build_table_linear_to_gamma(&fDstGammaTableStorage[i * kDstGammaTableSize],
- kDstGammaTableSize, curve.fG, curve.fA, curve.fB,
- curve.fC, curve.fD, curve.fE, curve.fF);
- fDstGammaTables[i] = &fDstGammaTableStorage[i * kDstGammaTableSize];
- }
- }
- }
- }
+ build_gamma_tables(fSrcGammaTables, fSrcGammaTableStorage, 256, srcSpace, kToLinear);
+ build_gamma_tables(fDstGammaTables, fDstGammaTableStorage, SkDefaultXform::kDstGammaTableSize,
+ dstSpace, kFromLinear);
}
static float byte_to_float(uint8_t byte) {
static constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v');
static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
-static bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t* src, size_t len) {
- for (uint32_t i = 0; i < numGammas; i++) {
- if (len < 12) {
- // FIXME (msarett):
- // We could potentially return false here after correctly parsing *some* of the
- // gammas correctly. Should we somehow try to indicate a partial success?
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
+static SkGammas::Type set_gamma_value(SkGammas::Data* data, float value) {
+ if (color_space_almost_equal(2.2f, value)) {
+ data->fNamed = SkColorSpace::k2Dot2Curve_GammaNamed;
+ return SkGammas::Type::kNamed_Type;
+ }
- // We need to count the number of bytes in the tag, so we are able to move to the
- // next tag on the next loop iteration.
- size_t tagBytes;
+ if (color_space_almost_equal(1.0f, value)) {
+ data->fNamed = SkColorSpace::kLinear_GammaNamed;
+ return SkGammas::Type::kNamed_Type;
+ }
- uint32_t type = read_big_endian_uint(src);
- switch (type) {
- case kTAG_CurveType: {
- uint32_t count = read_big_endian_uint(src + 8);
+ if (color_space_almost_equal(0.0f, value)) {
+ return SkGammas::Type::kNone_Type;
+ }
- // tagBytes = 12 + 2 * count
- // We need to do safe addition here to avoid integer overflow.
- if (!safe_add(count, count, &tagBytes) ||
- !safe_add((size_t) 12, tagBytes, &tagBytes))
- {
- SkColorSpacePrintf("Invalid gamma count");
- return false;
- }
+ data->fValue = value;
+ return SkGammas::Type::kValue_Type;
+}
- if (0 == count) {
- // Some tags require a gamma curve, but the author doesn't actually want
- // to transform the data. In this case, it is common to see a curve with
- // a count of 0.
- gammas[i].fNamed = SkColorSpace::kLinear_GammaNamed;
- break;
- } else if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
+static float read_big_endian_16_dot_16(const uint8_t buf[4]) {
+ // It just so happens that SkFixed is also 16.16!
+ return SkFixedToFloat(read_big_endian_int(buf));
+}
+
+/**
+ * @param outData Set to the appropriate value on success. If we have table or
+ * parametric gamma, it is the responsibility of the caller to set
+ * fOffset.
+ * @param outParams If this is a parametric gamma, this is set to the appropriate
+ * parameters on success.
+ * @param outTagBytes Will be set to the length of the tag on success.
+ * @src Pointer to tag data.
+ * @len Length of tag data in bytes.
+ *
+ * @return kNone_Type on failure, otherwise the type of the gamma tag.
+ */
+static SkGammas::Type parse_gamma(SkGammas::Data* outData, SkGammas::Params* outParams,
+ size_t* outTagBytes, const uint8_t* src, size_t len) {
+ if (len < 12) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return SkGammas::Type::kNone_Type;
+ }
+
+ // In the case of consecutive gamma tags, we need to count the number of bytes in the
+ // tag, so that we can move on to the next tag.
+ size_t tagBytes;
+
+ uint32_t type = read_big_endian_uint(src);
+ // Bytes 4-7 are reserved and should be set to zero.
+ switch (type) {
+ case kTAG_CurveType: {
+ uint32_t count = read_big_endian_uint(src + 8);
+
+ // tagBytes = 12 + 2 * count
+ // We need to do safe addition here to avoid integer overflow.
+ if (!safe_add(count, count, &tagBytes) ||
+ !safe_add((size_t) 12, tagBytes, &tagBytes))
+ {
+ SkColorSpacePrintf("Invalid gamma count");
+ return SkGammas::Type::kNone_Type;
+ }
+
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return SkGammas::Type::kNone_Type;
+ }
+ *outTagBytes = tagBytes;
+
+ if (0 == count) {
+ // Some tags require a gamma curve, but the author doesn't actually want
+ // to transform the data. In this case, it is common to see a curve with
+ // a count of 0.
+ outData->fNamed = SkColorSpace::kLinear_GammaNamed;
+ return SkGammas::Type::kNamed_Type;
+ }
+
+ const uint16_t* table = (const uint16_t*) (src + 12);
+ if (1 == count) {
+ // The table entry is the gamma (with a bias of 256).
+ float value = (read_big_endian_short((const uint8_t*) table)) / 256.0f;
+ SkColorSpacePrintf("gamma %g\n", value);
+
+ return set_gamma_value(outData, value);
+ }
+
+ // Check for frequently occurring sRGB curves.
+ // We do this by sampling a few values and see if they match our expectation.
+ // A more robust solution would be to compare each value in this curve against
+ // an sRGB curve to see if we remain below an error threshold. At this time,
+ // we haven't seen any images in the wild that make this kind of
+ // calculation necessary. We encounter identical gamma curves over and
+ // over again, but relatively few variations.
+ if (1024 == count) {
+ // The magic values were chosen because they match both the very common
+ // HP sRGB gamma table and the less common Canon sRGB gamma table (which use
+ // different rounding rules).
+ if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
+ 3366 == read_big_endian_short((const uint8_t*) &table[257]) &&
+ 14116 == read_big_endian_short((const uint8_t*) &table[513]) &&
+ 34318 == read_big_endian_short((const uint8_t*) &table[768]) &&
+ 65535 == read_big_endian_short((const uint8_t*) &table[1023])) {
+ outData->fNamed = SkColorSpace::kSRGB_GammaNamed;
+ return SkGammas::Type::kNamed_Type;
}
+ }
- const uint16_t* table = (const uint16_t*) (src + 12);
- if (1 == count) {
- // The table entry is the gamma (with a bias of 256).
- float value = (read_big_endian_short((const uint8_t*) table)) / 256.0f;
- set_gamma_value(&gammas[i], value);
- SkColorSpacePrintf("gamma %g\n", value);
- break;
+ if (26 == count) {
+ // The magic values were chosen because they match a very common LCMS sRGB
+ // gamma table.
+ if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
+ 3062 == read_big_endian_short((const uint8_t*) &table[6]) &&
+ 12824 == read_big_endian_short((const uint8_t*) &table[12]) &&
+ 31237 == read_big_endian_short((const uint8_t*) &table[18]) &&
+ 65535 == read_big_endian_short((const uint8_t*) &table[25])) {
+ outData->fNamed = SkColorSpace::kSRGB_GammaNamed;
+ return SkGammas::Type::kNamed_Type;
}
+ }
- // Check for frequently occurring sRGB curves.
- // We do this by sampling a few values and see if they match our expectation.
- // A more robust solution would be to compare each value in this curve against
- // an sRGB curve to see if we remain below an error threshold. At this time,
- // we haven't seen any images in the wild that make this kind of
- // calculation necessary. We encounter identical gamma curves over and
- // over again, but relatively few variations.
- if (1024 == count) {
- // The magic values were chosen because they match a very common sRGB
- // gamma table and the less common Canon sRGB gamma table (which use
- // different rounding rules).
- if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
- 3366 == read_big_endian_short((const uint8_t*) &table[257]) &&
- 14116 == read_big_endian_short((const uint8_t*) &table[513]) &&
- 34318 == read_big_endian_short((const uint8_t*) &table[768]) &&
- 65535 == read_big_endian_short((const uint8_t*) &table[1023])) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- break;
- }
- } else if (26 == count) {
- // The magic values were chosen because they match a very common sRGB
- // gamma table.
- if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
- 3062 == read_big_endian_short((const uint8_t*) &table[6]) &&
- 12824 == read_big_endian_short((const uint8_t*) &table[12]) &&
- 31237 == read_big_endian_short((const uint8_t*) &table[18]) &&
- 65535 == read_big_endian_short((const uint8_t*) &table[25])) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- break;
- }
- } else if (4096 == count) {
- // The magic values were chosen because they match Nikon, Epson, and
- // LCMS sRGB gamma tables (all of which use different rounding rules).
- if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
- 950 == read_big_endian_short((const uint8_t*) &table[515]) &&
- 3342 == read_big_endian_short((const uint8_t*) &table[1025]) &&
- 14079 == read_big_endian_short((const uint8_t*) &table[2051]) &&
- 65535 == read_big_endian_short((const uint8_t*) &table[4095])) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- break;
- }
+ if (4096 == count) {
+ // The magic values were chosen because they match Nikon, Epson, and
+ // LCMS sRGB gamma tables (all of which use different rounding rules).
+ if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
+ 950 == read_big_endian_short((const uint8_t*) &table[515]) &&
+ 3342 == read_big_endian_short((const uint8_t*) &table[1025]) &&
+ 14079 == read_big_endian_short((const uint8_t*) &table[2051]) &&
+ 65535 == read_big_endian_short((const uint8_t*) &table[4095])) {
+ outData->fNamed = SkColorSpace::kSRGB_GammaNamed;
+ return SkGammas::Type::kNamed_Type;
}
+ }
+
+ // Otherwise, we will represent gamma with a table.
+ outData->fTable.fSize = count;
+ return SkGammas::Type::kTable_Type;
+ }
+ case kTAG_ParaCurveType: {
+ enum ParaCurveType {
+ kExponential_ParaCurveType = 0,
+ kGAB_ParaCurveType = 1,
+ kGABC_ParaCurveType = 2,
+ kGABDE_ParaCurveType = 3,
+ kGABCDEF_ParaCurveType = 4,
+ };
+
+ // Determine the format of the parametric curve tag.
+ uint16_t format = read_big_endian_short(src + 8);
+ if (format > kGABCDEF_ParaCurveType) {
+ SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
+ return SkGammas::Type::kNone_Type;
+ }
- // Otherwise, fill in the interpolation table.
- gammas[i].fTableSize = count;
- gammas[i].fTable = std::unique_ptr<float[]>(new float[count]);
- for (uint32_t j = 0; j < count; j++) {
- gammas[i].fTable[j] =
- (read_big_endian_short((const uint8_t*) &table[j])) / 65535.0f;
+ if (kExponential_ParaCurveType == format) {
+ tagBytes = 12 + 4;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return SkGammas::Type::kNone_Type;
}
- break;
+
+ // Y = X^g
+ float g = read_big_endian_16_dot_16(src + 12);
+
+ *outTagBytes = tagBytes;
+ return set_gamma_value(outData, g);
+ }
+
+ // Here's where the real parametric gammas start. There are many
+ // permutations of the same equations.
+ //
+ // Y = (aX + b)^g + c for X >= d
+ // Y = eX + f otherwise
+ //
+ // We will fill in with zeros as necessary to always match the above form.
+ if (len < 24) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return SkGammas::Type::kNone_Type;
}
- case kTAG_ParaCurveType: {
- enum ParaCurveType {
- kExponential_ParaCurveType = 0,
- kGAB_ParaCurveType = 1,
- kGABC_ParaCurveType = 2,
- kGABDE_ParaCurveType = 3,
- kGABCDEF_ParaCurveType = 4,
- };
-
- // Determine the format of the parametric curve tag.
- uint16_t format = read_big_endian_short(src + 8);
- if (kExponential_ParaCurveType == format) {
- tagBytes = 12 + 4;
+ float g = read_big_endian_16_dot_16(src + 12);
+ float a = read_big_endian_16_dot_16(src + 16);
+ float b = read_big_endian_16_dot_16(src + 20);
+ float c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f;
+ switch(format) {
+ case kGAB_ParaCurveType:
+ tagBytes = 12 + 12;
+
+ // Y = (aX + b)^g for X >= -b/a
+ // Y = 0 otherwise
+ d = -b / a;
+ break;
+ case kGABC_ParaCurveType:
+ tagBytes = 12 + 16;
if (len < tagBytes) {
SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
+ return SkGammas::Type::kNone_Type;
}
- // Y = X^g
- int32_t g = read_big_endian_int(src + 12);
- set_gamma_value(&gammas[i], SkFixedToFloat(g));
- } else {
- // Here's where the real parametric gammas start. There are many
- // permutations of the same equations.
- //
- // Y = (aX + b)^g + c for X >= d
- // Y = eX + f otherwise
- //
- // We will fill in with zeros as necessary to always match the above form.
- float g = 0.0f, a = 0.0f, b = 0.0f, c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f;
- switch(format) {
- case kGAB_ParaCurveType: {
- tagBytes = 12 + 12;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g for X >= -b/a
- // Y = 0 otherwise
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- if (0.0f == a) {
- return false;
- }
-
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- d = -b / a;
- break;
- }
- case kGABC_ParaCurveType:
- tagBytes = 12 + 16;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g + c for X >= -b/a
- // Y = c otherwise
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- if (0.0f == a) {
- return false;
- }
-
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- c = SkFixedToFloat(read_big_endian_int(src + 24));
- d = -b / a;
- f = c;
- break;
- case kGABDE_ParaCurveType:
- tagBytes = 12 + 20;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g for X >= d
- // Y = cX otherwise
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- d = SkFixedToFloat(read_big_endian_int(src + 28));
- e = SkFixedToFloat(read_big_endian_int(src + 24));
- break;
- case kGABCDEF_ParaCurveType:
- tagBytes = 12 + 28;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g + c for X >= d
- // Y = eX + f otherwise
- // NOTE: The ICC spec writes "cX" in place of "eX" but I think
- // it's a typo.
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- c = SkFixedToFloat(read_big_endian_int(src + 24));
- d = SkFixedToFloat(read_big_endian_int(src + 28));
- e = SkFixedToFloat(read_big_endian_int(src + 32));
- f = SkFixedToFloat(read_big_endian_int(src + 36));
- break;
- default:
- SkColorSpacePrintf("Invalid parametric curve type\n");
- return false;
+ // Y = (aX + b)^g + c for X >= -b/a
+ // Y = c otherwise
+ c = read_big_endian_16_dot_16(src + 24);
+ d = -b / a;
+ f = c;
+ break;
+ case kGABDE_ParaCurveType:
+ tagBytes = 12 + 20;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return SkGammas::Type::kNone_Type;
}
- // Recognize and simplify a very common parametric representation of sRGB gamma.
- if (color_space_almost_equal(0.9479f, a) &&
- color_space_almost_equal(0.0521f, b) &&
- color_space_almost_equal(0.0000f, c) &&
- color_space_almost_equal(0.0405f, d) &&
- color_space_almost_equal(0.0774f, e) &&
- color_space_almost_equal(0.0000f, f) &&
- color_space_almost_equal(2.4000f, g)) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- } else {
- // Fail on invalid gammas.
- if (d <= 0.0f) {
- // Y = (aX + b)^g + c for always
- if (0.0f == a || 0.0f == g) {
- SkColorSpacePrintf("A or G is zero, constant gamma function "
- "is nonsense");
- return false;
- }
- } else if (d >= 1.0f) {
- // Y = eX + f for always
- if (0.0f == e) {
- SkColorSpacePrintf("E is zero, constant gamma function is "
- "nonsense");
- return false;
- }
- } else if ((0.0f == a || 0.0f == g) && 0.0f == e) {
- SkColorSpacePrintf("A or G, and E are zero, constant gamma function "
- "is nonsense");
- return false;
- }
+ // Y = (aX + b)^g for X >= d
+ // Y = eX otherwise
+ d = read_big_endian_16_dot_16(src + 28);
- gammas[i].fG = g;
- gammas[i].fA = a;
- gammas[i].fB = b;
- gammas[i].fC = c;
- gammas[i].fD = d;
- gammas[i].fE = e;
- gammas[i].fF = f;
+ // Not a bug! We define |e| to always be the coefficient on X in the
+ // second equation. The spec calls this |c| in this particular equation.
+ // We don't follow their convention because then |c| would have a
+ // different meaning in each of our cases.
+ e = read_big_endian_16_dot_16(src + 24);
+ break;
+ case kGABCDEF_ParaCurveType:
+ tagBytes = 12 + 28;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return SkGammas::Type::kNone_Type;
}
+
+ // Y = (aX + b)^g + c for X >= d
+ // Y = eX + f otherwise
+ // NOTE: The ICC spec writes "cX" in place of "eX" but I think
+ // it's a typo.
+ c = read_big_endian_16_dot_16(src + 24);
+ d = read_big_endian_16_dot_16(src + 28);
+ e = read_big_endian_16_dot_16(src + 32);
+ f = read_big_endian_16_dot_16(src + 36);
+ break;
+ default:
+ SkASSERT(false);
+ return SkGammas::Type::kNone_Type;
+ }
+
+ // Recognize and simplify a very common parametric representation of sRGB gamma.
+ if (color_space_almost_equal(0.9479f, a) &&
+ color_space_almost_equal(0.0521f, b) &&
+ color_space_almost_equal(0.0000f, c) &&
+ color_space_almost_equal(0.0405f, d) &&
+ color_space_almost_equal(0.0774f, e) &&
+ color_space_almost_equal(0.0000f, f) &&
+ color_space_almost_equal(2.4000f, g)) {
+ outData->fNamed = SkColorSpace::kSRGB_GammaNamed;
+ return SkGammas::Type::kNamed_Type;
+ }
+
+ // Fail on invalid gammas.
+ if (SkScalarIsNaN(d)) {
+ return SkGammas::Type::kNone_Type;
+ }
+
+ if (d <= 0.0f) {
+ // Y = (aX + b)^g + c for always
+ if (0.0f == a || 0.0f == g) {
+ SkColorSpacePrintf("A or G is zero, constant gamma function "
+ "is nonsense");
+ return SkGammas::Type::kNone_Type;
}
+ }
- break;
+ if (d >= 1.0f) {
+ // Y = eX + f for always
+ if (0.0f == e) {
+ SkColorSpacePrintf("E is zero, constant gamma function is "
+ "nonsense");
+ return SkGammas::Type::kNone_Type;
+ }
}
- default:
- SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
- return false;
+
+ if ((0.0f == a || 0.0f == g) && 0.0f == e) {
+ SkColorSpacePrintf("A or G, and E are zero, constant gamma function "
+ "is nonsense");
+ return SkGammas::Type::kNone_Type;
+ }
+
+ *outTagBytes = tagBytes;
+
+ outParams->fG = g;
+ outParams->fA = a;
+ outParams->fB = b;
+ outParams->fC = c;
+ outParams->fD = d;
+ outParams->fE = e;
+ outParams->fF = f;
+ return SkGammas::Type::kParam_Type;
}
+ default:
+ SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
+ return SkGammas::Type::kNone_Type;
+ }
+}
+
+/**
+ * Returns the additional size in bytes needed to store the gamma tag.
+ */
+static size_t gamma_alloc_size(SkGammas::Type type, const SkGammas::Data& data) {
+ switch (type) {
+ case SkGammas::Type::kNamed_Type:
+ case SkGammas::Type::kValue_Type:
+ return 0;
+ case SkGammas::Type::kTable_Type:
+ return sizeof(float) * data.fTable.fSize;
+ case SkGammas::Type::kParam_Type:
+ return sizeof(SkGammas::Params);
+ default:
+ SkASSERT(false);
+ return 0;
+ }
+}
- // Ensure that we have successfully read a gamma representation.
- SkASSERT(gammas[i].isNamed() || gammas[i].isValue() || gammas[i].isTable() ||
- gammas[i].isParametric());
+/**
+ * Sets invalid gamma to the default value.
+ */
+static void handle_invalid_gamma(SkGammas::Type* type, SkGammas::Data* data) {
+ if (SkGammas::Type::kNone_Type == *type) {
+ *type = SkGammas::Type::kNamed_Type;
+ data->fNamed = SkColorSpace::kSRGB_GammaNamed;
+ }
+}
- // Adjust src and len if there is another gamma curve to load.
- if (i != numGammas - 1) {
- // Each curve is padded to 4-byte alignment.
- tagBytes = SkAlign4(tagBytes);
- if (len < tagBytes) {
- return false;
+/**
+ * Finish loading the gammas, now that we have allocated memory for the SkGammas struct.
+ *
+ * There's nothing to do for the simple cases, but for table gammas we need to actually
+ * read the table into heap memory. And for parametric gammas, we need to copy over the
+ * parameter values.
+ *
+ * @param memory Pointer to start of the SkGammas memory block
+ * @param offset Bytes of memory (after the SkGammas struct) that are already in use.
+ * @param data In-out variable. Will fill in the offset to the table or parameters
+ * if necessary.
+ * @param params Parameters for gamma curve. Only initialized/used when we have a
+ * parametric gamma.
+ * @param src Pointer to start of the gamma tag.
+ *
+ * @return Additional bytes of memory that are being used by this gamma curve.
+ */
+static size_t load_gammas(void* memory, size_t offset, SkGammas::Type type,
+ SkGammas::Data* data, const SkGammas::Params& params,
+ const uint8_t* src) {
+ void* storage = SkTAddOffset<void>(memory, offset + sizeof(SkGammas));
+
+ switch (type) {
+ case SkGammas::Type::kNamed_Type:
+ case SkGammas::Type::kValue_Type:
+ // Nothing to do here.
+ return 0;
+ case SkGammas::Type::kTable_Type: {
+ data->fTable.fOffset = offset;
+
+ float* outTable = (float*) storage;
+ const uint16_t* inTable = (const uint16_t*) (src + 12);
+ for (int i = 0; i < data->fTable.fSize; i++) {
+ outTable[i] = read_big_endian_16_dot_16((const uint8_t*) &inTable[i]);
}
- src += tagBytes;
- len -= tagBytes;
+ return sizeof(float) * data->fTable.fSize;
}
+ case SkGammas::Type::kParam_Type:
+ data->fTable.fOffset = offset;
+ memcpy(storage, ¶ms, sizeof(SkGammas::Params));
+ return sizeof(SkGammas::Params);
+ default:
+ SkASSERT(false);
+ return 0;
}
-
- return true;
}
static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' ');
-bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels, uint32_t outputChannels,
- const uint8_t* src, size_t len) {
+static bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels,
+ uint32_t outputChannels, const uint8_t* src, size_t len) {
// 16 bytes reserved for grid points, 2 for precision, 2 for padding.
// The color LUT data follows after this header.
static constexpr uint32_t kColorLUTHeaderSize = 20;
return true;
}
-bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
+static bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
if (len < 48) {
SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len);
return false;
return true;
}
-bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* toXYZ,
- const uint8_t* src, size_t len) {
+static bool load_a2b0(SkColorLookUpTable* colorLUT, SkColorSpace::GammaNamed* gammaNamed,
+ sk_sp<SkGammas>* gammas, SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
if (len < 32) {
SkColorSpacePrintf("A to B tag is too small (%d bytes).", len);
return false;
uint32_t offsetToMCurves = read_big_endian_int(src + 20);
if (0 != offsetToMCurves && offsetToMCurves < len) {
- if (!load_gammas(gammas, outputChannels, src + offsetToMCurves, len - offsetToMCurves)) {
- SkColorSpacePrintf("Failed to read M curves from A to B tag. Using linear gamma.\n");
- gammas[0].fNamed = SkColorSpace::kLinear_GammaNamed;
- gammas[1].fNamed = SkColorSpace::kLinear_GammaNamed;
- gammas[2].fNamed = SkColorSpace::kLinear_GammaNamed;
+ const uint8_t* rTagPtr = src + offsetToMCurves;
+ size_t tagLen = len - offsetToMCurves;
+
+ SkGammas::Data rData;
+ SkGammas::Params rParams;
+
+ // On an invalid first gamma, tagBytes remains set as zero. This causes the two
+ // subsequent to be treated as identical (which is what we want).
+ size_t tagBytes = 0;
+ SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen);
+ handle_invalid_gamma(&rType, &rData);
+ size_t alignedTagBytes = SkAlign4(tagBytes);
+
+ if ((3 * alignedTagBytes <= tagLen) &&
+ !memcmp(rTagPtr, rTagPtr + 1 * alignedTagBytes, tagBytes) &&
+ !memcmp(rTagPtr, rTagPtr + 2 * alignedTagBytes, tagBytes))
+ {
+ if (SkGammas::Type::kNamed_Type == rType) {
+ *gammaNamed = rData.fNamed;
+ } else {
+ size_t allocSize = sizeof(SkGammas) + gamma_alloc_size(rType, rData);
+ void* memory = sk_malloc_throw(allocSize);
+ *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+ load_gammas(memory, 0, rType, &rData, rParams, rTagPtr);
+
+ (*gammas)->fRedType = rType;
+ (*gammas)->fGreenType = rType;
+ (*gammas)->fBlueType = rType;
+
+ (*gammas)->fRedData = rData;
+ (*gammas)->fGreenData = rData;
+ (*gammas)->fBlueData = rData;
+ }
+ } else {
+ const uint8_t* gTagPtr = rTagPtr + alignedTagBytes;
+ tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
+ SkGammas::Data gData;
+ SkGammas::Params gParams;
+ tagBytes = 0;
+ SkGammas::Type gType = parse_gamma(&gData, &gParams, &tagBytes, gTagPtr,
+ tagLen);
+ handle_invalid_gamma(&gType, &gData);
+
+ alignedTagBytes = SkAlign4(tagBytes);
+ const uint8_t* bTagPtr = gTagPtr + alignedTagBytes;
+ tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
+ SkGammas::Data bData;
+ SkGammas::Params bParams;
+ SkGammas::Type bType = parse_gamma(&bData, &bParams, &tagBytes, bTagPtr,
+ tagLen);
+ handle_invalid_gamma(&bType, &bData);
+
+ size_t allocSize = sizeof(SkGammas) + gamma_alloc_size(rType, rData)
+ + gamma_alloc_size(gType, gData)
+ + gamma_alloc_size(bType, bData);
+ void* memory = sk_malloc_throw(allocSize);
+ *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+
+ uint32_t offset = 0;
+ (*gammas)->fRedType = rType;
+ offset += load_gammas(memory, offset, rType, &rData, rParams, rTagPtr);
+
+ (*gammas)->fGreenType = gType;
+ offset += load_gammas(memory, offset, gType, &gData, gParams, gTagPtr);
+
+ (*gammas)->fBlueType = bType;
+ load_gammas(memory, offset, bType, &bData, bParams, bTagPtr);
+
+ (*gammas)->fRedData = rData;
+ (*gammas)->fGreenData = gData;
+ (*gammas)->fBlueData = bData;
}
}
return true;
}
+static bool tag_equals(const ICCTag* a, const ICCTag* b, const uint8_t* base) {
+ if (!a || !b) {
+ return a == b;
+ }
+
+ if (a->fLength != b->fLength) {
+ return false;
+ }
+
+ if (a->fOffset == b->fOffset) {
+ return true;
+ }
+
+ return !memcmp(a->addr(base), b->addr(base), a->fLength);
+}
+
sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
if (!input || len < kICCHeaderSize) {
return_null("Data is null or not large enough to contain an ICC profile");
void* memory = sk_malloc_throw(len);
memcpy(memory, input, len);
sk_sp<SkData> data = SkData::MakeFromMalloc(memory, len);
- const void* base = data->data();
- const uint8_t* ptr = (const uint8_t*) base;
+ const uint8_t* base = data->bytes();
+ const uint8_t* ptr = base;
// Read the ICC profile header and check to make sure that it is valid.
ICCProfileHeader header;
const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
if (r && g && b) {
float toXYZ[9];
- if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLength) ||
- !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLength) ||
- !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLength))
+ if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) ||
+ !load_xyz(&toXYZ[3], g->addr(base), g->fLength) ||
+ !load_xyz(&toXYZ[6], b->addr(base), b->fLength))
{
return_null("Need valid rgb tags for XYZ space");
}
SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
mat.set3x3RowMajorf(toXYZ);
- // It is not uncommon to see missing or empty gamma tags. This indicates
- // that we should use unit gamma.
- SkGammaCurve curves[3];
r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
- if (!r || !load_gammas(&curves[0], 1, r->addr((const uint8_t*) base), r->fLength))
- {
- SkColorSpacePrintf("Failed to read R gamma tag.\n");
- curves[0].fNamed = SkColorSpace::kLinear_GammaNamed;
- }
- if (!g || !load_gammas(&curves[1], 1, g->addr((const uint8_t*) base), g->fLength))
- {
- SkColorSpacePrintf("Failed to read G gamma tag.\n");
- curves[1].fNamed = SkColorSpace::kLinear_GammaNamed;
+
+ // If some, but not all, of the gamma tags are missing, assume that all
+ // gammas are meant to be the same. This behavior is an arbitrary guess,
+ // but it simplifies the code below.
+ if ((!r || !g || !b) && (r || g || b)) {
+ if (!r) {
+ r = g ? g : b;
+ }
+
+ if (!g) {
+ g = r ? r : b;
+ }
+
+ if (!b) {
+ b = r ? r : g;
+ }
}
- if (!b || !load_gammas(&curves[2], 1, b->addr((const uint8_t*) base), b->fLength))
- {
- SkColorSpacePrintf("Failed to read B gamma tag.\n");
- curves[2].fNamed = SkColorSpace::kLinear_GammaNamed;
+
+ GammaNamed gammaNamed = kNonStandard_GammaNamed;
+ sk_sp<SkGammas> gammas = nullptr;
+ size_t tagBytes;
+ if (r && g && b) {
+ if (tag_equals(r, g, base) && tag_equals(g, b, base)) {
+ SkGammas::Data data;
+ SkGammas::Params params;
+ SkGammas::Type Type =
+ parse_gamma(&data, ¶ms, &tagBytes, r->addr(base), r->fLength);
+ handle_invalid_gamma(&Type, &data);
+
+ if (SkGammas::Type::kNamed_Type == Type) {
+ gammaNamed = data.fNamed;
+ } else {
+ size_t allocSize = sizeof(SkGammas) + gamma_alloc_size(Type, data);
+ void* memory = sk_malloc_throw(allocSize);
+ gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+ load_gammas(memory, 0, Type, &data, params, r->addr(base));
+
+ gammas->fRedType = Type;
+ gammas->fGreenType = Type;
+ gammas->fBlueType = Type;
+
+ gammas->fRedData = data;
+ gammas->fGreenData = data;
+ gammas->fBlueData = data;
+ }
+ } else {
+ SkGammas::Data rData;
+ SkGammas::Params rParams;
+ SkGammas::Type rType =
+ parse_gamma(&rData, &rParams, &tagBytes, r->addr(base), r->fLength);
+ handle_invalid_gamma(&rType, &rData);
+
+ SkGammas::Data gData;
+ SkGammas::Params gParams;
+ SkGammas::Type gType =
+ parse_gamma(&gData, &gParams, &tagBytes, g->addr(base), g->fLength);
+ handle_invalid_gamma(&gType, &gData);
+
+ SkGammas::Data bData;
+ SkGammas::Params bParams;
+ SkGammas::Type bType =
+ parse_gamma(&bData, &bParams, &tagBytes, b->addr(base), b->fLength);
+ handle_invalid_gamma(&bType, &bData);
+
+ size_t allocSize = sizeof(SkGammas) + gamma_alloc_size(rType, rData)
+ + gamma_alloc_size(gType, gData)
+ + gamma_alloc_size(bType, bData);
+ void* memory = sk_malloc_throw(allocSize);
+ gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+
+ uint32_t offset = 0;
+ gammas->fRedType = rType;
+ offset += load_gammas(memory, offset, rType, &rData, rParams,
+ r->addr(base));
+
+ gammas->fGreenType = gType;
+ offset += load_gammas(memory, offset, gType, &gData, gParams,
+ g->addr(base));
+
+ gammas->fBlueType = bType;
+ load_gammas(memory, offset, bType, &bData, bParams, b->addr(base));
+
+ gammas->fRedData = rData;
+ gammas->fGreenData = gData;
+ gammas->fBlueData = bData;
+ }
+ } else {
+ gammaNamed = kLinear_GammaNamed;
}
- GammaNamed gammaNamed = SkGammas::Named(curves);
if (kNonStandard_GammaNamed == gammaNamed) {
- sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
- std::move(curves[1]),
- std::move(curves[2]));
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, std::move(gammas),
- mat, std::move(data)));
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, gammaNamed,
+ std::move(gammas), mat,
+ std::move(data)));
} else {
return SkColorSpace_Base::NewRGB(gammaNamed, mat);
}
// Recognize color profile specified by A2B0 tag.
const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
if (a2b0) {
+ GammaNamed gammaNamed = kNonStandard_GammaNamed;
+ sk_sp<SkGammas> gammas = nullptr;
sk_sp<SkColorLookUpTable> colorLUT = sk_make_sp<SkColorLookUpTable>();
- SkGammaCurve curves[3];
SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
- if (!load_a2b0(colorLUT.get(), curves, &toXYZ, a2b0->addr((const uint8_t*) base),
+ if (!load_a2b0(colorLUT.get(), &gammaNamed, &gammas, &toXYZ, a2b0->addr(base),
a2b0->fLength)) {
return_null("Failed to parse A2B0 tag");
}
- GammaNamed gammaNamed = SkGammas::Named(curves);
colorLUT = colorLUT->fTable ? colorLUT : nullptr;
if (colorLUT || kNonStandard_GammaNamed == gammaNamed) {
- sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
- std::move(curves[1]),
- std::move(curves[2]));
-
return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(colorLUT),
- std::move(gammas), toXYZ,
- std::move(data)));
+ gammaNamed, std::move(gammas),
+ toXYZ, std::move(data)));
} else {
return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ);
}
ptr16[1] = 0;
}
-static float get_gamma_value(const SkGammaCurve* curve) {
- switch (curve->fNamed) {
- case SkColorSpace::kSRGB_GammaNamed:
- // FIXME (msarett):
- // kSRGB cannot be represented by a value. Here we fall through to 2.2f,
- // which is a close guess. To be more accurate, we need to represent sRGB
- // gamma with a parametric curve.
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- return 2.2f;
- case SkColorSpace::kLinear_GammaNamed:
- return 1.0f;
- default:
- SkASSERT(curve->isValue());
- return curve->fValue;
- }
-}
-
sk_sp<SkData> SkColorSpace_Base::writeToICC() const {
// Return if this object was created from a profile, or if we have already serialized
// the profile.
// Write TRC tags
GammaNamed gammaNamed = this->gammaNamed();
if (kNonStandard_GammaNamed == gammaNamed) {
- write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fRed));
+ // FIXME (msarett):
+ // Write the correct gamma representation rather than 2.2f.
+ write_trc_tag((uint32_t*) ptr, 2.2f);
ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fGreen));
+ write_trc_tag((uint32_t*) ptr, 2.2f);
ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fBlue));
+ write_trc_tag((uint32_t*) ptr, 2.2f);
ptr += SkAlign4(kTAG_TRC_Bytes);
} else {
switch (gammaNamed) {