SkWriteICCProfile: Write CICP data for PQ and HLG color spaces
In icc_from_color_space, do not early-out for non-sRGBish spaces.
Add functions to get_cicp_primaries and get_cicp_trfn to translate
from skcms_Matrix3x3 and skcms_TransferFunction to their enum values.
Add the function write_cicp_tag to create the CICP tag using these
enum values.
There was significant overlay between get_color_profile_description
and get_cicp_primaries and get_cicp_trfn. Refactor the code to
generate the description tag to use these constants. Merge the
code for the cprt (copyright) and desc (description) tags into
a single write_text_tag function.
Bug: 1366315
Change-Id: I75f633c8ce3ce48ee0bc55f3223fb49cd2021540
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/585076
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Christopher Cameron <ccameron@google.com>
diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp
index 6e0c93e..9944791 100644
--- a/src/core/SkICC.cpp
+++ b/src/core/SkICC.cpp
@@ -6,6 +6,7 @@
*/
#include "include/core/SkICC.h"
+#include "include/core/SkStream.h"
#include "include/private/SkFixed.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkColorSpacePriv.h"
@@ -14,106 +15,75 @@
#include "src/core/SkMD5.h"
#include "src/core/SkUtils.h"
+#include <string>
#include <vector>
-static constexpr char kDescriptionTagBodyPrefix[12] =
- { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'};
-
-static constexpr size_t kICCDescriptionTagSize = 44;
-
-static_assert(kICCDescriptionTagSize ==
- sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), "");
-static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2; // ascii->utf16be
-
-static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes.");
-static constexpr uint32_t kDescriptionTagHeader[7] {
- SkEndian_SwapBE32(kTAG_TextType), // Type signature
- 0, // Reserved
- SkEndian_SwapBE32(1), // Number of records
- SkEndian_SwapBE32(12), // Record size (must be 12)
- SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
- SkEndian_SwapBE32(kDescriptionTagBodySize), // Length of string
- SkEndian_SwapBE32(28), // Offset of string
-};
-
-static constexpr uint32_t kWhitePointTag[5] {
- SkEndian_SwapBE32(kXYZ_PCSSpace),
- 0,
- SkEndian_SwapBE32(0x0000f6d6), // X = 0.96420 (D50)
- SkEndian_SwapBE32(0x00010000), // Y = 1.00000 (D50)
- SkEndian_SwapBE32(0x0000d32d), // Z = 0.82491 (D50)
-};
-
-// 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,
-};
-static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes.");
-static constexpr uint32_t kCopyrightTagHeader[7] {
- SkEndian_SwapBE32(kTAG_TextType), // Type signature
- 0, // Reserved
- SkEndian_SwapBE32(1), // Number of records
- SkEndian_SwapBE32(12), // Record size (must be 12)
- SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
- SkEndian_SwapBE32(sizeof(kCopyrightTagBody)), // Length of string
- SkEndian_SwapBE32(28), // Offset of string
-};
-
static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
-static constexpr uint32_t kTAG_XYZ_Bytes = 20;
-static constexpr uint32_t kTAG_TRC_Bytes = 40;
+static constexpr uint32_t kTAG_cicp = SkSetFourByteTag('c', 'i', 'c', 'p');
static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
-static constexpr uint32_t kICCHeader[kICCHeaderSize / 4]{
- 0, // Size of the profile (computed)
- 0, // Preferred CMM type (ignored)
- SkEndian_SwapBE32(0x04300000), // Version 4.3
- SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
- SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space
- SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space
- 0,
- 0,
- 0, // Date and time (ignored)
- SkEndian_SwapBE32(kACSP_Signature), // Profile signature
- 0, // Platform target (ignored)
- 0x00000000, // Flags: not embedded, can be used independently
- 0, // Device manufacturer (ignored)
- 0, // Device model (ignored)
- 0,
- 0, // Device attributes (ignored)
- SkEndian_SwapBE32(1), // Relative colorimetric rendering intent
- SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X)
- SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y)
- SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z)
- 0, // Profile creator (ignored)
- 0,
- 0,
- 0,
- 0, // Profile id checksum (ignored)
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0, // Reserved (ignored)
- 0, // Number of tags (computed)
+struct ICCHeader {
+ // Size of the profile (computed)
+ uint32_t size;
+
+ // Preferred CMM type (ignored)
+ uint32_t cmm_type = 0;
+
+ // Version 4.3 or 4.4 if CICP is included.
+ uint32_t version = SkEndian_SwapBE32(0x04300000);
+
+ // Display device profile
+ uint32_t profile_class = SkEndian_SwapBE32(kDisplay_Profile);
+
+ // RGB input color space;
+ uint32_t data_color_space = SkEndian_SwapBE32(kRGB_ColorSpace);
+
+ // XYZ profile connection space
+ uint32_t pcs = SkEndian_SwapBE32(kXYZ_PCSSpace);
+
+ // Date and time (ignored)
+ uint8_t creation_date_time[12] = {0};
+
+ // Profile signature
+ uint32_t signature = SkEndian_SwapBE32(kACSP_Signature);
+
+ // Platform target (ignored)
+ uint32_t platform = 0;
+
+ // Flags: not embedded, can be used independently
+ uint32_t flags = 0x00000000;
+
+ // Device manufacturer (ignored)
+ uint32_t device_manufacturer = 0;
+
+ // Device model (ignored)
+ uint32_t device_model = 0;
+
+ // Device attributes (ignored)
+ uint8_t device_attributes[8] = {0};
+
+ // Relative colorimetric rendering intent
+ uint32_t rendering_intent = SkEndian_SwapBE32(1);
+
+ // D50 standard illuminant (X, Y, Z)
+ uint32_t illuminant_X = SkEndian_SwapBE32(0x0000f6d6);
+ uint32_t illuminant_Y = SkEndian_SwapBE32(0x00010000);
+ uint32_t illuminant_Z = SkEndian_SwapBE32(0x0000d32d);
+
+ // Profile creator (ignored)
+ uint32_t creator = 0;
+
+ // Profile id checksum (ignored)
+ uint8_t profile_id[16] = {0};
+
+ // Reserved (ignored)
+ uint8_t reserved[28] = {0};
+
+ // Technically not part of header, but required
+ uint32_t tag_count = 0;
};
-// Where inside kICCHeader we store the size of the profile.
-static constexpr uint32_t kICCHeaderSizeoOffset = 0;
-
-// Where inside kICCHeader we store the number of tags.
-static constexpr uint32_t kICCHeaderNumEntriesOffset = sizeof(kICCHeader) - sizeof(uint32_t);
-
// This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible
// when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed).
// The use of double is necessary to accommodate the full potential 32-bit mantissa of the 16.16
@@ -122,35 +92,44 @@
return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5));
}
+static sk_sp<SkData> write_xyz_tag(uint32_t x, uint32_t y, uint32_t z) {
+ uint32_t data[] = {
+ SkEndian_SwapBE32(kXYZ_PCSSpace),
+ 0,
+ SkEndian_SwapBE32(x),
+ SkEndian_SwapBE32(y),
+ SkEndian_SwapBE32(z),
+ };
+ return SkData::MakeWithCopy(data, sizeof(data));
+}
+
static sk_sp<SkData> write_xyz_tag(const skcms_Matrix3x3& toXYZD50, int col) {
- sk_sp<SkData> result = SkData::MakeUninitialized(kTAG_XYZ_Bytes);
- uint32_t* ptr = reinterpret_cast<uint32_t*>(result->writable_data());
- ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
- ptr[1] = 0;
- ptr[2] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[0][col]));
- ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[1][col]));
- ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[2][col]));
- return result;
+ return write_xyz_tag(float_round_to_fixed(toXYZD50.vals[0][col]),
+ float_round_to_fixed(toXYZD50.vals[1][col]),
+ float_round_to_fixed(toXYZD50.vals[2][col]));
}
static sk_sp<SkData> write_wtpt_tag() {
- return SkData::MakeWithoutCopy(kWhitePointTag, sizeof(kWhitePointTag));
+ return write_xyz_tag(0x0000f6d6, // X = 0.96420 (D50)
+ 0x00010000, // Y = 1.00000 (D50)
+ 0x0000d32d); // Z = 0.82491 (D50)
}
-static sk_sp<SkData> write_trc_tag(const skcms_TransferFunction& fn) {
- sk_sp<SkData> result = SkData::MakeUninitialized(kTAG_TRC_Bytes);
- uint32_t* ptr = reinterpret_cast<uint32_t*>(result->writable_data());
- ptr[0] = SkEndian_SwapBE32(kTAG_ParaCurveType);
- ptr[1] = 0;
- ptr[2] = (uint32_t) (SkEndian_SwapBE16(kGABCDEF_ParaCurveType));
- ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(fn.g));
- ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(fn.a));
- ptr[5] = SkEndian_SwapBE32(float_round_to_fixed(fn.b));
- ptr[6] = SkEndian_SwapBE32(float_round_to_fixed(fn.c));
- ptr[7] = SkEndian_SwapBE32(float_round_to_fixed(fn.d));
- ptr[8] = SkEndian_SwapBE32(float_round_to_fixed(fn.e));
- ptr[9] = SkEndian_SwapBE32(float_round_to_fixed(fn.f));
- return result;
+static sk_sp<SkData> write_para_tag(const skcms_TransferFunction& fn) {
+ SkASSERT(skcms_TransferFunction_isSRGBish(&fn));
+ const uint32_t data[] = {
+ SkEndian_SwapBE32(kTAG_ParaCurveType),
+ 0,
+ (uint32_t)(SkEndian_SwapBE16(kGABCDEF_ParaCurveType)),
+ SkEndian_SwapBE32(float_round_to_fixed(fn.g)),
+ SkEndian_SwapBE32(float_round_to_fixed(fn.a)),
+ SkEndian_SwapBE32(float_round_to_fixed(fn.b)),
+ SkEndian_SwapBE32(float_round_to_fixed(fn.c)),
+ SkEndian_SwapBE32(float_round_to_fixed(fn.d)),
+ SkEndian_SwapBE32(float_round_to_fixed(fn.e)),
+ SkEndian_SwapBE32(float_round_to_fixed(fn.f)),
+ };
+ return SkData::MakeWithCopy(data, sizeof(data));
}
static bool nearly_equal(float x, float y) {
@@ -187,124 +166,192 @@
return true;
}
-// Return nullptr if the color profile doen't have a special name.
-const char* get_color_profile_description(const skcms_TransferFunction& fn,
- const skcms_Matrix3x3& toXYZD50) {
- bool srgb_xfer = nearly_equal(fn, SkNamedTransferFn::kSRGB);
- bool srgb_gamut = nearly_equal(toXYZD50, SkNamedGamut::kSRGB);
- if (srgb_xfer && srgb_gamut) {
+static constexpr uint32_t kCICPPrimariesSRGB = 1;
+static constexpr uint32_t kCICPPrimariesP3 = 12;
+static constexpr uint32_t kCICPPrimariesRec2020 = 9;
+
+static uint32_t get_cicp_primaries(const skcms_Matrix3x3& toXYZD50) {
+ if (nearly_equal(toXYZD50, SkNamedGamut::kSRGB)) {
+ return kCICPPrimariesSRGB;
+ } else if (nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3)) {
+ return kCICPPrimariesP3;
+ } else if (nearly_equal(toXYZD50, SkNamedGamut::kRec2020)) {
+ return kCICPPrimariesRec2020;
+ }
+ return 0;
+}
+
+static constexpr uint32_t kCICPTrfnSRGB = 1;
+static constexpr uint32_t kCICPTrfn2Dot2 = 4;
+static constexpr uint32_t kCICPTrfnLinear = 8;
+static constexpr uint32_t kCICPTrfnPQ = 16;
+static constexpr uint32_t kCICPTrfnHLG = 18;
+
+static uint32_t get_cicp_trfn(const skcms_TransferFunction& fn) {
+ switch (classify_transfer_fn(fn)) {
+ case Bad_TF:
+ return 0;
+ case sRGBish_TF:
+ if (nearly_equal(fn, SkNamedTransferFn::kSRGB)) {
+ return kCICPTrfnSRGB;
+ } else if (nearly_equal(fn, SkNamedTransferFn::k2Dot2)) {
+ return kCICPTrfn2Dot2;
+ } else if (nearly_equal(fn, SkNamedTransferFn::kLinear)) {
+ return kCICPTrfnLinear;
+ }
+ break;
+ case PQish_TF:
+ // All PQ transfer functions are mapped to the single PQ value,
+ // ignoring their SDR white level.
+ return kCICPTrfnPQ;
+ break;
+ case HLGish_TF:
+ // All HLG transfer functions are mapped to the single HLG value.
+ return kCICPTrfnHLG;
+ break;
+ case HLGinvish_TF:
+ return 0;
+ }
+ return 0;
+}
+
+static std::string get_desc_string(const skcms_TransferFunction& fn,
+ const skcms_Matrix3x3& toXYZD50,
+ uint32_t cicp_trfn,
+ uint32_t cicp_primaries) {
+ // Use a unique string for sRGB.
+ if (cicp_trfn == kCICPPrimariesSRGB && cicp_primaries == kCICPTrfnSRGB) {
return "sRGB";
}
- bool line_xfer = nearly_equal(fn, SkNamedTransferFn::kLinear);
- if (line_xfer && srgb_gamut) {
- return "Linear Transfer with sRGB Gamut";
- }
- bool twoDotTwo = nearly_equal(fn, SkNamedTransferFn::k2Dot2);
- if (twoDotTwo && srgb_gamut) {
- return "2.2 Transfer with sRGB Gamut";
- }
- if (twoDotTwo && nearly_equal(toXYZD50, SkNamedGamut::kAdobeRGB)) {
- return "AdobeRGB";
- }
- bool display_p3 = nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3);
- if (srgb_xfer || line_xfer) {
- if (srgb_xfer && display_p3) {
- return "sRGB Transfer with Display P3 Gamut";
+
+ // If available, use the named CICP primaries and transfer function.
+ if (cicp_primaries && cicp_trfn) {
+ std::string result;
+ switch (cicp_trfn) {
+ case kCICPTrfnSRGB:
+ result += "sRGB";
+ break;
+ case kCICPTrfnLinear:
+ result += "Linear";
+ break;
+ case kCICPTrfn2Dot2:
+ result += "2.2";
+ break;
+ case kCICPTrfnPQ:
+ result += "PQ";
+ break;
+ case kCICPTrfnHLG:
+ result += "HLG";
+ break;
+ default:
+ result += "Unknown";
+ break;
}
- if (line_xfer && display_p3) {
- return "Linear Transfer with Display P3 Gamut";
+ result += " Transfer with ";
+ switch (cicp_primaries) {
+ case kCICPPrimariesSRGB:
+ result += "sRGB";
+ break;
+ case kCICPPrimariesP3:
+ result += "Display P3";
+ break;
+ case kCICPPrimariesRec2020:
+ result += "Rec2020";
+ break;
+ default:
+ result += "Unknown";
+ break;
}
- bool rec2020 = nearly_equal(toXYZD50, SkNamedGamut::kRec2020);
- if (srgb_xfer && rec2020) {
- return "sRGB Transfer with Rec-BT-2020 Gamut";
- }
- if (line_xfer && rec2020) {
- return "Linear Transfer with Rec-BT-2020 Gamut";
- }
+ result += " Gamut";
+ return result;
}
- return nullptr;
+
+ // Fall back to a prefix plus md5 hash.
+ SkMD5 md5;
+ md5.write(&toXYZD50, sizeof(toXYZD50));
+ md5.write(&fn, sizeof(fn));
+ SkMD5::Digest digest = md5.finish();
+ std::string md5_hexstring(2 * sizeof(SkMD5::Digest), ' ');
+ for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
+ uint8_t byte = digest.data[i];
+ md5_hexstring[2 * i + 0] = SkHexadecimalDigits::gUpper[byte >> 4];
+ md5_hexstring[2 * i + 1] = SkHexadecimalDigits::gUpper[byte & 0xF];
+ }
+ return "Google/Skia/" + md5_hexstring;
}
-static void get_color_profile_tag(char dst[kICCDescriptionTagSize],
- const skcms_TransferFunction& fn,
- const skcms_Matrix3x3& toXYZD50) {
- SkASSERT(dst);
- if (const char* description = get_color_profile_description(fn, toXYZD50)) {
- SkASSERT(strlen(description) < kICCDescriptionTagSize);
-
- // Without these extra (), GCC would warn us something like
- // ... sepecified bound 44 equals destination size ...
- // which, yeah, is exactly what we're trying to do, copy the string
- // and zero the rest of the destination if any. Sheesh.
- (strncpy(dst, description, kICCDescriptionTagSize));
- // "If the length of src is less than n, strncpy() writes additional
- // null bytes to dest to ensure that a total of n bytes are written."
- } else {
- memcpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix));
- SkMD5 md5;
- md5.write(&toXYZD50, sizeof(toXYZD50));
- static_assert(sizeof(fn) == sizeof(float) * 7, "packed");
- md5.write(&fn, sizeof(fn));
- SkMD5::Digest digest = md5.finish();
- char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
- for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
- uint8_t byte = digest.data[i];
- *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4];
- *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF];
- }
- SkASSERT(ptr == dst + kICCDescriptionTagSize);
+static sk_sp<SkData> write_text_tag(const std::string& text) {
+ uint32_t header[] = {
+ SkEndian_SwapBE32(kTAG_TextType), // Type signature
+ 0, // Reserved
+ SkEndian_SwapBE32(1), // Number of records
+ SkEndian_SwapBE32(12), // Record size (must be 12)
+ SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
+ SkEndian_SwapBE32(2 * text.length()), // Length of string in bytes
+ SkEndian_SwapBE32(28), // Offset of string
+ };
+ SkDynamicMemoryWStream s;
+ s.write(header, sizeof(header));
+ for (size_t i = 0; i < text.length(); i++) {
+ // Convert ASCII to big-endian UTF-16.
+ s.write8(0);
+ s.write8(text[i]);
}
+ s.padToAlign4();
+ return s.detachAsData();
}
-sk_sp<SkData> write_desc_tag(const skcms_TransferFunction& fn, const skcms_Matrix3x3& toXYZD50) {
- sk_sp<SkData> result =
- SkData::MakeUninitialized(sizeof(kDescriptionTagHeader) + kDescriptionTagBodySize);
- uint8_t* ptr = reinterpret_cast<uint8_t*>(result->writable_data());
- memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
- ptr += sizeof(kDescriptionTagHeader);
- {
- char colorProfileTag[kICCDescriptionTagSize];
- get_color_profile_tag(colorProfileTag, fn, toXYZD50);
-
- // ASCII --> big-endian UTF-16.
- for (size_t i = 0; i < kICCDescriptionTagSize; i++) {
- *ptr++ = 0;
- *ptr++ = colorProfileTag[i];
- }
- }
- return result;
-}
-
-sk_sp<SkData> write_cprt_tag() {
- sk_sp<SkData> result =
- SkData::MakeUninitialized(sizeof(kCopyrightTagHeader) + sizeof(kCopyrightTagBody));
- uint8_t* ptr = reinterpret_cast<uint8_t*>(result->writable_data());
- memcpy(ptr, kCopyrightTagHeader, sizeof(kCopyrightTagHeader));
- ptr += sizeof(kCopyrightTagHeader);
- memcpy(ptr, kCopyrightTagBody, sizeof(kCopyrightTagBody));
- ptr += sizeof(kCopyrightTagBody);
- return result;
+static sk_sp<SkData> write_cicp_tag(uint32_t primaries, uint32_t trfn) {
+ SkDynamicMemoryWStream s;
+ s.write32(SkEndian_SwapBE32(kTAG_cicp)); // Type signature
+ s.write32(0); // Reserved
+ s.write8(primaries); // Color primaries
+ s.write8(trfn); // Transfer characteristics
+ s.write8(0); // RGB matrix
+ s.write8(1); // Full range
+ return s.detachAsData();
}
sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn, const skcms_Matrix3x3& toXYZD50) {
- // We can't encode HDR transfer functions in ICC
- // TODO(crbug.com/1366315): Use CICP tag for HDR transfer functions.
+ // Compute the CICP primaries and transfer function, if they can be
+ // identified.
+ uint32_t cicp_primaries = get_cicp_primaries(toXYZD50);
+ uint32_t cicp_trfn = get_cicp_trfn(fn);
if (classify_transfer_fn(fn) != sRGBish_TF) {
- return nullptr;
+ // Non-sRGB-ish transfer functions can only be represented by CICP. IF
+ // the transfer function is not sRGB-ish, and we don't have a CICP
+ // representation, then fail.
+ if (!cicp_primaries || !cicp_trfn) {
+ return nullptr;
+ }
}
+
std::vector<std::pair<uint32_t, sk_sp<SkData>>> tags;
// Compute profile description tag
- tags.emplace_back(kTAG_desc, write_desc_tag(fn, toXYZD50));
+ std::string description = get_desc_string(fn, toXYZD50, cicp_trfn, cicp_primaries);
+ tags.emplace_back(kTAG_desc, write_text_tag(description));
// Compute XYZ tags
tags.emplace_back(kTAG_rXYZ, write_xyz_tag(toXYZD50, 0));
tags.emplace_back(kTAG_gXYZ, write_xyz_tag(toXYZD50, 1));
tags.emplace_back(kTAG_bXYZ, write_xyz_tag(toXYZD50, 2));
- // Compute TRC tags. Use empty data to indicate that the entry should use
- // the previous tag's data.
- tags.emplace_back(kTAG_rTRC, write_trc_tag(fn));
+ // If this is an HLG or PQ profile, include a CICP tag.
+ bool has_cicp = false;
+ if (cicp_trfn == kCICPTrfnPQ || cicp_trfn == kCICPTrfnHLG) {
+ has_cicp = true;
+ tags.emplace_back(kTAG_cicp, write_cicp_tag(cicp_primaries, cicp_trfn));
+
+ // Use sRGB as the transfer function.
+ // TODO(https://crbug.com/1366315): Provide a LUT based transform to
+ // perform tone mapping.
+ tags.emplace_back(kTAG_rTRC, write_para_tag(SkNamedTransferFn::kSRGB));
+ } else {
+ tags.emplace_back(kTAG_rTRC, write_para_tag(fn));
+ }
+ // Use empty data to indicate that the entry should use the previous tag's
+ // data.
tags.emplace_back(kTAG_gTRC, SkData::MakeEmpty());
tags.emplace_back(kTAG_bTRC, SkData::MakeEmpty());
@@ -312,7 +359,7 @@
tags.emplace_back(kTAG_wtpt, write_wtpt_tag());
// Compute copyright tag
- tags.emplace_back(kTAG_cprt, write_cprt_tag());
+ tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2016"));
// Compute the size of the profile.
size_t tag_data_size = 0;
@@ -323,17 +370,23 @@
size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
// Write the header.
+ ICCHeader header;
+ header.size = SkEndian_SwapBE32(profile_size);
+ header.tag_count = SkEndian_SwapBE32(tags.size());
+ if (has_cicp) {
+ header.version = SkEndian_SwapBE32(0x04400000);
+ }
+ static_assert(sizeof(header) == kICCHeaderSize);
+
SkAutoMalloc profile(profile_size);
uint8_t* ptr = (uint8_t*)profile.get();
- memcpy(ptr, kICCHeader, sizeof(kICCHeader));
- *reinterpret_cast<uint32_t*>(ptr + kICCHeaderSizeoOffset) = SkEndian_SwapBE32(profile_size);
- *reinterpret_cast<uint32_t*>(ptr + kICCHeaderNumEntriesOffset) = SkEndian_SwapBE32(tags.size());
- ptr += sizeof(kICCHeader);
+ memcpy(ptr, &header, sizeof(header));
+ ptr += sizeof(header);
// Write the tag table. Track the offset and size of the previous tag to
// compute each tag's offset. An empty SkData indicates that the previous
// tag is to be reused.
- size_t last_tag_offset = sizeof(kICCHeader) + tag_table_size;
+ size_t last_tag_offset = sizeof(header) + tag_table_size;
size_t last_tag_size = 0;
for (const auto& tag : tags) {
if (!tag.second->isEmpty()) {
diff --git a/src/images/SkImageEncoderFns.h b/src/images/SkImageEncoderFns.h
index 6ce9c24..56417e1 100644
--- a/src/images/SkImageEncoderFns.h
+++ b/src/images/SkImageEncoderFns.h
@@ -186,9 +186,10 @@
return nullptr;
}
- skcms_TransferFunction fn;
skcms_Matrix3x3 toXYZD50;
- if (cs->isNumericalTransferFn(&fn) && cs->toXYZD50(&toXYZD50)) {
+ if (cs->toXYZD50(&toXYZD50)) {
+ skcms_TransferFunction fn;
+ cs->transferFn(&fn);
return SkWriteICCProfile(fn, toXYZD50);
}
return nullptr;
diff --git a/tests/ICCTest.cpp b/tests/ICCTest.cpp
index 07e4277..1415bcf 100644
--- a/tests/ICCTest.cpp
+++ b/tests/ICCTest.cpp
@@ -18,9 +18,34 @@
if (sk_sp<SkData> profile = GetResourceAsData("icc_profiles/AdobeRGB1998.icc")) {
skcms_ICCProfile parsed;
REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed));
+ REPORTER_ASSERT(r, !parsed.has_CICP);
auto got = SkColorSpace::Make(parsed);
auto want = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB);
REPORTER_ASSERT(r, SkColorSpace::Equals(got.get(), want.get()));
}
}
+
+DEF_TEST(PQ, r) {
+ SK_API sk_sp<SkData> profile =
+ SkWriteICCProfile(SkNamedTransferFn::kPQ, SkNamedGamut::kRec2020);
+ skcms_ICCProfile parsed;
+ REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed));
+ REPORTER_ASSERT(r, parsed.has_CICP);
+ REPORTER_ASSERT(r, parsed.CICP.color_primaries == 9);
+ REPORTER_ASSERT(r, parsed.CICP.transfer_characteristics == 16);
+ REPORTER_ASSERT(r, parsed.CICP.matrix_coefficients == 0);
+ REPORTER_ASSERT(r, parsed.CICP.video_full_range_flag == 1);
+}
+
+DEF_TEST(HLG, r) {
+ SK_API sk_sp<SkData> profile =
+ SkWriteICCProfile(SkNamedTransferFn::kHLG, SkNamedGamut::kDisplayP3);
+ skcms_ICCProfile parsed;
+ REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed));
+ REPORTER_ASSERT(r, parsed.has_CICP);
+ REPORTER_ASSERT(r, parsed.CICP.color_primaries == 12);
+ REPORTER_ASSERT(r, parsed.CICP.transfer_characteristics == 18);
+ REPORTER_ASSERT(r, parsed.CICP.matrix_coefficients == 0);
+ REPORTER_ASSERT(r, parsed.CICP.video_full_range_flag == 1);
+}