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/tests/CodexTest.cpp b/tests/CodexTest.cpp
index febaf7d..b53cbe1 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 @@
     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