Index: src/codec/SkPngCodec.cpp |
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp |
index f946876af2734dd7f660066d68efaa0e91585363..d3aa5e7af44e928c63312d371db1779e8f6739cb 100644 |
--- a/src/codec/SkPngCodec.cpp |
+++ b/src/codec/SkPngCodec.cpp |
@@ -165,6 +165,78 @@ bool SkPngCodec::IsPng(const char* buf, size_t bytesRead) { |
return !png_sig_cmp((png_bytep) buf, (png_size_t)0, bytesRead); |
} |
+// Returns a colorSpace object that represents any color space information in |
+// the encoded data. If the encoded data contains no color space, this will |
+// return NULL. |
+SkColorSpace* read_color_space(png_structp png_ptr, png_infop info_ptr) { |
+ |
+ // First check for an ICC profile |
+ png_charp name; |
scroggo
2016/02/24 18:31:36
Will png_get_iCCP let you pass nullptr for name? I
msarett
2016/02/29 21:47:54
We need to pass these in or png_get_iCCP does noth
|
+ int compression; |
scroggo
2016/02/24 18:31:36
Do we need this variable?
msarett
2016/02/29 21:47:55
We need to pass these in or png_get_iCCP does noth
|
+ png_bytep profile; |
+ png_uint_32 length; |
+ if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile, |
+ &length)) { |
+ |
+ // Compression can only ever be "deflate", so this is uninteresting. |
scroggo
2016/02/24 18:31:36
I didn't understand this comment the first time ar
msarett
2016/02/29 21:47:55
libpng has already decompressed the profile for us
|
+ // Could knowing the name of the profile ever be interesting? Maybe for debugging? |
scroggo
2016/02/24 18:31:36
I could see it being useful in theory. No idea how
msarett
2016/02/29 21:47:55
Yes I think that'd be the place to put it.
|
+ return SkColorSpace::NewICC(profile, length); |
+ } |
+ |
+ // Second, check for sRGB. |
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { |
+ |
+ // Here we are ignoring the sRGB "intent". Should we use the "intent"? |
scroggo
2016/02/24 18:31:36
FIXME/TODO? What is the "intent"?
msarett
2016/02/29 21:47:54
Done.
|
+ return SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); |
+ } |
+ |
+ // Next, check for chromaticities. |
+ png_fixed_point XYZ[9]; |
+ SkFloat3x3 toXYZD50; |
+ png_fixed_point gamma; |
+ SkFloat3 gammas; |
+ if (png_get_cHRM_XYZ_fixed(png_ptr, info_ptr, &XYZ[0], &XYZ[1], &XYZ[2], &XYZ[3], &XYZ[4], |
+ &XYZ[5], &XYZ[6], &XYZ[7], &XYZ[8])) { |
+ |
+ // This could be wrong. I don't see any guarantee from the PNG specification that |
scroggo
2016/02/24 18:31:36
FIXME? Maybe specify how you came up with this com
msarett
2016/02/29 21:47:54
The computation isn't wrong. What might be wrong
|
+ // these XYZ values will be D50. In fact, libpng will set its XYZ values to D65 |
+ // values if it sees an sRGB chunk. |
scroggo
2016/02/24 18:31:36
It seems like we'll only reach here if we did not
msarett
2016/02/29 21:47:54
Yes you're right, the comment is unclear. I'll re
|
+ for (int i = 0; i < 9; i++) { |
+ toXYZD50.fMat[i] = ((float) XYZ[i]) * 0.00001f; |
+ } |
+ |
+ if (!(PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma))) { |
+ // If the image does not specify gamma, let's choose linear. Should we default |
+ // to sRGB? Instead of choosing a default, should we just give up on the color |
+ // profile? |
+ gamma = PNG_GAMMA_LINEAR; |
+ } |
+ gammas.fVec[0] = gammas.fVec[1] = gammas.fVec[2] = ((float) gamma) * 0.00001f; |
+ |
+ return SkColorSpace::NewRGB(toXYZD50, gammas); |
+ } |
+ |
+ // Last, check for gamma. |
+ if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { |
+ |
+ // Guess a default value for cHRM? Or should we just give up? |
+ // Here we use the identity matrix as a default. |
scroggo
2016/02/24 18:31:36
Might want a method on SkFloat3x3 to set to the id
msarett
2016/02/29 21:47:55
I'll add a FIXME. Don't want to do this yet since
scroggo
2016/02/29 22:18:49
sgtm. FWIW, even if it's not the default, we could
|
+ memset(toXYZD50.fMat, 0, 9 * sizeof(float)); |
+ toXYZD50.fMat[0] = toXYZD50.fMat[4] = toXYZD50.fMat[8] = 1.0f; |
+ |
+ // Set the gammas. |
+ gammas.fVec[0] = gammas.fVec[1] = gammas.fVec[2] = ((float) gamma) * 0.00001f; |
+ |
+ return SkColorSpace::NewRGB(toXYZD50, gammas); |
+ } |
+ |
+ // Finally, what should we do if there is no color space information in the PNG? |
+ // The specification says that this indicates "gamma is unknown" and that the |
+ // "color is device dependent". I'm assuming we can represent this with NULL. |
+ // But should we guess sRGB? Are most images intended to be sRGB? |
+ return nullptr; |
+} |
+ |
// Reads the header and initializes the output fields, if not NULL. |
// |
// @param stream Input data. Will be read to get enough information to properly |
@@ -182,12 +254,17 @@ bool SkPngCodec::IsPng(const char* buf, size_t bytesRead) { |
// bit depth of the encoded image on success. |
// @param numberPassesPtr Optional output variable. If non-NULL, will be set to |
// the number_passes of the encoded image on success. |
+// @param colorSpace Optional output variable. If non-NULL, will be set to a |
+// colorSpace object that represents any color space information in the |
+// encoded data. If the encoded data contains no color space, information |
+// this will be set to NULL. |
// @return true on success, in which case the caller is responsible for calling |
// png_destroy_read_struct(png_ptrp, info_ptrp). |
// If it returns false, the passed in fields (except stream) are unchanged. |
static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
png_structp* png_ptrp, png_infop* info_ptrp, |
- SkImageInfo* imageInfo, int* bitDepthPtr, int* numberPassesPtr) { |
+ SkImageInfo* imageInfo, int* bitDepthPtr, int* numberPassesPtr, |
+ SkColorSpace** colorSpace) { |
// The image is known to be a PNG. Decode enough to know the SkImageInfo. |
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, |
sk_error_fn, sk_warning_fn); |
@@ -310,6 +387,10 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
*numberPassesPtr = numberPasses; |
} |
+ if (colorSpace) { |
+ *colorSpace = read_color_space(png_ptr, info_ptr); |
+ } |
+ |
SkColorProfileType profileType = kLinear_SkColorProfileType; |
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { |
profileType = kSRGB_SkColorProfileType; |
@@ -330,8 +411,9 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
} |
SkPngCodec::SkPngCodec(const SkImageInfo& info, SkStream* stream, SkPngChunkReader* chunkReader, |
- png_structp png_ptr, png_infop info_ptr, int bitDepth, int numberPasses) |
- : INHERITED(info, stream) |
+ png_structp png_ptr, png_infop info_ptr, int bitDepth, int numberPasses, |
+ SkColorSpace* colorSpace) |
+ : INHERITED(info, stream, colorSpace) |
, fPngChunkReader(SkSafeRef(chunkReader)) |
, fPng_ptr(png_ptr) |
, fInfo_ptr(info_ptr) |
@@ -434,7 +516,7 @@ bool SkPngCodec::onRewind() { |
png_structp png_ptr; |
png_infop info_ptr; |
if (!read_header(this->stream(), fPngChunkReader.get(), &png_ptr, &info_ptr, |
- nullptr, nullptr, nullptr)) { |
+ nullptr, nullptr, nullptr, nullptr)) { |
scroggo
2016/02/24 18:31:36
You can leave off this last nullptr if you call re
msarett
2016/02/29 21:47:55
Done.
|
return false; |
} |
@@ -541,8 +623,9 @@ uint32_t SkPngCodec::onGetFillValue(SkColorType colorType) const { |
class SkPngScanlineDecoder : public SkPngCodec { |
public: |
SkPngScanlineDecoder(const SkImageInfo& srcInfo, SkStream* stream, |
- SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr, int bitDepth) |
- : INHERITED(srcInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth, 1) |
+ SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr, int bitDepth, |
+ SkColorSpace* colorSpace) |
+ : INHERITED(srcInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth, 1, colorSpace) |
, fSrcRow(nullptr) |
{} |
@@ -607,8 +690,9 @@ class SkPngInterlacedScanlineDecoder : public SkPngCodec { |
public: |
SkPngInterlacedScanlineDecoder(const SkImageInfo& srcInfo, SkStream* stream, |
SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr, |
- int bitDepth, int numberPasses) |
- : INHERITED(srcInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth, numberPasses) |
+ int bitDepth, int numberPasses, SkColorSpace* colorSpace) |
+ : INHERITED(srcInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth, numberPasses, |
+ colorSpace) |
, fHeight(-1) |
, fCanSkipRewind(false) |
{ |
@@ -733,17 +817,19 @@ SkCodec* SkPngCodec::NewFromStream(SkStream* stream, SkPngChunkReader* chunkRead |
SkImageInfo imageInfo; |
int bitDepth; |
int numberPasses; |
+ SkColorSpace* colorSpace = nullptr; |
if (!read_header(stream, chunkReader, &png_ptr, &info_ptr, &imageInfo, &bitDepth, |
- &numberPasses)) { |
+ &numberPasses, &colorSpace)) { |
scroggo
2016/02/24 18:31:36
I think read_color_space looks like a good standal
msarett
2016/02/29 21:47:54
Done.
|
return nullptr; |
} |
if (1 == numberPasses) { |
return new SkPngScanlineDecoder(imageInfo, streamDeleter.detach(), chunkReader, |
- png_ptr, info_ptr, bitDepth); |
+ png_ptr, info_ptr, bitDepth, colorSpace); |
} |
return new SkPngInterlacedScanlineDecoder(imageInfo, streamDeleter.detach(), chunkReader, |
- png_ptr, info_ptr, bitDepth, numberPasses); |
+ png_ptr, info_ptr, bitDepth, numberPasses, |
+ colorSpace); |
} |