diff options
-rw-r--r-- | gyp/skia_for_android_framework_defines.gypi | 1 | ||||
-rw-r--r-- | include/codec/SkCodec.h | 11 | ||||
-rw-r--r-- | include/core/SkImageDecoder.h | 34 | ||||
-rw-r--r-- | include/core/SkPngChunkReader.h | 45 | ||||
-rw-r--r-- | src/codec/SkCodec.cpp | 35 | ||||
-rw-r--r-- | src/codec/SkCodec_libpng.cpp | 79 | ||||
-rw-r--r-- | src/codec/SkCodec_libpng.h | 22 | ||||
-rw-r--r-- | src/images/SkImageDecoder.cpp | 2 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libpng.cpp | 7 | ||||
-rw-r--r-- | src/ports/SkImageDecoder_empty.cpp | 3 | ||||
-rw-r--r-- | tests/CodexTest.cpp | 160 |
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 |