Add SkPngChunkReader.
This class allows a client of SkCodec to read chunks in the data
stream that are not recognized by libpng. This is used by Android
to specify ninepatch data.
Taken from SkImageDecoder::Peeker. Modify the name of the class
and its method to be more specific to their use. Make
SkImageDecoder::Peeker a subclass of the new class, to help stage
the change in Android.
Add a test to verify that it works.
BUG=skia:4574
BUG=skia:3257
Committed: https://skia.googlesource.com/skia/+/3389e00136188800b98ca69488c0418c374fd78b
Review URL: https://codereview.chromium.org/1040453002
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 071a4b8..11eb1f9 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -25,7 +25,6 @@
};
static const DecoderProc gDecoderProcs[] = {
- { SkPngCodec::IsPng, SkPngCodec::NewFromStream },
#if !defined(GOOGLE3)
{ SkJpegCodec::IsJpeg, SkJpegCodec::NewFromStream },
#endif
@@ -36,7 +35,8 @@
{ SkWbmpCodec::IsWbmp, SkWbmpCodec::NewFromStream }
};
-SkCodec* SkCodec::NewFromStream(SkStream* stream) {
+SkCodec* SkCodec::NewFromStream(SkStream* stream,
+ SkPngChunkReader* chunkReader) {
if (!stream) {
return nullptr;
}
@@ -44,15 +44,24 @@
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::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 82c952e..355d493 100644
--- a/src/codec/SkCodec_libpng.cpp
+++ b/src/codec/SkCodec_libpng.cpp
@@ -65,6 +65,14 @@
}
}
+#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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 9809b0c..c2a5f4a 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 @@
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 @@
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 @@
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();