aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gyp/skia_for_android_framework_defines.gypi1
-rw-r--r--include/codec/SkCodec.h11
-rw-r--r--include/core/SkImageDecoder.h34
-rw-r--r--include/core/SkPngChunkReader.h45
-rw-r--r--src/codec/SkCodec.cpp35
-rw-r--r--src/codec/SkCodec_libpng.cpp79
-rw-r--r--src/codec/SkCodec_libpng.h22
-rw-r--r--src/images/SkImageDecoder.cpp2
-rw-r--r--src/images/SkImageDecoder_libpng.cpp7
-rw-r--r--src/ports/SkImageDecoder_empty.cpp3
-rw-r--r--tests/CodexTest.cpp160
11 files changed, 327 insertions, 72 deletions
diff --git a/gyp/skia_for_android_framework_defines.gypi b/gyp/skia_for_android_framework_defines.gypi
index 773c530d0d..825429b9a0 100644
--- a/gyp/skia_for_android_framework_defines.gypi
+++ b/gyp/skia_for_android_framework_defines.gypi
@@ -19,6 +19,7 @@
'SK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS',
'SK_SUPPORT_LEGACY_GRADIENT_DITHERING',
'SK_IGNORE_GL_TEXTURE_TARGET',
+ 'SK_LEGACY_PEEKER',
],
},
}
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index d90fea8ced..dffab6b377 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -17,6 +17,7 @@
#include "SkTypes.h"
class SkData;
+class SkPngChunkReader;
class SkSampler;
/**
@@ -28,18 +29,24 @@ public:
* If this stream represents an encoded image that we know how to decode,
* return an SkCodec that can decode it. Otherwise return NULL.
*
+ * If SkPngChunkReader is not NULL, take a ref and pass it to libpng if
+ * the image is a png.
+ *
* If NULL is returned, the stream is deleted immediately. Otherwise, the
* SkCodec takes ownership of it, and will delete it when done with it.
*/
- static SkCodec* NewFromStream(SkStream*);
+ static SkCodec* NewFromStream(SkStream*, SkPngChunkReader* = NULL);
/**
* If this data represents an encoded image that we know how to decode,
* return an SkCodec that can decode it. Otherwise return NULL.
*
+ * If SkPngChunkReader is not NULL, take a ref and pass it to libpng if
+ * the image is a png.
+ *
* Will take a ref if it returns a codec, else will not affect the data.
*/
- static SkCodec* NewFromData(SkData*);
+ static SkCodec* NewFromData(SkData*, SkPngChunkReader* = NULL);
virtual ~SkCodec();
diff --git a/include/core/SkImageDecoder.h b/include/core/SkImageDecoder.h
index 144878c166..30323b59ee 100644
--- a/include/core/SkImageDecoder.h
+++ b/include/core/SkImageDecoder.h
@@ -10,11 +10,14 @@
#include "SkBitmap.h"
#include "SkImage.h"
+#include "SkPngChunkReader.h"
#include "SkRect.h"
#include "SkRefCnt.h"
#include "SkTRegistry.h"
#include "SkTypes.h"
+//#define SK_LEGACY_PEEKER
+
class SkStream;
class SkStreamRewindable;
@@ -126,23 +129,20 @@ public:
*/
bool getRequireUnpremultipliedColors() const { return fRequireUnpremultipliedColors; }
- /** \class Peeker
-
- Base class for optional callbacks to retrieve meta/chunk data out of
- an image as it is being decoded.
- */
- class Peeker : public SkRefCnt {
+#ifdef SK_LEGACY_PEEKER
+ // Android subclasses SkImageDecoder::Peeker, which has been changed into SkPngChunkReader.
+ // Temporarily use this class until Android can be updated to directly inherit from
+ // SkPngChunkReader.
+ class Peeker : public SkPngChunkReader {
public:
- /** Return true to continue decoding, or false to indicate an error, which
- will cause the decoder to not return the image.
- */
+ bool readChunk(const char tag[], const void* data, size_t length) final {
+ return this->peek(tag, data, length);
+ }
virtual bool peek(const char tag[], const void* data, size_t length) = 0;
- private:
- typedef SkRefCnt INHERITED;
};
-
- Peeker* getPeeker() const { return fPeeker; }
- Peeker* setPeeker(Peeker*);
+#endif
+ SkPngChunkReader* getPeeker() const { return fPeeker; }
+ SkPngChunkReader* setPeeker(SkPngChunkReader*);
/**
* By default, the codec will try to comply with the "pref" colortype
@@ -229,8 +229,8 @@ public:
to allocate the memory from a cache, volatile memory, or even from
an existing bitmap's memory.
- If a Peeker is installed via setPeeker, it may be used to peek into
- meta data during the decode.
+ If an SkPngChunkReader is installed via setPeeker, it may be used to
+ peek into meta data during the decode.
*/
Result decode(SkStream*, SkBitmap* bitmap, SkColorType pref, Mode);
Result decode(SkStream* stream, SkBitmap* bitmap, Mode mode) {
@@ -350,7 +350,7 @@ protected:
SkColorType getPrefColorType(SrcDepth, bool hasAlpha) const;
private:
- Peeker* fPeeker;
+ SkPngChunkReader* fPeeker;
SkBitmap::Allocator* fAllocator;
int fSampleSize;
SkColorType fDefaultPref; // use if fUsePrefTable is false
diff --git a/include/core/SkPngChunkReader.h b/include/core/SkPngChunkReader.h
new file mode 100644
index 0000000000..f424dd8cfc
--- /dev/null
+++ b/include/core/SkPngChunkReader.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPngChunkReader_DEFINED
+#define SkPngChunkReader_DEFINED
+
+#include "SkTypes.h"
+#include "SkRefCnt.h"
+
+/**
+ * SkPngChunkReader
+ *
+ * Base class for optional callbacks to retrieve meta/chunk data out of a PNG
+ * encoded image as it is being decoded.
+ * Used by SkImageDecoder and SkCodec.
+ */
+class SkPngChunkReader : public SkRefCnt {
+public:
+ /**
+ * This will be called by the decoder when it sees an unknown chunk.
+ *
+ * Use by SkCodec:
+ * Depending on the location of the unknown chunks, this callback may be
+ * called by
+ * - the factory (NewFromStream/NewFromData)
+ * - getPixels
+ * - startScanlineDecode
+ * - the first call to getScanlines/skipScanlines
+ * The callback may be called from a different thread (e.g. if the SkCodec
+ * is passed to another thread), and it may be called multiple times, if
+ * the SkCodec is used multiple times.
+ *
+ * @param tag Name for this type of chunk.
+ * @param data Data to be interpreted by the subclass.
+ * @param length Number of bytes of data in the chunk.
+ * @return true to continue decoding, or false to indicate an error, which
+ * will cause the decoder to not return the image.
+ */
+ virtual bool readChunk(const char tag[], const void* data, size_t length) = 0;
+};
+#endif // SkPngChunkReader_DEFINED
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 071a4b8371..11eb1f9847 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -25,7 +25,6 @@ struct DecoderProc {
};
static const DecoderProc gDecoderProcs[] = {
- { SkPngCodec::IsPng, SkPngCodec::NewFromStream },
#if !defined(GOOGLE3)
{ SkJpegCodec::IsJpeg, SkJpegCodec::NewFromStream },
#endif
@@ -36,7 +35,8 @@ static const DecoderProc gDecoderProcs[] = {
{ SkWbmpCodec::IsWbmp, SkWbmpCodec::NewFromStream }
};
-SkCodec* SkCodec::NewFromStream(SkStream* stream) {
+SkCodec* SkCodec::NewFromStream(SkStream* stream,
+ SkPngChunkReader* chunkReader) {
if (!stream) {
return nullptr;
}
@@ -44,15 +44,24 @@ SkCodec* SkCodec::NewFromStream(SkStream* stream) {
SkAutoTDelete<SkStream> streamDeleter(stream);
SkAutoTDelete<SkCodec> codec(nullptr);
- for (uint32_t i = 0; i < SK_ARRAY_COUNT(gDecoderProcs); i++) {
- DecoderProc proc = gDecoderProcs[i];
- const bool correctFormat = proc.IsFormat(stream);
- if (!stream->rewind()) {
- return nullptr;
- }
- if (correctFormat) {
- codec.reset(proc.NewFromStream(streamDeleter.detach()));
- break;
+ // PNG is special, since we want to be able to supply an SkPngChunkReader.
+ // But this code follows the same pattern as the loop.
+ const bool isPng = SkPngCodec::IsPng(stream);
+ if (!stream->rewind()) {
+ return NULL;
+ }
+ if (isPng) {
+ codec.reset(SkPngCodec::NewFromStream(streamDeleter.detach(), chunkReader));
+ } else {
+ for (DecoderProc proc : gDecoderProcs) {
+ const bool correctFormat = proc.IsFormat(stream);
+ if (!stream->rewind()) {
+ return nullptr;
+ }
+ if (correctFormat) {
+ codec.reset(proc.NewFromStream(streamDeleter.detach()));
+ break;
+ }
}
}
@@ -68,11 +77,11 @@ SkCodec* SkCodec::NewFromStream(SkStream* stream) {
}
}
-SkCodec* SkCodec::NewFromData(SkData* data) {
+SkCodec* SkCodec::NewFromData(SkData* data, SkPngChunkReader* reader) {
if (!data) {
return nullptr;
}
- return NewFromStream(new SkMemoryStream(data));
+ return NewFromStream(new SkMemoryStream(data), reader);
}
SkCodec::SkCodec(const SkImageInfo& info, SkStream* stream)
diff --git a/src/codec/SkCodec_libpng.cpp b/src/codec/SkCodec_libpng.cpp
index 82c952e97b..355d493309 100644
--- a/src/codec/SkCodec_libpng.cpp
+++ b/src/codec/SkCodec_libpng.cpp
@@ -65,6 +65,14 @@ static void sk_read_fn(png_structp png_ptr, png_bytep data,
}
}
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
+ SkPngChunkReader* chunkReader = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr);
+ // readChunk() returning true means continue decoding
+ return chunkReader->readChunk((const char*)chunk->name, chunk->data, chunk->size) ? 1 : -1;
+}
+#endif
+
///////////////////////////////////////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////////////////////////////////////
@@ -211,14 +219,29 @@ bool SkPngCodec::IsPng(SkStream* stream) {
return true;
}
-// Reads the header, and initializes the passed in fields, if not nullptr (except
-// stream, which is passed to the read function).
-// Returns true on success, in which case the caller is responsible for calling
-// png_destroy_read_struct. If it returns false, the passed in fields (except
-// stream) are unchanged.
-static bool read_header(SkStream* stream, png_structp* png_ptrp,
- png_infop* info_ptrp, SkImageInfo* imageInfo,
- int* bitDepthPtr, int* numberPassesPtr) {
+// Reads the header and initializes the output fields, if not NULL.
+//
+// @param stream Input data. Will be read to get enough information to properly
+// setup the codec.
+// @param chunkReader SkPngChunkReader, for reading unknown chunks. May be NULL.
+// If not NULL, png_ptr will hold an *unowned* pointer to it. The caller is
+// expected to continue to own it for the lifetime of the png_ptr.
+// @param png_ptrp Optional output variable. If non-NULL, will be set to a new
+// png_structp on success.
+// @param info_ptrp Optional output variable. If non-NULL, will be set to a new
+// png_infop on success;
+// @param imageInfo Optional output variable. If non-NULL, will be set to
+// reflect the properties of the encoded image on success.
+// @param bitDepthPtr Optional output variable. If non-NULL, will be set to the
+// 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.
+// @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) {
// 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);
@@ -243,10 +266,14 @@ static bool read_header(SkStream* stream, png_structp* png_ptrp,
png_set_read_fn(png_ptr, static_cast<void*>(stream), sk_read_fn);
- // FIXME: This is where the old code hooks up the Peeker. Does it need to
- // be set this early? (i.e. where are the user chunks? early in the stream,
- // potentially?)
- // If it does, we need to figure out a way to set it here.
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+ // FIXME: Does this need to be installed so early?
+ // hookup our chunkReader so we can see any user-chunks the caller may be interested in
+ if (chunkReader) {
+ png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
+ png_set_read_user_chunk_fn(png_ptr, (png_voidp) chunkReader, sk_read_user_chunk);
+ }
+#endif
// The call to png_read_info() gives us all of the information from the
// PNG file before the first IDAT (image data chunk).
@@ -356,9 +383,10 @@ static bool read_header(SkStream* stream, png_structp* png_ptrp,
return true;
}
-SkPngCodec::SkPngCodec(const SkImageInfo& info, SkStream* stream,
+SkPngCodec::SkPngCodec(const SkImageInfo& info, SkStream* stream, SkPngChunkReader* chunkReader,
png_structp png_ptr, png_infop info_ptr, int bitDepth, int numberPasses)
: INHERITED(info, stream)
+ , fPngChunkReader(SkSafeRef(chunkReader))
, fPng_ptr(png_ptr)
, fInfo_ptr(info_ptr)
, fSrcConfig(SkSwizzler::kUnknown)
@@ -453,7 +481,8 @@ bool SkPngCodec::onRewind() {
png_structp png_ptr;
png_infop info_ptr;
- if (!read_header(this->stream(), &png_ptr, &info_ptr, nullptr, nullptr, nullptr)) {
+ if (!read_header(this->stream(), fPngChunkReader.get(), &png_ptr, &info_ptr,
+ nullptr, nullptr, nullptr)) {
return false;
}
@@ -602,8 +631,8 @@ bool SkPngCodec::onReallyHasAlpha() const {
class SkPngScanlineDecoder : public SkPngCodec {
public:
SkPngScanlineDecoder(const SkImageInfo& srcInfo, SkStream* stream,
- png_structp png_ptr, png_infop info_ptr, int bitDepth)
- : INHERITED(srcInfo, stream, png_ptr, info_ptr, bitDepth, 1)
+ SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr, int bitDepth)
+ : INHERITED(srcInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth, 1)
, fAlphaState(kUnknown_AlphaState)
, fSrcRow(nullptr)
{}
@@ -686,8 +715,9 @@ private:
class SkPngInterlacedScanlineDecoder : public SkPngCodec {
public:
SkPngInterlacedScanlineDecoder(const SkImageInfo& srcInfo, SkStream* stream,
- png_structp png_ptr, png_infop info_ptr, int bitDepth, int numberPasses)
- : INHERITED(srcInfo, stream, png_ptr, info_ptr, bitDepth, numberPasses)
+ SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr,
+ int bitDepth, int numberPasses)
+ : INHERITED(srcInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth, numberPasses)
, fAlphaState(kUnknown_AlphaState)
, fHeight(-1)
, fCanSkipRewind(false)
@@ -822,7 +852,7 @@ private:
typedef SkPngCodec INHERITED;
};
-SkCodec* SkPngCodec::NewFromStream(SkStream* stream) {
+SkCodec* SkPngCodec::NewFromStream(SkStream* stream, SkPngChunkReader* chunkReader) {
SkAutoTDelete<SkStream> streamDeleter(stream);
png_structp png_ptr;
png_infop info_ptr;
@@ -830,15 +860,16 @@ SkCodec* SkPngCodec::NewFromStream(SkStream* stream) {
int bitDepth;
int numberPasses;
- if (!read_header(stream, &png_ptr, &info_ptr, &imageInfo, &bitDepth, &numberPasses)) {
+ if (!read_header(stream, chunkReader, &png_ptr, &info_ptr, &imageInfo, &bitDepth,
+ &numberPasses)) {
return nullptr;
}
if (1 == numberPasses) {
- return new SkPngScanlineDecoder(imageInfo, streamDeleter.detach(), png_ptr, info_ptr,
- bitDepth);
+ return new SkPngScanlineDecoder(imageInfo, streamDeleter.detach(), chunkReader,
+ png_ptr, info_ptr, bitDepth);
}
- return new SkPngInterlacedScanlineDecoder(imageInfo, streamDeleter.detach(), png_ptr,
- info_ptr, bitDepth, numberPasses);
+ return new SkPngInterlacedScanlineDecoder(imageInfo, streamDeleter.detach(), chunkReader,
+ png_ptr, info_ptr, bitDepth, numberPasses);
}
diff --git a/src/codec/SkCodec_libpng.h b/src/codec/SkCodec_libpng.h
index 9809b0c39a..c2a5f4a707 100644
--- a/src/codec/SkCodec_libpng.h
+++ b/src/codec/SkCodec_libpng.h
@@ -7,6 +7,7 @@
#include "SkCodec.h"
#include "SkColorTable.h"
+#include "SkPngChunkReader.h"
#include "SkEncodedFormat.h"
#include "SkImageInfo.h"
#include "SkRefCnt.h"
@@ -21,7 +22,7 @@ public:
static bool IsPng(SkStream*);
// Assume IsPng was called and returned true.
- static SkCodec* NewFromStream(SkStream*);
+ static SkCodec* NewFromStream(SkStream*, SkPngChunkReader* = NULL);
virtual ~SkPngCodec();
@@ -41,7 +42,7 @@ protected:
return fSwizzler;
}
- SkPngCodec(const SkImageInfo&, SkStream*, png_structp, png_infop, int, int);
+ SkPngCodec(const SkImageInfo&, SkStream*, SkPngChunkReader*, png_structp, png_infop, int, int);
png_structp png_ptr() { return fPng_ptr; }
SkSwizzler* swizzler() { return fSwizzler; }
@@ -62,17 +63,18 @@ protected:
virtual AlphaState alphaInScanlineDecode() const = 0;
private:
- png_structp fPng_ptr;
- png_infop fInfo_ptr;
+ SkAutoTUnref<SkPngChunkReader> fPngChunkReader;
+ png_structp fPng_ptr;
+ png_infop fInfo_ptr;
// These are stored here so they can be used both by normal decoding and scanline decoding.
- SkAutoTUnref<SkColorTable> fColorTable; // May be unpremul.
- SkAutoTDelete<SkSwizzler> fSwizzler;
+ SkAutoTUnref<SkColorTable> fColorTable; // May be unpremul.
+ SkAutoTDelete<SkSwizzler> fSwizzler;
- SkSwizzler::SrcConfig fSrcConfig;
- const int fNumberPasses;
- int fBitDepth;
- AlphaState fAlphaState;
+ SkSwizzler::SrcConfig fSrcConfig;
+ const int fNumberPasses;
+ int fBitDepth;
+ AlphaState fAlphaState;
bool decodePalette(bool premultiply, int* ctableCount);
void destroyReadStruct();
diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp
index d2ad553769..221faf74d5 100644
--- a/src/images/SkImageDecoder.cpp
+++ b/src/images/SkImageDecoder.cpp
@@ -83,7 +83,7 @@ const char* SkImageDecoder::GetFormatName(Format format) {
return "Unknown Format";
}
-SkImageDecoder::Peeker* SkImageDecoder::setPeeker(Peeker* peeker) {
+SkPngChunkReader* SkImageDecoder::setPeeker(SkPngChunkReader* peeker) {
SkRefCnt_SafeAssign(fPeeker, peeker);
return peeker;
}
diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp
index 47963b5543..a03ed10453 100644
--- a/src/images/SkImageDecoder_libpng.cpp
+++ b/src/images/SkImageDecoder_libpng.cpp
@@ -124,10 +124,9 @@ static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
- SkImageDecoder::Peeker* peeker =
- (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
- // peek() returning true means continue decoding
- return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
+ SkPngChunkReader* peeker = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr);
+ // readChunk() returning true means continue decoding
+ return peeker->readChunk((const char*)chunk->name, chunk->data, chunk->size) ?
1 : -1;
}
#endif
diff --git a/src/ports/SkImageDecoder_empty.cpp b/src/ports/SkImageDecoder_empty.cpp
index 624457b3bc..11c3db1afc 100644
--- a/src/ports/SkImageDecoder_empty.cpp
+++ b/src/ports/SkImageDecoder_empty.cpp
@@ -14,6 +14,7 @@
#include "SkStream.h"
class SkColorTable;
+class SkPngChunkReader;
// Empty implementations for SkImageDecoder.
@@ -60,7 +61,7 @@ const char* SkImageDecoder::GetFormatName(Format) {
return nullptr;
}
-SkImageDecoder::Peeker* SkImageDecoder::setPeeker(Peeker*) {
+SkPngChunkReader* SkImageDecoder::setPeeker(SkPngChunkReader*) {
return nullptr;
}
diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp
index febaf7d43b..b53cbe1a4d 100644
--- a/tests/CodexTest.cpp
+++ b/tests/CodexTest.cpp
@@ -12,8 +12,12 @@
#include "SkData.h"
#include "SkMD5.h"
#include "SkRandom.h"
+#include "SkStream.h"
+#include "SkPngChunkReader.h"
#include "Test.h"
+#include "png.h"
+
static SkStreamAsset* resource(const char path[]) {
SkString fullPath = GetResourcePath(path);
return SkStream::NewFromFile(fullPath.c_str());
@@ -685,3 +689,159 @@ DEF_TEST(Codec_Params, r) {
test_invalid_parameters(r, "index8.png");
test_invalid_parameters(r, "mandrill.wbmp");
}
+
+static void codex_test_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
+ SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
+ if (!sk_stream->write(data, len)) {
+ png_error(png_ptr, "sk_write_fn Error!");
+ }
+}
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+DEF_TEST(Codec_pngChunkReader, r) {
+ // Create a dummy bitmap. Use unpremul RGBA for libpng.
+ SkBitmap bm;
+ const int w = 1;
+ const int h = 1;
+ const SkImageInfo bmInfo = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType,
+ kUnpremul_SkAlphaType);
+ bm.setInfo(bmInfo);
+ bm.allocPixels();
+ bm.eraseColor(SK_ColorBLUE);
+ SkMD5::Digest goodDigest;
+ md5(bm, &goodDigest);
+
+ // Write to a png file.
+ png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ REPORTER_ASSERT(r, png);
+ if (!png) {
+ return;
+ }
+
+ png_infop info = png_create_info_struct(png);
+ REPORTER_ASSERT(r, info);
+ if (!info) {
+ png_destroy_write_struct(&png, nullptr);
+ return;
+ }
+
+ if (setjmp(png_jmpbuf(png))) {
+ ERRORF(r, "failed writing png");
+ png_destroy_write_struct(&png, &info);
+ return;
+ }
+
+ SkDynamicMemoryWStream wStream;
+ png_set_write_fn(png, (void*) (&wStream), codex_test_write_fn, nullptr);
+
+ png_set_IHDR(png, info, (png_uint_32)w, (png_uint_32)h, 8,
+ PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ // Create some chunks that match the Android framework's use.
+ static png_unknown_chunk gUnknowns[] = {
+ { "npOl", (png_byte*)"outline", sizeof("outline"), PNG_HAVE_PLTE },
+ { "npLb", (png_byte*)"layoutBounds", sizeof("layoutBounds"), PNG_HAVE_PLTE },
+ { "npTc", (png_byte*)"ninePatchData", sizeof("ninePatchData"), PNG_HAVE_PLTE },
+ };
+
+ png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"npOl\0npLb\0npTc\0", 3);
+ png_set_unknown_chunks(png, info, gUnknowns, SK_ARRAY_COUNT(gUnknowns));
+#if PNG_LIBPNG_VER < 10600
+ /* Deal with unknown chunk location bug in 1.5.x and earlier */
+ png_set_unknown_chunk_location(png, info, 0, PNG_HAVE_PLTE);
+ png_set_unknown_chunk_location(png, info, 1, PNG_HAVE_PLTE);
+#endif
+
+ png_write_info(png, info);
+
+ for (int j = 0; j < h; j++) {
+ png_bytep row = (png_bytep)(bm.getAddr(0, j));
+ png_write_rows(png, &row, 1);
+ }
+ png_write_end(png, info);
+ png_destroy_write_struct(&png, &info);
+
+ class ChunkReader : public SkPngChunkReader {
+ public:
+ ChunkReader(skiatest::Reporter* r)
+ : fReporter(r)
+ {
+ this->reset();
+ }
+
+ bool readChunk(const char tag[], const void* data, size_t length) override {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gUnknowns); ++i) {
+ if (!strcmp(tag, (const char*) gUnknowns[i].name)) {
+ // Tag matches. This should have been the first time we see it.
+ REPORTER_ASSERT(fReporter, !fSeen[i]);
+ fSeen[i] = true;
+
+ // Data and length should match
+ REPORTER_ASSERT(fReporter, length == gUnknowns[i].size);
+ REPORTER_ASSERT(fReporter, !strcmp((const char*) data,
+ (const char*) gUnknowns[i].data));
+ return true;
+ }
+ }
+ ERRORF(fReporter, "Saw an unexpected unknown chunk.");
+ return true;
+ }
+
+ bool allHaveBeenSeen() {
+ bool ret = true;
+ for (auto seen : fSeen) {
+ ret &= seen;
+ }
+ return ret;
+ }
+
+ void reset() {
+ sk_bzero(fSeen, sizeof(fSeen));
+ }
+
+ private:
+ skiatest::Reporter* fReporter; // Unowned
+ bool fSeen[3];
+ };
+
+ ChunkReader chunkReader(r);
+
+ // Now read the file with SkCodec.
+ SkAutoTUnref<SkData> data(wStream.copyToData());
+ SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data, &chunkReader));
+ REPORTER_ASSERT(r, codec);
+ if (!codec) {
+ return;
+ }
+
+ // Now compare to the original.
+ SkBitmap decodedBm;
+ decodedBm.setInfo(codec->getInfo());
+ decodedBm.allocPixels();
+ SkCodec::Result result = codec->getPixels(codec->getInfo(), decodedBm.getPixels(),
+ decodedBm.rowBytes());
+ REPORTER_ASSERT(r, SkCodec::kSuccess == result);
+
+ if (decodedBm.colorType() != bm.colorType()) {
+ SkBitmap tmp;
+ bool success = decodedBm.copyTo(&tmp, bm.colorType());
+ REPORTER_ASSERT(r, success);
+ if (!success) {
+ return;
+ }
+
+ tmp.swap(decodedBm);
+ }
+
+ compare_to_good_digest(r, goodDigest, decodedBm);
+ REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen());
+
+ // Decoding again will read the chunks again.
+ chunkReader.reset();
+ REPORTER_ASSERT(r, !chunkReader.allHaveBeenSeen());
+ result = codec->getPixels(codec->getInfo(), decodedBm.getPixels(), decodedBm.rowBytes());
+ REPORTER_ASSERT(r, SkCodec::kSuccess == result);
+ REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen());
+}
+#endif // PNG_READ_UNKNOWN_CHUNKS_SUPPORTED