8e6c7ada5a
This is a step towards using SkCodec in Chromium, where progressive decoding is necessary. Switch from using png_read_row (which expects all the data to be available) to png_process_data, which uses callbacks when rows are available. Create a new API for SkCodec, which supports progressive decoding and scanline decoding. Future changes will switch the other clients off of startScanlineDecode and get/skip-Scanlines to the new API. Remove SkCodec::kNone_ScanlineOrder, which was only used for interlaced PNG images. In the new API, interlaced PNG fits kTopDown. Also remove updateCurrScanline(), which was only used by the old implementation for interlaced PNG. DMSrcSink: - In CodecSrc::kScanline_Mode, use the new method for scanline decoding for the supported formats (just PNG and PNG-in-ICO for now). fuzz.cpp: - Remove reference to kNone_ScanlineOrder SkCodec: - Add new APIs: - startIncrementalDecode - incrementalDecode - Remove kNone_SkScanlineOrder and updateCurrScanline() - Set fDstInfo and fOptions in getPixels(). This may not be necessary for all implementations, but it simplifies things for SkPngCodec. SkPngCodec: - Implement new APIs - Switch from sk_read_fn/png_read_row etc to png_process_data - Expand AutoCleanPng's role to decode the header and create the SkPngCodec - Make the interlaced PNG decoder report how many lines were initialized during an incomplete decode SkIcoCodec: - Implement the new APIs; supported for PNG in ICO SkSampledCodec: - Call the new method for decoding scanlines, and fall back to the old method if the new version is unimplemented - Remove references to kNone_SkScanlineOrder tests/CodecPartial: - Add a test which decodes part of an image, then finishes the decode, and compares it to the straightforward method tests/CodecTest: - Add a test which decodes all scanlines using the new method - Repurpose the Codec_stripes test to decode using the new method in sections rather than all at once - In the method check(), add a parameter for whether the image supports the new method of scanline decoding, and be explicit about whether an image supports incomplete - Test incomplete PNG decodes. We should have been doing it anyway for non-interlaced (except for an image that is too small - one row), but the new method supports interlaced incomplete as well - Make test_invalid_parameters test the new method - Add a test to ensure that it's safe to fall back to scanline decoding without rewinding BUG=skia:4211 The new version was generally faster than the old version (but not significantly so). Some raw performance differences can be found at https://docs.google.com/a/google.com/spreadsheets/d/1Gis3aRCEa72qBNDRMgGDg3jD-pMgO-FXldlNF9ejo4o/ Design doc can be found at https://docs.google.com/a/google.com/document/d/11Mn8-ePDKwVEMCjs3nWwSjxcSpJ_Cu8DF57KNtUmgLM/ GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1997703003 Review-Url: https://codereview.chromium.org/1997703003
197 lines
6.0 KiB
C++
197 lines
6.0 KiB
C++
/*
|
|
* Copyright 2016 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "SkBitmap.h"
|
|
#include "SkCodec.h"
|
|
#include "SkData.h"
|
|
#include "SkImageInfo.h"
|
|
#include "SkRWBuffer.h"
|
|
#include "SkString.h"
|
|
|
|
#include "Resources.h"
|
|
#include "Test.h"
|
|
|
|
static sk_sp<SkData> make_from_resource(const char* name) {
|
|
SkString fullPath = GetResourcePath(name);
|
|
return SkData::MakeFromFileName(fullPath.c_str());
|
|
}
|
|
|
|
static SkImageInfo standardize_info(SkCodec* codec) {
|
|
SkImageInfo defaultInfo = codec->getInfo();
|
|
// Note: This drops the SkColorSpace, allowing the equality check between two
|
|
// different codecs created from the same file to have the same SkImageInfo.
|
|
return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
|
|
}
|
|
|
|
static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
|
|
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(std::move(data)));
|
|
if (!codec) {
|
|
return false;
|
|
}
|
|
|
|
const SkImageInfo info = standardize_info(codec);
|
|
dst->allocPixels(info);
|
|
return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
|
|
}
|
|
|
|
/*
|
|
* Represents a stream without all of its data.
|
|
*/
|
|
class HaltingStream : public SkStream {
|
|
public:
|
|
HaltingStream(sk_sp<SkData> data)
|
|
: fTotalSize(data->size())
|
|
, fLimit(fTotalSize / 2)
|
|
, fStream(std::move(data))
|
|
{}
|
|
|
|
void addNewData() {
|
|
// Arbitrary size, but deliberately different from
|
|
// the buffer size used by SkPngCodec.
|
|
fLimit = SkTMin(fTotalSize, fLimit + 1000);
|
|
}
|
|
|
|
size_t read(void* buffer, size_t size) override {
|
|
if (fStream.getPosition() + size > fLimit) {
|
|
size = fLimit - fStream.getPosition();
|
|
}
|
|
|
|
return fStream.read(buffer, size);
|
|
}
|
|
|
|
bool isAtEnd() const override {
|
|
return fStream.isAtEnd();
|
|
}
|
|
|
|
bool hasPosition() const override { return true; }
|
|
size_t getPosition() const override { return fStream.getPosition(); }
|
|
bool rewind() override { return fStream.rewind(); }
|
|
bool move(long offset) override { return fStream.move(offset); }
|
|
|
|
private:
|
|
const size_t fTotalSize;
|
|
size_t fLimit;
|
|
SkMemoryStream fStream;
|
|
};
|
|
|
|
static void test_partial(skiatest::Reporter* r, const char* name) {
|
|
sk_sp<SkData> file = make_from_resource(name);
|
|
if (!file) {
|
|
SkDebugf("missing resource %s\n", name);
|
|
return;
|
|
}
|
|
|
|
SkBitmap truth;
|
|
if (!create_truth(file, &truth)) {
|
|
ERRORF(r, "Failed to decode %s\n", name);
|
|
return;
|
|
}
|
|
|
|
const size_t fileSize = file->size();
|
|
|
|
// Now decode part of the file
|
|
HaltingStream* stream = new HaltingStream(file);
|
|
|
|
// Note that we cheat and hold on to a pointer to stream, though it is owned by
|
|
// partialCodec.
|
|
SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream(stream));
|
|
if (!partialCodec) {
|
|
// Technically, this could be a small file where half the file is not
|
|
// enough.
|
|
ERRORF(r, "Failed to create codec for %s", name);
|
|
return;
|
|
}
|
|
|
|
const SkImageInfo info = standardize_info(partialCodec);
|
|
SkASSERT(info == truth.info());
|
|
SkBitmap incremental;
|
|
incremental.allocPixels(info);
|
|
|
|
const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
|
|
incremental.getPixels(), incremental.rowBytes());
|
|
if (startResult != SkCodec::kSuccess) {
|
|
ERRORF(r, "Failed to start incremental decode\n");
|
|
return;
|
|
}
|
|
|
|
while (true) {
|
|
const SkCodec::Result result = partialCodec->incrementalDecode();
|
|
|
|
if (stream->getPosition() == fileSize) {
|
|
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
|
|
break;
|
|
}
|
|
|
|
SkASSERT(stream->getPosition() < fileSize);
|
|
|
|
REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
|
|
|
|
// Append an arbitrary amount of data.
|
|
stream->addNewData();
|
|
}
|
|
|
|
// compare to original
|
|
for (int i = 0; i < info.height(); i++) {
|
|
REPORTER_ASSERT(r, !memcmp(truth.getAddr(0, 0), incremental.getAddr(0, 0),
|
|
info.minRowBytes()));
|
|
}
|
|
}
|
|
|
|
DEF_TEST(Codec_partial, r) {
|
|
test_partial(r, "plane.png");
|
|
test_partial(r, "plane_interlaced.png");
|
|
test_partial(r, "yellow_rose.png");
|
|
test_partial(r, "index8.png");
|
|
test_partial(r, "color_wheel.png");
|
|
test_partial(r, "mandrill_256.png");
|
|
test_partial(r, "mandrill_32.png");
|
|
test_partial(r, "arrow.png");
|
|
test_partial(r, "randPixels.png");
|
|
test_partial(r, "baby_tux.png");
|
|
}
|
|
|
|
// Test that calling getPixels when an incremental decode has been
|
|
// started (but not finished) makes the next call to incrementalDecode
|
|
// require a call to startIncrementalDecode.
|
|
static void test_interleaved(skiatest::Reporter* r, const char* name) {
|
|
sk_sp<SkData> file = make_from_resource(name);
|
|
SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream(
|
|
new HaltingStream(std::move(file))));
|
|
if (!partialCodec) {
|
|
ERRORF(r, "Failed to create codec for %s", name);
|
|
return;
|
|
}
|
|
|
|
const SkImageInfo info = standardize_info(partialCodec);
|
|
SkBitmap incremental;
|
|
incremental.allocPixels(info);
|
|
|
|
const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
|
|
incremental.getPixels(), incremental.rowBytes());
|
|
if (startResult != SkCodec::kSuccess) {
|
|
ERRORF(r, "Failed to start incremental decode\n");
|
|
return;
|
|
}
|
|
|
|
SkCodec::Result result = partialCodec->incrementalDecode();
|
|
REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
|
|
|
|
SkBitmap full;
|
|
full.allocPixels(info);
|
|
result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
|
|
REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
|
|
|
|
// Now incremental decode will fail
|
|
result = partialCodec->incrementalDecode();
|
|
REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
|
|
}
|
|
|
|
DEF_TEST(Codec_rewind, r) {
|
|
test_interleaved(r, "plane.png");
|
|
test_interleaved(r, "plane_interlaced.png");
|
|
}
|