Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(103)

Unified Diff: src/core/SkColorSpace.cpp

Issue 2001203003: Write ICC profiles from SkColorSpace object (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: std::move Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: src/core/SkColorSpace.cpp
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index 8f67a7f9580fed78ea359d2fae199e339fda7f6a..1cbb1922d3186b037cccfc66285cad73aa7f0284 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -7,6 +7,7 @@
#include "SkColorSpace.h"
#include "SkColorSpace_Base.h"
+#include "SkEndian.h"
#include "SkOnce.h"
static bool color_space_almost_equal(float a, float b) {
@@ -15,27 +16,30 @@ static bool color_space_almost_equal(float a, float b) {
//////////////////////////////////////////////////////////////////////////////////////////////////
-SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named)
+SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named,
+ sk_sp<SkData> profileData)
: fGammaNamed(kNonStandard_GammaNamed)
, fToXYZD50(toXYZD50)
, fNamed(named)
+ , fProfileData(std::move(profileData))
{}
SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkGammas> gammas, const SkMatrix44& toXYZD50,
- Named named)
- : INHERITED(kNonStandard_GammaNamed, toXYZD50, named)
+ Named named, sk_sp<SkData> profileData)
+ : INHERITED(kNonStandard_GammaNamed, toXYZD50, named, std::move(profileData))
, fGammas(gammas)
{}
SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkGammas> gammas, GammaNamed gammaNamed,
- const SkMatrix44& toXYZD50, Named named)
- : INHERITED(gammaNamed, toXYZD50, named)
+ const SkMatrix44& toXYZD50, Named named,
+ sk_sp<SkData> profileData)
+ : INHERITED(gammaNamed, toXYZD50, named, std::move(profileData))
, fGammas(gammas)
{}
SkColorSpace_Base::SkColorSpace_Base(SkColorLookUpTable* colorLUT, sk_sp<SkGammas> gammas,
- const SkMatrix44& toXYZD50)
- : INHERITED(kNonStandard_GammaNamed, toXYZD50, kUnknown_Named)
+ const SkMatrix44& toXYZD50, sk_sp<SkData> profileData)
+ : INHERITED(kNonStandard_GammaNamed, toXYZD50, kUnknown_Named, std::move(profileData))
, fColorLUT(colorLUT)
, fGammas(gammas)
{}
@@ -82,7 +86,12 @@ static SkGammas* g2Dot2CurveGammas;
static SkOnce gLinearGammasOnce;
static SkGammas* gLinearGammas;
-sk_sp<SkColorSpace> SkColorSpace::NewRGB(float gammaVals[3], const SkMatrix44& toXYZD50) {
+sk_sp<SkColorSpace> SkColorSpace::NewRGB(const float gammaVals[3], const SkMatrix44& toXYZD50) {
+ return SkColorSpace_Base::NewRGB(gammaVals, toXYZD50, nullptr);
+}
+
+sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(const float gammaVals[3], const SkMatrix44& toXYZD50,
+ sk_sp<SkData> profileData) {
sk_sp<SkGammas> gammas = nullptr;
GammaNamed gammaNamed = kNonStandard_GammaNamed;
@@ -116,7 +125,8 @@ sk_sp<SkColorSpace> SkColorSpace::NewRGB(float gammaVals[3], const SkMatrix44& t
if (!gammas) {
gammas = sk_sp<SkGammas>(new SkGammas(gammaVals[0], gammaVals[1], gammaVals[2]));
}
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammas, gammaNamed, toXYZD50, kUnknown_Named));
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammas, gammaNamed, toXYZD50, kUnknown_Named,
+ std::move(profileData)));
}
sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
@@ -135,7 +145,7 @@ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
srgbToxyzD50.set3x3ColMajorf(gSRGB_toXYZD50);
sRGB = new SkColorSpace_Base(sk_ref_sp(g2Dot2CurveGammas), k2Dot2Curve_GammaNamed,
- srgbToxyzD50, kSRGB_Named);
+ srgbToxyzD50, kSRGB_Named, nullptr);
});
return sk_ref_sp(sRGB);
}
@@ -149,7 +159,7 @@ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
adobergbToxyzD50.set3x3ColMajorf(gAdobeRGB_toXYZD50);
adobeRGB = new SkColorSpace_Base(sk_ref_sp(g2Dot2CurveGammas),
k2Dot2Curve_GammaNamed, adobergbToxyzD50,
- kAdobeRGB_Named);
+ kAdobeRGB_Named, nullptr);
});
return sk_ref_sp(adobeRGB);
}
@@ -201,6 +211,11 @@ static const size_t kICCHeaderSize = 132;
static const size_t kICCTagTableEntrySize = 12;
static const uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' ');
+static const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
scroggo 2016/05/24 15:57:50 Now that we don't have to worry about VS 2013, sho
msarett 2016/05/24 18:38:06 Sure sounds good to me.
+static const uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
+static const uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
+static const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
+static const uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p');
struct ICCProfileHeader {
uint32_t fSize;
@@ -266,9 +281,6 @@ struct ICCProfileHeader {
// These are the three basic classes of profiles that we might expect to see embedded
// in images. Four additional classes exist, but they generally are used as a convenient
// way for CMMs to store calculated transforms.
- const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
- const uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
- const uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
return_if_false(fProfileClass == kDisplay_Profile ||
fProfileClass == kInput_Profile ||
fProfileClass == kOutput_Profile,
@@ -280,10 +292,9 @@ struct ICCProfileHeader {
// TODO (msarett):
// All the profiles we've tested so far use XYZ as the profile connection space.
- const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
- return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad signature");
+ return_if_false(fSignature == kACSP_Signature, "Bad signature");
// TODO (msarett):
// Should we treat different rendering intents differently?
@@ -705,13 +716,18 @@ bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* t
return true;
}
-sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
- const uint8_t* ptr = (const uint8_t*) base;
-
+sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
if (len < kICCHeaderSize) {
return_null("Data is not large enough to contain an ICC profile");
}
+ // Create our own copy of the input.
+ 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;
+
// Read the ICC profile header and check to make sure that it is valid.
ICCProfileHeader header;
header.init(ptr, len);
@@ -793,9 +809,10 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
gammaVals[0] = gammas->fRed.fValue;
gammaVals[1] = gammas->fGreen.fValue;
gammaVals[2] = gammas->fBlue.fValue;
- return SkColorSpace::NewRGB(gammaVals, mat);
+ return SkColorSpace_Base::NewRGB(gammaVals, mat, std::move(data));
} else {
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammas, mat, kUnknown_Named));
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammas, mat, kUnknown_Named,
+ std::move(data)));
}
}
@@ -814,7 +831,7 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
std::move(curves[2])));
if (colorLUT->fTable) {
return sk_sp<SkColorSpace>(new SkColorSpace_Base(colorLUT.release(), gammas,
- toXYZ));
+ toXYZ, std::move(data)));
} else if (gammas->isValues()) {
// When we have values, take advantage of the NewFromRGB initializer.
// This allows us to check for canonical sRGB and Adobe RGB.
@@ -822,10 +839,11 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
gammaVals[0] = gammas->fRed.fValue;
gammaVals[1] = gammas->fGreen.fValue;
gammaVals[2] = gammas->fBlue.fValue;
- return SkColorSpace::NewRGB(gammaVals, toXYZ);
+ return SkColorSpace_Base::NewRGB(gammaVals, toXYZ, std::move(data));
} else {
return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammas, toXYZ,
- kUnknown_Named));
+ kUnknown_Named,
+ std::move(data)));
}
}
@@ -836,3 +854,202 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
return_null("ICC profile contains unsupported colorspace");
}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// We will write a profile with the minimum nine required tags.
+static const uint32_t kICCNumEntries = 9;
+
+static const uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
+static const uint32_t kTAG_desc_Bytes = 12;
+static const uint32_t kTAG_desc_Offset = kICCHeaderSize + kICCNumEntries * kICCTagTableEntrySize;
+
+static const uint32_t kTAG_XYZ_Bytes = 20;
+static const uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
+static const uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
+static const uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
+
+static const uint32_t kTAG_TRC_Bytes = 14;
+static const uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
+static const uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
+static const uint32_t kTAG_bTRC_Offset = kTAG_gTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
+
+static const uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
+static const uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
+
+static const uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
+static const uint32_t kTAG_cprt_Bytes = 12;
+static const uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
+
+static const uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
+
+static const uint32_t gICCHeader[kICCHeaderSize / 4] {
+ SkEndian_SwapBE32(kICCProfileSize), // Size of the profile
+ 0, // Preferred CMM type (ignored)
+ SkEndian_SwapBE32(0x02100000), // Version 2.1
+ 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)
+ SkEndian_SwapBE32(kICCNumEntries), // Number of tags
+};
+
+static const uint32_t gICCTagTable[3 * kICCNumEntries] {
+ // Profile description
+ SkEndian_SwapBE32(kTAG_desc),
+ SkEndian_SwapBE32(kTAG_desc_Offset),
+ SkEndian_SwapBE32(kTAG_desc_Bytes),
+
+ // rXYZ
+ SkEndian_SwapBE32(kTAG_rXYZ),
+ SkEndian_SwapBE32(kTAG_rXYZ_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // gXYZ
+ SkEndian_SwapBE32(kTAG_gXYZ),
+ SkEndian_SwapBE32(kTAG_gXYZ_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // bXYZ
+ SkEndian_SwapBE32(kTAG_bXYZ),
+ SkEndian_SwapBE32(kTAG_bXYZ_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // rTRC
+ SkEndian_SwapBE32(kTAG_rTRC),
+ SkEndian_SwapBE32(kTAG_rTRC_Offset),
+ SkEndian_SwapBE32(kTAG_TRC_Bytes),
+
+ // gTRC
+ SkEndian_SwapBE32(kTAG_gTRC),
+ SkEndian_SwapBE32(kTAG_gTRC_Offset),
+ SkEndian_SwapBE32(kTAG_TRC_Bytes),
+
+ // bTRC
+ SkEndian_SwapBE32(kTAG_bTRC),
+ SkEndian_SwapBE32(kTAG_bTRC_Offset),
+ SkEndian_SwapBE32(kTAG_TRC_Bytes),
+
+ // White point
+ SkEndian_SwapBE32(kTAG_wtpt),
+ SkEndian_SwapBE32(kTAG_wtpt_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // Copyright
+ SkEndian_SwapBE32(kTAG_cprt),
+ SkEndian_SwapBE32(kTAG_cprt_Offset),
+ SkEndian_SwapBE32(kTAG_cprt_Bytes),
+};
+
+static const uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c');
+static const uint32_t gEmptyTextTag[3] {
+ SkEndian_SwapBE32(kTAG_TextType), // Type signature
+ 0, // Reserved
+ 0, // Zero records
+};
+
+static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int row) {
+ ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
+ ptr[1] = 0;
+ ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 0)));
+ ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 1)));
+ ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 2)));
+}
+
+static void write_trc_tag(uint32_t* ptr, float value) {
+ ptr[0] = SkEndian_SwapBE32(kTAG_CurveType);
+ ptr[1] = 0;
+
+ // Gamma will be specified with a single value.
+ ptr[2] = SkEndian_SwapBE32(1);
+
+ // Convert gamma to 16-bit fixed point.
+ uint16_t* ptr16 = (uint16_t*) (ptr + 3);
+ ptr16[0] = SkEndian_SwapBE16((uint16_t) (value * 256.0f));
+
+ // Pad tag with zero.
+ ptr16[1] = 0;
+}
+
+sk_sp<SkData> SkColorSpace::writeToICC() {
+ // Return if this object was created from a profile, or if we have already serialized
+ // the profile.
+ if (fProfileData) {
+ return fProfileData;
+ }
+
+ // The client may create an SkColorSpace using an SkMatrix44, but currently we only
+ // support writing profiles with 3x3 matrices.
+ // TODO (msarett): Fix this!
+ if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) ||
+ 0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) ||
+ 0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3))
+ {
+ return nullptr;
+ }
+
+ void* profile = sk_malloc_throw(kICCProfileSize);
scroggo 2016/05/24 15:57:50 Should this be in an auto deleter of some kind?
msarett 2016/05/24 18:38:06 Yeah I think that's probably safer, though at the
+ uint8_t* ptr = (uint8_t*) profile;
+
+ // Write profile header
+ memcpy(ptr, gICCHeader, sizeof(gICCHeader));
+ ptr += sizeof(gICCHeader);
+
+ // Write tag table
+ memcpy(ptr, gICCTagTable, sizeof(gICCTagTable));
+ ptr += sizeof(gICCTagTable);
+
+ // Write profile description tag
+ memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
+ ptr += sizeof(gEmptyTextTag);
+
+ // Write XYZ tags
+ write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0);
+ ptr += kTAG_XYZ_Bytes;
+ write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1);
+ ptr += kTAG_XYZ_Bytes;
+ write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2);
+ ptr += kTAG_XYZ_Bytes;
+
+ // Write TRC tags
+ SkASSERT(as_CSB(this)->fGammas->fRed.isValue());
+ write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fRed.fValue);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ SkASSERT(as_CSB(this)->fGammas->fGreen.isValue());
+ write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fGreen.fValue);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ SkASSERT(as_CSB(this)->fGammas->fBlue.isValue());
+ write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fBlue.fValue);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+
+ // Write white point tag
+ uint32_t* ptr32 = (uint32_t*) ptr;
+ ptr32[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
+ ptr32[1] = 0;
+ // TODO (msarett): These values correspond to the D65 white point. This may not always be
+ // correct.
+ ptr32[2] = SkEndian_SwapBE32(0x0000f351);
+ ptr32[3] = SkEndian_SwapBE32(0x00010000);
+ ptr32[4] = SkEndian_SwapBE32(0x000116cc);
+ ptr += kTAG_XYZ_Bytes;
+
+ // Write copyright tag
+ memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
+
+ // We choose to hold onto the profile here, in case we need to serialize again.
msarett 2016/05/24 15:36:58 I'm not sure if this is the right decision...
scroggo 2016/05/24 15:57:50 Add a comment? (Or leave it out for now?)
msarett 2016/05/24 18:38:06 Leaving it out for now... That allows this functi
+ fProfileData = std::move(SkData::MakeFromMalloc(profile, kICCProfileSize));
+ return fProfileData;
+}

Powered by Google App Engine
This is Rietveld 408576698