Revert of Make SkPngCodec decode progressively. (patchset #26 id:520001 of https://codereview.chromium.org/1997703003/ )

Reason for revert:
Still causing problems in Google3, e.g.

https://test.corp.google.com/ui#cl=124138817&flags=CAMQBQ==&id=OCL:124138817:BASE:124139560:1465227435491:219ffbdb&t=//third_party/skia/HEAD:dm

Original issue's description:
> Make SkPngCodec decode progressively.
>
> 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()
>
> 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
> - Make initializeSwizzler return a bool instead of an SkCodec::Result
>   (It only returned kSuccess or kInvalidInput anyway)
>
> 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
>
> Committed: https://skia.googlesource.com/skia/+/a4b09a117d4d1ba5dda372e6a2323e653766539e
>
> Committed: https://skia.googlesource.com/skia/+/30e78c9737ff4861dc4e3fa1e4cd010680ed6965
>
> Committed: https://skia.googlesource.com/skia/+/6fb2391b2cc83ee2160b4e994faa8128975acc1f

TBR=reed@google.com,msarett@google.com,scroggo@chromium.org
# Not skipping CQ checks because original CL landed more than 1 days ago.
BUG=skia:4211
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2044573002

Review-Url: https://codereview.chromium.org/2044573002
This commit is contained in:
scroggo 2016-06-06 11:26:17 -07:00 committed by Commit bot
parent a4fa4f6eb7
commit d8d6855345
16 changed files with 599 additions and 1321 deletions

View File

@ -438,55 +438,31 @@ Error CodecSrc::draw(SkCanvas* canvas) const {
break;
}
case kScanline_Mode: {
void* dst = pixels.get();
uint32_t height = decodeInfo.height();
const bool png = fPath.endsWith("png");
const bool ico = fPath.endsWith("ico");
bool useOldScanlineMethod = !png && !ico;
if (png || ico) {
if (SkCodec::kSuccess == codec->startIncrementalDecode(decodeInfo, dst,
rowBytes, nullptr, colorPtr, &colorCount)) {
int rowsDecoded;
if (SkCodec::kIncompleteInput == codec->incrementalDecode(&rowsDecoded)) {
codec->fillIncompleteImage(decodeInfo, dst, rowBytes,
SkCodec::kNo_ZeroInitialized, height,
rowsDecoded);
}
} else {
if (png) {
// Error: PNG should support incremental decode.
return "Could not start incremental decode";
}
// Otherwise, this is an ICO. Since incremental failed, it must contain a BMP,
// which should work via startScanlineDecode
useOldScanlineMethod = true;
}
if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
&colorCount)) {
return "Could not start scanline decoder";
}
if (useOldScanlineMethod) {
if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
&colorCount)) {
return "Could not start scanline decoder";
}
switch (codec->getScanlineOrder()) {
case SkCodec::kTopDown_SkScanlineOrder:
case SkCodec::kBottomUp_SkScanlineOrder:
// We do not need to check the return value. On an incomplete
// image, memory will be filled with a default value.
codec->getScanlines(dst, height, rowBytes);
break;
case SkCodec::kOutOfOrder_SkScanlineOrder: {
for (int y = 0; y < decodeInfo.height(); y++) {
int dstY = codec->outputScanline(y);
void* dstPtr = SkTAddOffset<void>(dst, rowBytes * dstY);
// We complete the loop, even if this call begins to fail
// due to an incomplete image. This ensures any uninitialized
// memory will be filled with the proper value.
codec->getScanlines(dstPtr, 1, rowBytes);
}
break;
void* dst = pixels.get();
uint32_t height = decodeInfo.height();
switch (codec->getScanlineOrder()) {
case SkCodec::kTopDown_SkScanlineOrder:
case SkCodec::kBottomUp_SkScanlineOrder:
case SkCodec::kNone_SkScanlineOrder:
// We do not need to check the return value. On an incomplete
// image, memory will be filled with a default value.
codec->getScanlines(dst, height, rowBytes);
break;
case SkCodec::kOutOfOrder_SkScanlineOrder: {
for (int y = 0; y < decodeInfo.height(); y++) {
int dstY = codec->outputScanline(y);
void* dstPtr = SkTAddOffset<void>(dst, rowBytes * dstY);
// We complete the loop, even if this call begins to fail
// due to an incomplete image. This ensures any uninitialized
// memory will be filled with the proper value.
codec->getScanlines(dstPtr, 1, rowBytes);
}
break;
}
}

View File

@ -187,6 +187,7 @@ int fuzz_img(SkData* bytes, uint8_t scale, uint8_t mode) {
switch (codec->getScanlineOrder()) {
case SkCodec::kTopDown_SkScanlineOrder:
case SkCodec::kBottomUp_SkScanlineOrder:
case SkCodec::kNone_SkScanlineOrder:
// We do not need to check the return value. On an incomplete
// image, memory will be filled with a default value.
codec->getScanlines(dst, height, rowBytes);

View File

@ -35,8 +35,9 @@ protected:
// bitmap is unlocked.
SkAutoTUnref<SkDiscardableMemoryPool> pool(
SkDiscardableMemoryPool::Create(1));
SkDEPRECATED_InstallDiscardablePixelRef(SkImageGenerator::NewFromEncoded(data),
nullptr, &fBitmap, pool);
SkAssertResult(SkDEPRECATED_InstallDiscardablePixelRef(
SkImageGenerator::NewFromEncoded(data),
nullptr, &fBitmap, pool));
}
}

View File

@ -24,11 +24,11 @@ class SkPngChunkReader;
class SkSampler;
namespace DM {
class CodecSrc;
class ColorCodecSrc;
}
class ColorCodecBench;
/**
* Abstraction layer directly on top of an image codec.
*/
@ -259,8 +259,8 @@ public:
* If the EncodedFormat is kWEBP_SkEncodedFormat (the only one which
* currently supports subsets), the top and left values must be even.
*
* In getPixels and incremental decode, we will attempt to decode the
* exact rectangular subset specified by fSubset.
* In getPixels, we will attempt to decode the exact rectangular
* subset specified by fSubset.
*
* In a scanline decode, it does not make sense to specify a subset
* top or subset height, since the client already controls which rows
@ -354,67 +354,6 @@ public:
return this->onGetYUV8Planes(sizeInfo, planes);
}
/**
* Prepare for an incremental decode with the specified options.
*
* This may require a rewind.
*
* @param dstInfo Info of the destination. If the dimensions do not match
* those of getInfo, this implies a scale.
* @param dst Memory to write to. Needs to be large enough to hold the subset,
* if present, or the full image as described in dstInfo.
* @param options Contains decoding options, including if memory is zero
* initialized and whether to decode a subset.
* @param ctable A pointer to a color table. When dstInfo.colorType() is
* kIndex8, this should be non-NULL and have enough storage for 256
* colors. The color table will be populated after decoding the palette.
* @param ctableCount A pointer to the size of the color table. When
* dstInfo.colorType() is kIndex8, this should be non-NULL. It will
* be modified to the true size of the color table (<= 256) after
* decoding the palette.
* @return Enum representing success or reason for failure.
*/
Result startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
const SkCodec::Options*, SkPMColor* ctable, int* ctableCount);
Result startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
const SkCodec::Options* options) {
return this->startIncrementalDecode(dstInfo, dst, rowBytes, options, nullptr, nullptr);
}
Result startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes) {
return this->startIncrementalDecode(dstInfo, dst, rowBytes, nullptr, nullptr, nullptr);
}
/**
* Start/continue the incremental decode.
*
* Not valid to call before calling startIncrementalDecode().
*
* After the first call, should only be called again if more data has been
* provided to the source SkStream.
*
* Unlike getPixels and getScanlines, this does not do any filling. This is
* left up to the caller, since they may be skipping lines or continuing the
* decode later. In the latter case, they may choose to initialize all lines
* first, or only initialize the remaining lines after the first call.
*
* @param rowsDecoded Optional output variable returning the total number of
* lines initialized. Only meaningful if this method returns kIncompleteInput.
* Otherwise the implementation may not set it.
* Note that some implementations may have initialized this many rows, but
* not necessarily finished those rows (e.g. interlaced PNG). This may be
* useful for determining what rows the client needs to initialize.
* @return kSuccess if all lines requested in startIncrementalDecode have
* been completely decoded. kIncompleteInput otherwise.
*/
Result incrementalDecode(int* rowsDecoded = nullptr) {
if (!fStartedIncrementalDecode) {
return kInvalidParameters;
}
return this->onIncrementalDecode(rowsDecoded);
}
/**
* The remaining functions revolve around decoding scanlines.
*/
@ -535,6 +474,17 @@ public:
* Interlaced gifs are an example.
*/
kOutOfOrder_SkScanlineOrder,
/*
* Indicates that the entire image must be decoded in order to output
* any amount of scanlines. In this case, it is a REALLY BAD IDEA to
* request scanlines 1-by-1 or in small chunks. The client should
* determine which scanlines are needed and ask for all of them in
* a single call to getScanlines().
*
* Interlaced pngs are an example.
*/
kNone_SkScanlineOrder,
};
/**
@ -683,6 +633,11 @@ protected:
*/
virtual SkScanlineOrder onGetScanlineOrder() const { return kTopDown_SkScanlineOrder; }
/**
* Update the current scanline. Used by interlaced png.
*/
void updateCurrScanline(int newY) { fCurrScanline = newY; }
const SkImageInfo& dstInfo() const { return fDstInfo; }
const SkCodec::Options& options() const { return fOptions; }
@ -714,7 +669,6 @@ private:
SkImageInfo fDstInfo;
SkCodec::Options fOptions;
int fCurrScanline;
bool fStartedIncrementalDecode;
/**
* Return whether these dimensions are supported as a scale.
@ -735,16 +689,6 @@ private:
return kUnimplemented;
}
virtual Result onStartIncrementalDecode(const SkImageInfo& /*dstInfo*/, void*, size_t,
const SkCodec::Options&, SkPMColor*, int*) {
return kUnimplemented;
}
virtual Result onIncrementalDecode(int*) {
return kUnimplemented;
}
virtual bool onSkipScanlines(int /*countLines*/) { return false; }
virtual int onGetScanlines(void* /*dst*/, int /*countLines*/, size_t /*rowBytes*/) { return 0; }
@ -781,7 +725,6 @@ private:
friend class DM::ColorCodecSrc;
friend class ColorCodecBench;
friend class DM::CodecSrc; // for fillIncompleteImage
friend class SkSampledCodec;
friend class SkIcoCodec;
};

View File

@ -154,20 +154,6 @@ bool SkCodec::rewindIfNeeded() {
return this->onRewind();
}
#define CHECK_COLOR_TABLE \
if (kIndex_8_SkColorType == info.colorType()) { \
if (nullptr == ctable || nullptr == ctableCount) { \
return SkCodec::kInvalidParameters; \
} \
} else { \
if (ctableCount) { \
*ctableCount = 0; \
} \
ctableCount = nullptr; \
ctable = nullptr; \
}
SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
const Options* options, SkPMColor ctable[], int* ctableCount) {
if (kUnknown_SkColorType == info.colorType()) {
@ -180,7 +166,17 @@ SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t
return kInvalidParameters;
}
CHECK_COLOR_TABLE;
if (kIndex_8_SkColorType == info.colorType()) {
if (nullptr == ctable || nullptr == ctableCount) {
return kInvalidParameters;
}
} else {
if (ctableCount) {
*ctableCount = 0;
}
ctableCount = nullptr;
ctable = nullptr;
}
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
@ -232,77 +228,22 @@ SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t
return this->getPixels(info, pixels, rowBytes, nullptr, nullptr, nullptr);
}
SkCodec::Result SkCodec::startIncrementalDecode(const SkImageInfo& info, void* pixels,
size_t rowBytes, const SkCodec::Options* options, SkPMColor* ctable, int* ctableCount) {
fStartedIncrementalDecode = false;
if (kUnknown_SkColorType == info.colorType()) {
return kInvalidConversion;
}
if (nullptr == pixels) {
return kInvalidParameters;
}
// Ensure that valid color ptrs are passed in for kIndex8 color type
CHECK_COLOR_TABLE;
// FIXME: If the rows come after the rows of a previous incremental decode,
// we might be able to skip the rewind, but only the implementation knows
// that. (e.g. PNG will always need to rewind, since we called longjmp, but
// a bottom-up BMP could skip rewinding if the new rows are above the old
// rows.)
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
// Set options.
Options optsStorage;
if (nullptr == options) {
options = &optsStorage;
} else if (options->fSubset) {
SkIRect size = SkIRect::MakeSize(info.dimensions());
if (!size.contains(*options->fSubset)) {
return kInvalidParameters;
}
const int top = options->fSubset->top();
const int bottom = options->fSubset->bottom();
if (top < 0 || top >= info.height() || top >= bottom || bottom > info.height()) {
return kInvalidParameters;
}
}
if (!this->dimensionsSupported(info.dimensions())) {
return kInvalidScale;
}
fDstInfo = info;
fOptions = *options;
const Result result = this->onStartIncrementalDecode(info, pixels, rowBytes,
fOptions, ctable, ctableCount);
if (kSuccess == result) {
fStartedIncrementalDecode = true;
} else if (kUnimplemented == result) {
// FIXME: This is temporarily necessary, until we transition SkCodec
// implementations from scanline decoding to incremental decoding.
// SkAndroidCodec will first attempt to use incremental decoding, but
// will fall back to scanline decoding if incremental returns
// kUnimplemented. rewindIfNeeded(), above, set fNeedsRewind to true
// (after potentially rewinding), but we do not want the next call to
// startScanlineDecode() to do a rewind.
fNeedsRewind = false;
}
return result;
}
SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info,
SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo,
const SkCodec::Options* options, SkPMColor ctable[], int* ctableCount) {
// Reset fCurrScanline in case of failure.
fCurrScanline = -1;
// Ensure that valid color ptrs are passed in for kIndex8 color type
CHECK_COLOR_TABLE;
if (kIndex_8_SkColorType == dstInfo.colorType()) {
if (nullptr == ctable || nullptr == ctableCount) {
return SkCodec::kInvalidParameters;
}
} else {
if (ctableCount) {
*ctableCount = 0;
}
ctableCount = nullptr;
ctable = nullptr;
}
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
@ -313,38 +254,36 @@ SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info,
if (nullptr == options) {
options = &optsStorage;
} else if (options->fSubset) {
SkIRect size = SkIRect::MakeSize(info.dimensions());
SkIRect size = SkIRect::MakeSize(dstInfo.dimensions());
if (!size.contains(*options->fSubset)) {
return kInvalidInput;
}
// We only support subsetting in the x-dimension for scanline decoder.
// Subsetting in the y-dimension can be accomplished using skipScanlines().
if (options->fSubset->top() != 0 || options->fSubset->height() != info.height()) {
if (options->fSubset->top() != 0 || options->fSubset->height() != dstInfo.height()) {
return kInvalidInput;
}
}
// FIXME: Support subsets somehow?
if (!this->dimensionsSupported(info.dimensions())) {
if (!this->dimensionsSupported(dstInfo.dimensions())) {
return kInvalidScale;
}
const Result result = this->onStartScanlineDecode(info, *options, ctable, ctableCount);
const Result result = this->onStartScanlineDecode(dstInfo, *options, ctable, ctableCount);
if (result != SkCodec::kSuccess) {
return result;
}
fCurrScanline = 0;
fDstInfo = info;
fDstInfo = dstInfo;
fOptions = *options;
return kSuccess;
}
#undef CHECK_COLOR_TABLE
SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info) {
return this->startScanlineDecode(info, nullptr, nullptr, nullptr);
SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo) {
return this->startScanlineDecode(dstInfo, nullptr, nullptr, nullptr);
}
int SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) {
@ -392,6 +331,7 @@ int SkCodec::outputScanline(int inputScanline) const {
int SkCodec::onOutputScanline(int inputScanline) const {
switch (this->getScanlineOrder()) {
case kTopDown_SkScanlineOrder:
case kNone_SkScanlineOrder:
return inputScanline;
case kBottomUp_SkScanlineOrder:
return this->getInfo().height() - inputScanline - 1;
@ -425,7 +365,8 @@ void SkCodec::fillIncompleteImage(const SkImageInfo& info, void* dst, size_t row
}
switch (this->getScanlineOrder()) {
case kTopDown_SkScanlineOrder: {
case kTopDown_SkScanlineOrder:
case kNone_SkScanlineOrder: {
const SkImageInfo fillInfo = info.makeWH(fillWidth, linesRemaining);
fillDst = SkTAddOffset<void>(dst, linesDecoded * rowBytes);
fill_proc(fillInfo, fillDst, rowBytes, fillValue, zeroInit, sampler);

View File

@ -520,7 +520,7 @@ uint32_t SkGifCodec::onGetFillValue(SkColorType colorType) const {
SkCodec::Result SkGifCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
const SkCodec::Options& opts, SkPMColor inputColorPtr[], int* inputColorCount) {
return this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, opts);
return this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, this->options());
}
void SkGifCodec::handleScanlineFrame(int count, int* rowsBeforeFrame, int* rowsInFrame) {

View File

@ -186,7 +186,6 @@ SkIcoCodec::SkIcoCodec(int width, int height, const SkEncodedInfo& info,
: INHERITED(width, height, info, nullptr)
, fEmbeddedCodecs(codecs)
, fCurrScanlineCodec(nullptr)
, fCurrIncrementalCodec(nullptr)
{}
/*
@ -290,7 +289,6 @@ SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
result = embeddedCodec->startScanlineDecode(dstInfo, &options, colorTable, colorCount);
if (kSuccess == result) {
fCurrScanlineCodec = embeddedCodec;
fCurrIncrementalCodec = nullptr;
return result;
}
@ -311,82 +309,13 @@ bool SkIcoCodec::onSkipScanlines(int count) {
return fCurrScanlineCodec->skipScanlines(count);
}
SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
void* pixels, size_t rowBytes, const SkCodec::Options& options,
SkPMColor* colorTable, int* colorCount) {
int index = 0;
while (true) {
index = this->chooseCodec(dstInfo.dimensions(), index);
if (index < 0) {
break;
}
SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index);
switch (embeddedCodec->startIncrementalDecode(dstInfo,
pixels, rowBytes, &options, colorTable, colorCount)) {
case kSuccess:
fCurrIncrementalCodec = embeddedCodec;
fCurrScanlineCodec = nullptr;
return kSuccess;
case kUnimplemented:
// FIXME: embeddedCodec is a BMP. If scanline decoding would work,
// return kUnimplemented so that SkSampledCodec will fall through
// to use the scanline decoder.
// Note that calling startScanlineDecode will require an extra
// rewind. The embedded codec has an SkMemoryStream, which is
// cheap to rewind, though it will do extra work re-reading the
// header.
// Also note that we pass nullptr for Options. This is because
// Options that are valid for incremental decoding may not be
// valid for scanline decoding.
// Once BMP supports incremental decoding this workaround can go
// away.
if (embeddedCodec->startScanlineDecode(dstInfo, nullptr,
colorTable, colorCount) == kSuccess) {
return kUnimplemented;
}
// Move on to the next embedded codec.
break;
default:
break;
}
index++;
}
SkCodecPrintf("Error: No matching candidate image in ico.\n");
return kInvalidScale;
}
SkCodec::Result SkIcoCodec::onIncrementalDecode(int* rowsDecoded) {
SkASSERT(fCurrIncrementalCodec);
return fCurrIncrementalCodec->incrementalDecode(rowsDecoded);
}
SkCodec::SkScanlineOrder SkIcoCodec::onGetScanlineOrder() const {
// FIXME: This function will possibly return the wrong value if it is called
// before startScanlineDecode()/startIncrementalDecode().
if (fCurrScanlineCodec) {
SkASSERT(!fCurrIncrementalCodec);
return fCurrScanlineCodec->getScanlineOrder();
}
if (fCurrIncrementalCodec) {
return fCurrIncrementalCodec->getScanlineOrder();
}
return INHERITED::onGetScanlineOrder();
// before startScanlineDecode().
return fCurrScanlineCodec ? fCurrScanlineCodec->getScanlineOrder() :
INHERITED::onGetScanlineOrder();
}
SkSampler* SkIcoCodec::getSampler(bool createIfNecessary) {
if (fCurrScanlineCodec) {
SkASSERT(!fCurrIncrementalCodec);
return fCurrScanlineCodec->getSampler(createIfNecessary);
}
if (fCurrIncrementalCodec) {
return fCurrIncrementalCodec->getSampler(createIfNecessary);
}
return nullptr;
return fCurrScanlineCodec ? fCurrScanlineCodec->getSampler(createIfNecessary) : nullptr;
}

View File

@ -54,11 +54,6 @@ private:
bool onSkipScanlines(int count) override;
Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
const SkCodec::Options&, SkPMColor*, int*) override;
Result onIncrementalDecode(int* rowsDecoded) override;
SkSampler* getSampler(bool createIfNecessary) override;
/*
@ -89,13 +84,5 @@ private:
// SkAutoTDelete. It will be deleted by the destructor of fEmbeddedCodecs.
SkCodec* fCurrScanlineCodec;
// Only used by incremental decoder. onStartIncrementalDecode() will set
// fCurrIncrementalCodec to one of the fEmbeddedCodecs, if it can find a
// codec of the appropriate size. We will use fCurrIncrementalCodec for
// subsequent calls to incrementalDecode().
// fCurrIncrementalCodec is owned by this class, but should not be an
// SkAutoTDelete. It will be deleted by the destructor of fEmbeddedCodecs.
SkCodec* fCurrIncrementalCodec;
typedef SkCodec INHERITED;
};

View File

@ -23,22 +23,27 @@
// Callback functions
///////////////////////////////////////////////////////////////////////////////
// When setjmp is first called, it returns 0, meaning longjmp was not called.
constexpr int kSetJmpOkay = 0;
// An error internal to libpng.
constexpr int kPngError = 1;
// Passed to longjmp when we have decoded as many lines as we need.
constexpr int kStopDecoding = 2;
static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
SkCodecPrintf("------ png error %s\n", msg);
longjmp(png_jmpbuf(png_ptr), kPngError);
longjmp(png_jmpbuf(png_ptr), 1);
}
void sk_warning_fn(png_structp, png_const_charp msg) {
SkCodecPrintf("----- png warning %s\n", msg);
}
static void sk_read_fn(png_structp png_ptr, png_bytep data,
png_size_t length) {
SkStream* stream = static_cast<SkStream*>(png_get_io_ptr(png_ptr));
const size_t bytes = stream->read(data, length);
if (bytes != length) {
// FIXME: We want to report the fact that the stream was truncated.
// One way to do that might be to pass a enum to longjmp so setjmp can
// specify the failure.
png_error(png_ptr, "Read Error!");
}
}
#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);
@ -53,22 +58,9 @@ static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
class AutoCleanPng : public SkNoncopyable {
public:
/*
* This class does not take ownership of stream or reader, but if codecPtr
* is non-NULL, and decodeBounds succeeds, it will have created a new
* SkCodec (pointed to by *codecPtr) which will own/ref them, as well as
* the png_ptr and info_ptr.
*/
AutoCleanPng(png_structp png_ptr, SkStream* stream, SkPngChunkReader* reader,
SkCodec** codecPtr)
AutoCleanPng(png_structp png_ptr)
: fPng_ptr(png_ptr)
, fInfo_ptr(nullptr)
, fDecodedBounds(false)
, fReadHeader(false)
, fStream(stream)
, fChunkReader(reader)
, fOutCodec(codecPtr)
{}
, fInfo_ptr(nullptr) {}
~AutoCleanPng() {
// fInfo_ptr will never be non-nullptr unless fPng_ptr is.
@ -83,117 +75,20 @@ public:
fInfo_ptr = info_ptr;
}
/**
* Reads enough of the input stream to decode the bounds.
* @return false if the stream is not a valid PNG (or too short).
* true if it read enough of the stream to determine the bounds.
* In the latter case, the stream may have been read beyond the
* point to determine the bounds, and the png_ptr will have saved
* any extra data. Further, if the codecPtr supplied to the
* constructor was not NULL, it will now point to a new SkCodec,
* which owns (or refs, in the case of the SkPngChunkReader) the
* inputs. If codecPtr was NULL, the png_ptr and info_ptr are
* unowned, and it is up to the caller to destroy them.
*/
bool decodeBounds();
private:
png_structp fPng_ptr;
png_infop fInfo_ptr;
bool fDecodedBounds;
bool fReadHeader;
SkStream* fStream;
SkPngChunkReader* fChunkReader;
SkCodec** fOutCodec;
/**
* Supplied to libpng to call when it has read enough data to determine
* bounds.
*/
static void InfoCallback(png_structp png_ptr, png_infop info_ptr) {
// png_get_progressive_ptr returns the pointer we set on the png_ptr with
// png_set_progressive_read_fn
static_cast<AutoCleanPng*>(png_get_progressive_ptr(png_ptr))->infoCallback();
}
void infoCallback();
void releasePngPtrs() {
void release() {
fPng_ptr = nullptr;
fInfo_ptr = nullptr;
}
private:
png_structp fPng_ptr;
png_infop fInfo_ptr;
};
#define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng)
bool AutoCleanPng::decodeBounds() {
if (setjmp(png_jmpbuf(fPng_ptr))) {
return false;
}
png_set_progressive_read_fn(fPng_ptr, this, InfoCallback, nullptr, nullptr);
// Arbitrary buffer size, though note that it matches (below)
// SkPngCodec::processData(). FIXME: Can we better suit this to the size of
// the PNG header?
constexpr size_t kBufferSize = 4096;
char buffer[kBufferSize];
while (true) {
const size_t bytesRead = fStream->read(buffer, kBufferSize);
if (!bytesRead) {
// We have read to the end of the input without decoding bounds.
break;
}
png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
if (fReadHeader) {
break;
}
}
// For safety, clear the pointer to this object.
png_set_progressive_read_fn(fPng_ptr, nullptr, nullptr, nullptr, nullptr);
return fDecodedBounds;
}
void SkPngCodec::processData() {
switch (setjmp(png_jmpbuf(fPng_ptr))) {
case kPngError:
// There was an error. Stop processing data.
// FIXME: Do we need to discard png_ptr?
return;
case kStopDecoding:
// We decoded all the lines we want.
return;
case kSetJmpOkay:
// Everything is okay.
break;
default:
// No other values should be passed to longjmp.
SkASSERT(false);
}
// Arbitrary buffer size
constexpr size_t kBufferSize = 4096;
char buffer[kBufferSize];
while (true) {
const size_t bytesRead = this->stream()->read(buffer, kBufferSize);
png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
if (!bytesRead) {
// We have read to the end of the input. Note that we quit *after*
// calling png_process_data, because decodeBounds may have told
// libpng to save the remainder of the buffer, in which case
// png_process_data will process the saved buffer, though the
// stream has no more to read.
break;
}
}
}
// Note: SkColorTable claims to store SkPMColors, which is not necessarily
// the case here.
// TODO: If we add support for non-native swizzles, we'll need to handle that here.
bool SkPngCodec::createColorTable(SkColorType dstColorType, bool premultiply, int* ctableCount) {
int numColors;
@ -372,254 +267,204 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) {
return nullptr;
}
class SkPngNormalDecoder : public SkPngCodec {
static int bytes_per_pixel(int bitsPerPixel) {
// Note that we will have to change this implementation if we start
// supporting outputs from libpng that are less than 8-bits per component.
return bitsPerPixel / 8;
}
// Subclass of SkPngCodec which supports scanline decoding
class SkPngScanlineDecoder : public SkPngCodec {
public:
SkPngNormalDecoder(int width, int height, const SkEncodedInfo& info, SkStream* stream,
SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth,
SkPngScanlineDecoder(int width, int height, const SkEncodedInfo& info, SkStream* stream,
SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr, int bitDepth,
sk_sp<SkColorSpace> colorSpace)
: INHERITED(width, height, info, stream, reader, png_ptr, info_ptr, bitDepth,
std::move(colorSpace))
, fLinesDecoded(0)
, fDst(nullptr)
, fRowBytes(0)
, fFirstRow(0)
, fLastRow(0)
: INHERITED(width, height, info, stream, chunkReader, png_ptr, info_ptr, bitDepth, 1,
colorSpace)
, fSrcRow(nullptr)
{}
static void AllRowsCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) {
GetDecoder(png_ptr)->allRowsCallback(row, rowNum);
Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
SkPMColor ctable[], int* ctableCount) override {
if (!conversion_possible(dstInfo, this->getInfo())) {
return kInvalidConversion;
}
const Result result = this->initializeSwizzler(dstInfo, options, ctable,
ctableCount);
if (result != kSuccess) {
return result;
}
fStorage.reset(this->getInfo().width() *
(bytes_per_pixel(this->getEncodedInfo().bitsPerPixel())));
fSrcRow = fStorage.get();
return kSuccess;
}
static void RowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) {
GetDecoder(png_ptr)->rowCallback(row, rowNum);
int onGetScanlines(void* dst, int count, size_t rowBytes) override {
// Assume that an error in libpng indicates an incomplete input.
int row = 0;
if (setjmp(png_jmpbuf(this->png_ptr()))) {
SkCodecPrintf("setjmp long jump!\n");
return row;
}
void* dstRow = dst;
for (; row < count; row++) {
png_read_row(this->png_ptr(), fSrcRow, nullptr);
this->swizzler()->swizzle(dstRow, fSrcRow);
dstRow = SkTAddOffset<void>(dstRow, rowBytes);
}
return row;
}
bool onSkipScanlines(int count) override {
// Assume that an error in libpng indicates an incomplete input.
if (setjmp(png_jmpbuf(this->png_ptr()))) {
SkCodecPrintf("setjmp long jump!\n");
return false;
}
for (int row = 0; row < count; row++) {
png_read_row(this->png_ptr(), fSrcRow, nullptr);
}
return true;
}
private:
int fLinesDecoded; // FIXME: Move to baseclass?
void* fDst;
size_t fRowBytes;
// Variables for partial decode
int fFirstRow; // FIXME: Move to baseclass?
int fLastRow;
SkAutoTMalloc<uint8_t> fStorage;
uint8_t* fSrcRow;
typedef SkPngCodec INHERITED;
static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) {
return static_cast<SkPngNormalDecoder*>(png_get_progressive_ptr(png_ptr));
}
Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
const int height = this->getInfo().height();
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, AllRowsCallback, nullptr);
fDst = dst;
fRowBytes = rowBytes;
fLinesDecoded = 0;
this->processData();
if (fLinesDecoded == height) {
return SkCodec::kSuccess;
}
if (rowsDecoded) {
*rowsDecoded = fLinesDecoded;
}
return SkCodec::kIncompleteInput;
}
void allRowsCallback(png_bytep row, int rowNum) {
SkASSERT(rowNum - fFirstRow == fLinesDecoded);
fLinesDecoded++;
this->swizzler()->swizzle(fDst, row);
fDst = SkTAddOffset<void>(fDst, fRowBytes);
}
void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, RowCallback, nullptr);
fFirstRow = firstRow;
fLastRow = lastRow;
fDst = dst;
fRowBytes = rowBytes;
fLinesDecoded = 0;
}
SkCodec::Result decode(int* rowsDecoded) override {
this->processData();
if (fLinesDecoded == fLastRow - fFirstRow + 1) {
return SkCodec::kSuccess;
}
if (rowsDecoded) {
*rowsDecoded = fLinesDecoded;
}
return SkCodec::kIncompleteInput;
}
void rowCallback(png_bytep row, int rowNum) {
if (rowNum < fFirstRow) {
// Ignore this row.
return;
}
SkASSERT(rowNum <= fLastRow);
if (this->swizzler()->rowNeeded(fLinesDecoded)) {
this->swizzler()->swizzle(fDst, row);
fDst = SkTAddOffset<void>(fDst, fRowBytes);
}
fLinesDecoded++;
if (rowNum == fLastRow) {
// Fake error to stop decoding scanlines.
longjmp(png_jmpbuf(this->png_ptr()), kStopDecoding);
}
}
};
class SkPngInterlacedDecoder : public SkPngCodec {
public:
SkPngInterlacedDecoder(int width, int height, const SkEncodedInfo& info, SkStream* stream,
SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth,
sk_sp<SkColorSpace> colorSpace, int numberPasses)
: INHERITED(width, height, info, stream, reader, png_ptr, info_ptr, bitDepth,
std::move(colorSpace))
, fNumberPasses(numberPasses)
, fFirstRow(0)
, fLastRow(0)
, fLinesDecoded(0)
, fInterlacedComplete(false)
, fPng_rowbytes(0)
{}
static void InterlacedRowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int pass) {
auto decoder = static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr));
decoder->interlacedRowCallback(row, rowNum, pass);
class SkPngInterlacedScanlineDecoder : public SkPngCodec {
public:
SkPngInterlacedScanlineDecoder(int width, int height, const SkEncodedInfo& info,
SkStream* stream, SkPngChunkReader* chunkReader, png_structp png_ptr,
png_infop info_ptr, int bitDepth, int numberPasses, sk_sp<SkColorSpace> colorSpace)
: INHERITED(width, height, info, stream, chunkReader, png_ptr, info_ptr, bitDepth,
numberPasses, colorSpace)
, fHeight(-1)
, fCanSkipRewind(false)
{
SkASSERT(numberPasses != 1);
}
Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
SkPMColor ctable[], int* ctableCount) override {
if (!conversion_possible(dstInfo, this->getInfo())) {
return kInvalidConversion;
}
const Result result = this->initializeSwizzler(dstInfo, options, ctable,
ctableCount);
if (result != kSuccess) {
return result;
}
fHeight = dstInfo.height();
// FIXME: This need not be called on a second call to onStartScanlineDecode.
fSrcRowBytes = this->getInfo().width() *
(bytes_per_pixel(this->getEncodedInfo().bitsPerPixel()));
fGarbageRow.reset(fSrcRowBytes);
fGarbageRowPtr = static_cast<uint8_t*>(fGarbageRow.get());
fCanSkipRewind = true;
return SkCodec::kSuccess;
}
int onGetScanlines(void* dst, int count, size_t dstRowBytes) override {
// rewind stream if have previously called onGetScanlines,
// since we need entire progressive image to get scanlines
if (fCanSkipRewind) {
// We already rewound in onStartScanlineDecode, so there is no reason to rewind.
// Next time onGetScanlines is called, we will need to rewind.
fCanSkipRewind = false;
} else {
// rewindIfNeeded resets fCurrScanline, since it assumes that start
// needs to be called again before scanline decoding. PNG scanline
// decoding is the exception, since it needs to rewind between
// calls to getScanlines. Keep track of fCurrScanline, to undo the
// reset.
const int currScanline = this->nextScanline();
// This method would never be called if currScanline is -1
SkASSERT(currScanline != -1);
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
this->updateCurrScanline(currScanline);
}
if (setjmp(png_jmpbuf(this->png_ptr()))) {
SkCodecPrintf("setjmp long jump!\n");
// FIXME (msarett): Returning 0 is pessimistic. If we can complete a single pass,
// we may be able to report that all of the memory has been initialized. Even if we
// fail on the first pass, we can still report than some scanlines are initialized.
return 0;
}
SkAutoTMalloc<uint8_t> storage(count * fSrcRowBytes);
uint8_t* storagePtr = storage.get();
uint8_t* srcRow;
const int startRow = this->nextScanline();
for (int i = 0; i < this->numberPasses(); i++) {
// read rows we planned to skip into garbage row
for (int y = 0; y < startRow; y++){
png_read_row(this->png_ptr(), fGarbageRowPtr, nullptr);
}
// read rows we care about into buffer
srcRow = storagePtr;
for (int y = 0; y < count; y++) {
png_read_row(this->png_ptr(), srcRow, nullptr);
srcRow += fSrcRowBytes;
}
// read rows we don't want into garbage buffer
for (int y = 0; y < fHeight - startRow - count; y++) {
png_read_row(this->png_ptr(), fGarbageRowPtr, nullptr);
}
}
//swizzle the rows we care about
srcRow = storagePtr;
void* dstRow = dst;
for (int y = 0; y < count; y++) {
this->swizzler()->swizzle(dstRow, srcRow);
dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
srcRow += fSrcRowBytes;
}
return count;
}
bool onSkipScanlines(int count) override {
// The non-virtual version will update fCurrScanline.
return true;
}
SkScanlineOrder onGetScanlineOrder() const override {
return kNone_SkScanlineOrder;
}
private:
const int fNumberPasses;
int fFirstRow;
int fLastRow;
void* fDst;
size_t fRowBytes;
int fLinesDecoded;
bool fInterlacedComplete;
size_t fPng_rowbytes;
SkAutoTMalloc<png_byte> fInterlaceBuffer;
int fHeight;
size_t fSrcRowBytes;
SkAutoMalloc fGarbageRow;
uint8_t* fGarbageRowPtr;
// FIXME: This imitates behavior in SkCodec::rewindIfNeeded. That function
// is called whenever some action is taken that reads the stream and
// therefore the next call will require a rewind. So it modifies a boolean
// to note that the *next* time it is called a rewind is needed.
// SkPngInterlacedScanlineDecoder has an extra wrinkle - calling
// onStartScanlineDecode followed by onGetScanlines does *not* require a
// rewind. Since rewindIfNeeded does not have this flexibility, we need to
// add another layer.
bool fCanSkipRewind;
typedef SkPngCodec INHERITED;
// FIXME: Currently sharing interlaced callback for all rows and subset. It's not
// as expensive as the subset version of non-interlaced, but it still does extra
// work.
void interlacedRowCallback(png_bytep row, int rowNum, int pass) {
if (rowNum < fFirstRow || rowNum > fLastRow) {
// Ignore this row
return;
}
png_bytep oldRow = fInterlaceBuffer.get() + (rowNum - fFirstRow) * fPng_rowbytes;
png_progressive_combine_row(this->png_ptr(), oldRow, row);
if (0 == pass) {
// The first pass initializes all rows.
SkASSERT(row);
SkASSERT(fLinesDecoded == rowNum - fFirstRow);
fLinesDecoded++;
} else {
SkASSERT(fLinesDecoded == fLastRow - fFirstRow + 1);
if (fNumberPasses - 1 == pass && rowNum == fLastRow) {
// Last pass, and we have read all of the rows we care about. Note that
// we do not care about reading anything beyond the end of the image (or
// beyond the last scanline requested).
fInterlacedComplete = true;
// Fake error to stop decoding scanlines.
longjmp(png_jmpbuf(this->png_ptr()), kStopDecoding);
}
}
}
SkCodec::Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
const int height = this->getInfo().height();
this->setUpInterlaceBuffer(height);
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr);
fFirstRow = 0;
fLastRow = height - 1;
fLinesDecoded = 0;
this->processData();
png_bytep srcRow = fInterlaceBuffer.get();
// FIXME: When resuming, this may rewrite rows that did not change.
for (int rowNum = 0; rowNum < fLinesDecoded; rowNum++) {
this->swizzler()->swizzle(dst, srcRow);
dst = SkTAddOffset<void>(dst, rowBytes);
srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes);
}
if (fInterlacedComplete) {
return SkCodec::kSuccess;
}
if (rowsDecoded) {
*rowsDecoded = fLinesDecoded;
}
return SkCodec::kIncompleteInput;
}
void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
// FIXME: We could skip rows in the interlace buffer that we won't put in the output.
this->setUpInterlaceBuffer(lastRow - firstRow + 1);
png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr);
fFirstRow = firstRow;
fLastRow = lastRow;
fDst = dst;
fRowBytes = rowBytes;
fLinesDecoded = 0;
}
SkCodec::Result decode(int* rowsDecoded) override {
this->processData();
// Now call the callback on all the rows that were decoded.
if (!fLinesDecoded) {
return SkCodec::kIncompleteInput;
}
const int lastRow = fLinesDecoded + fFirstRow - 1;
SkASSERT(lastRow <= fLastRow);
// FIXME: For resuming interlace, we may swizzle a row that hasn't changed. But it
// may be too tricky/expensive to handle that correctly.
png_bytep srcRow = fInterlaceBuffer.get();
const int sampleY = this->swizzler()->sampleY();
void* dst = fDst;
for (int rowNum = fFirstRow; rowNum <= lastRow; rowNum += sampleY) {
this->swizzler()->swizzle(dst, srcRow);
dst = SkTAddOffset<void>(dst, fRowBytes);
srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes * sampleY);
}
if (fInterlacedComplete) {
return SkCodec::kSuccess;
}
if (rowsDecoded) {
*rowsDecoded = fLinesDecoded;
}
return SkCodec::kIncompleteInput;
}
void setUpInterlaceBuffer(int height) {
fPng_rowbytes = png_get_rowbytes(this->png_ptr(), this->info_ptr());
fInterlaceBuffer.reset(fPng_rowbytes * height);
fInterlacedComplete = false;
}
};
// Reads the header and initializes the output fields, if not NULL.
@ -647,7 +492,7 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
return false;
}
AutoCleanPng autoClean(png_ptr, stream, chunkReader, outCodec);
AutoCleanPng autoClean(png_ptr);
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr) {
@ -662,6 +507,8 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
return false;
}
png_set_read_fn(png_ptr, static_cast<void*>(stream), sk_read_fn);
#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
// Hookup our chunkReader so we can see any user-chunks the caller may be interested in.
// This needs to be installed before we read the png header. Android may store ninepatch
@ -672,31 +519,12 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
}
#endif
const bool decodedBounds = autoClean.decodeBounds();
if (!decodedBounds) {
return false;
}
// On success, decodeBounds releases ownership of png_ptr and info_ptr.
if (png_ptrp) {
*png_ptrp = png_ptr;
}
if (info_ptrp) {
*info_ptrp = info_ptr;
}
// decodeBounds takes care of setting outCodec
if (outCodec) {
SkASSERT(*outCodec);
}
return true;
}
void AutoCleanPng::infoCallback() {
// The call to png_read_info() gives us all of the information from the
// PNG file before the first IDAT (image data chunk).
png_read_info(png_ptr, info_ptr);
png_uint_32 origWidth, origHeight;
int bitDepth, encodedColorType;
png_get_IHDR(fPng_ptr, fInfo_ptr, &origWidth, &origHeight, &bitDepth,
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
&encodedColorType, nullptr, nullptr, nullptr);
// Tell libpng to strip 16 bit/color files down to 8 bits/color.
@ -704,7 +532,7 @@ void AutoCleanPng::infoCallback() {
// RAW decodes?
if (bitDepth == 16) {
SkASSERT(PNG_COLOR_TYPE_PALETTE != encodedColorType);
png_set_strip_16(fPng_ptr);
png_set_strip_16(png_ptr);
}
// Now determine the default colorType and alphaType and set the required transforms.
@ -718,18 +546,18 @@ void AutoCleanPng::infoCallback() {
// byte into separate bytes (useful for paletted and grayscale images).
if (bitDepth < 8) {
// TODO: Should we use SkSwizzler here?
png_set_packing(fPng_ptr);
png_set_packing(png_ptr);
}
color = SkEncodedInfo::kPalette_Color;
// Set the alpha depending on if a transparency chunk exists.
alpha = png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS) ?
alpha = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ?
SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha;
break;
case PNG_COLOR_TYPE_RGB:
if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) {
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
// Convert to RGBA if transparency chunk exists.
png_set_tRNS_to_alpha(fPng_ptr);
png_set_tRNS_to_alpha(png_ptr);
color = SkEncodedInfo::kRGBA_Color;
alpha = SkEncodedInfo::kBinary_Alpha;
} else {
@ -741,11 +569,11 @@ void AutoCleanPng::infoCallback() {
// Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel.
if (bitDepth < 8) {
// TODO: Should we use SkSwizzler here?
png_set_expand_gray_1_2_4_to_8(fPng_ptr);
png_set_expand_gray_1_2_4_to_8(png_ptr);
}
if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(fPng_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png_ptr);
color = SkEncodedInfo::kGrayAlpha_Color;
alpha = SkEncodedInfo::kBinary_Alpha;
} else {
@ -768,55 +596,45 @@ void AutoCleanPng::infoCallback() {
alpha = SkEncodedInfo::kUnpremul_Alpha;
}
const int numberPasses = png_set_interlace_handling(fPng_ptr);
int numberPasses = png_set_interlace_handling(png_ptr);
fReadHeader = true;
#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
// 1 tells libpng to save any extra data. We may be able to be more efficient by saving
// it ourselves.
png_process_data_pause(fPng_ptr, 1);
fDecodedBounds = true;
#else
// We may have read more than the header. Empty buffer and move to the end of the
// header so future calls can read the rows.
fDecodedBounds = fStream->move(-fPng_ptr->buffer_size);
fPng_ptr->buffer_size = 0;
if (!fDecodedBounds) {
// Stream could not be moved to the correct place.
return;
autoClean.release();
if (png_ptrp) {
*png_ptrp = png_ptr;
}
#endif
if (fOutCodec) {
SkASSERT(nullptr == *fOutCodec);
sk_sp<SkColorSpace> colorSpace = read_color_space(fPng_ptr, fInfo_ptr);
if (info_ptrp) {
*info_ptrp = info_ptr;
}
if (outCodec) {
sk_sp<SkColorSpace> colorSpace = read_color_space(png_ptr, info_ptr);
if (!colorSpace) {
// Treat unmarked pngs as sRGB.
colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
}
SkEncodedInfo info = SkEncodedInfo::Make(color, alpha, 8);
if (1 == numberPasses) {
*fOutCodec = new SkPngNormalDecoder(origWidth, origHeight, info, fStream,
fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, std::move(colorSpace));
*outCodec = new SkPngScanlineDecoder(origWidth, origHeight, info, stream,
chunkReader, png_ptr, info_ptr, bitDepth, colorSpace);
} else {
*fOutCodec = new SkPngInterlacedDecoder(origWidth, origHeight, info, fStream,
fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, std::move(colorSpace),
numberPasses);
*outCodec = new SkPngInterlacedScanlineDecoder(origWidth, origHeight, info, stream,
chunkReader, png_ptr, info_ptr, bitDepth, numberPasses, colorSpace);
}
}
// Release the pointers, which are now owned by the codec or the caller is expected to
// take ownership.
this->releasePngPtrs();
return true;
}
SkPngCodec::SkPngCodec(int width, int height, const SkEncodedInfo& info, SkStream* stream,
SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr,
int bitDepth, sk_sp<SkColorSpace> colorSpace)
int bitDepth, int numberPasses, sk_sp<SkColorSpace> colorSpace)
: INHERITED(width, height, info, stream, colorSpace)
, fPngChunkReader(SkSafeRef(chunkReader))
, fPng_ptr(png_ptr)
, fInfo_ptr(info_ptr)
, fNumberPasses(numberPasses)
, fBitDepth(bitDepth)
{}
@ -838,19 +656,22 @@ void SkPngCodec::destroyReadStruct() {
// Getting the pixels
///////////////////////////////////////////////////////////////////////////////
bool SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo,
const Options& options,
SkPMColor ctable[],
int* ctableCount) {
SkCodec::Result SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo,
const Options& options,
SkPMColor ctable[],
int* ctableCount) {
// FIXME: Could we use the return value of setjmp to specify the type of
// error?
if (setjmp(png_jmpbuf(fPng_ptr))) {
return false;
SkCodecPrintf("setjmp long jump!\n");
return kInvalidInput;
}
png_read_update_info(fPng_ptr, fInfo_ptr);
if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) {
if (!this->createColorTable(requestedInfo.colorType(),
kPremul_SkAlphaType == requestedInfo.alphaType(), ctableCount)) {
return false;
return kInvalidInput;
}
}
@ -863,7 +684,7 @@ bool SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo,
options));
SkASSERT(fSwizzler);
return true;
return kSuccess;
}
@ -899,38 +720,77 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void*
}
// Note that ctable and ctableCount may be modified if there is a color table
if (!this->initializeSwizzler(requestedInfo, options, ctable, ctableCount)) {
return kInvalidInput; // or parameters?
const Result result = this->initializeSwizzler(requestedInfo, options, ctable, ctableCount);
if (result != kSuccess) {
return result;
}
return this->decodeAllRows(dst, dstRowBytes, rowsDecoded);
}
const int width = requestedInfo.width();
const int height = requestedInfo.height();
const int bpp = bytes_per_pixel(this->getEncodedInfo().bitsPerPixel());
const size_t srcRowBytes = width * bpp;
SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
void* dst, size_t rowBytes, const SkCodec::Options& options,
SkPMColor* ctable, int* ctableCount) {
if (!conversion_possible(dstInfo, this->getInfo())) {
return kInvalidConversion;
// FIXME: Could we use the return value of setjmp to specify the type of
// error?
int row = 0;
// This must be declared above the call to setjmp to avoid memory leaks on incomplete images.
SkAutoTMalloc<uint8_t> storage;
if (setjmp(png_jmpbuf(fPng_ptr))) {
// Assume that any error that occurs while reading rows is caused by an incomplete input.
if (fNumberPasses > 1) {
// FIXME (msarett): Handle incomplete interlaced pngs.
return (row == height) ? kSuccess : kInvalidInput;
}
// FIXME: We do a poor job on incomplete pngs compared to other decoders (ex: Chromium,
// Ubuntu Image Viewer). This is because we use the default buffer size in libpng (8192
// bytes), and if we can't fill the buffer, we immediately fail.
// For example, if we try to read 8192 bytes, and the image (incorrectly) only contains
// half that, which may have been enough to contain a non-zero number of lines, we fail
// when we could have decoded a few more lines and then failed.
// The read function that we provide for libpng has no way of indicating that we have
// made a partial read.
// Making our buffer size smaller improves our incomplete decodes, but what impact does
// it have on regular decode performance? Should we investigate using a different API
// instead of png_read_row? Chromium uses png_process_data.
*rowsDecoded = row;
return (row == height) ? kSuccess : kIncompleteInput;
}
if (!this->initializeSwizzler(dstInfo, options, ctable, ctableCount)) {
return kInvalidInput;
}
// FIXME: We could split these out based on subclass.
void* dstRow = dst;
if (fNumberPasses > 1) {
storage.reset(height * srcRowBytes);
uint8_t* const base = storage.get();
int firstRow, lastRow;
if (options.fSubset) {
firstRow = options.fSubset->top();
lastRow = options.fSubset->bottom() - 1;
for (int i = 0; i < fNumberPasses; i++) {
uint8_t* srcRow = base;
for (int y = 0; y < height; y++) {
png_read_row(fPng_ptr, srcRow, nullptr);
srcRow += srcRowBytes;
}
}
// Now swizzle it.
uint8_t* srcRow = base;
for (; row < height; row++) {
fSwizzler->swizzle(dstRow, srcRow);
dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
srcRow += srcRowBytes;
}
} else {
firstRow = 0;
lastRow = dstInfo.height() - 1;
storage.reset(srcRowBytes);
uint8_t* srcRow = storage.get();
for (; row < height; row++) {
png_read_row(fPng_ptr, srcRow, nullptr);
fSwizzler->swizzle(dstRow, srcRow);
dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
}
}
this->setRange(firstRow, lastRow, dst, rowBytes);
return kSuccess;
}
SkCodec::Result SkPngCodec::onIncrementalDecode(int* rowsDecoded) {
return this->decode(rowsDecoded);
// read rest of file, and get additional comment and time chunks in info_ptr
png_read_end(fPng_ptr, fInfo_ptr);
return kSuccess;
}
uint32_t SkPngCodec::onGetFillValue(SkColorType colorType) const {
@ -944,7 +804,7 @@ uint32_t SkPngCodec::onGetFillValue(SkColorType colorType) const {
SkCodec* SkPngCodec::NewFromStream(SkStream* stream, SkPngChunkReader* chunkReader) {
SkAutoTDelete<SkStream> streamDeleter(stream);
SkCodec* outCodec = nullptr;
SkCodec* outCodec;
if (read_header(stream, chunkReader, &outCodec, nullptr, nullptr)) {
// Codec has taken ownership of the stream.
SkASSERT(outCodec);

View File

@ -27,9 +27,6 @@ public:
virtual ~SkPngCodec();
protected:
SkPngCodec(int width, int height, const SkEncodedInfo&, SkStream*, SkPngChunkReader*,
png_structp, png_infop, int bitDepth, sk_sp<SkColorSpace>);
Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*)
override;
SkEncodedFormat onGetEncodedFormat() const override { return kPNG_SkEncodedFormat; }
@ -37,46 +34,34 @@ protected:
uint32_t onGetFillValue(SkColorType) const override;
// Helper to set up swizzler and color table. Also calls png_read_update_info.
bool initializeSwizzler(const SkImageInfo& requestedInfo, const Options&,
SkPMColor*, int* ctableCount);
Result initializeSwizzler(const SkImageInfo& requestedInfo, const Options&,
SkPMColor*, int* ctableCount);
SkSampler* getSampler(bool createIfNecessary) override {
SkASSERT(fSwizzler);
return fSwizzler;
}
SkPngCodec(int width, int height, const SkEncodedInfo&, SkStream*, SkPngChunkReader*,
png_structp, png_infop, int, int, sk_sp<SkColorSpace>);
png_structp png_ptr() { return fPng_ptr; }
png_infop info_ptr() { return fInfo_ptr; }
SkSwizzler* swizzler() { return fSwizzler; }
/**
* Pass available input to libpng to process it.
*
* libpng will call any relevant callbacks installed. This will continue decoding
* until it reaches the end of the file, or until a callback tells libpng to stop.
*/
void processData();
Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
const SkCodec::Options&,
SkPMColor* ctable, int* ctableCount) override;
Result onIncrementalDecode(int*) override;
int numberPasses() const { return fNumberPasses; }
private:
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;
const int fBitDepth;
const int fNumberPasses;
int fBitDepth;
bool createColorTable(SkColorType dstColorType, bool premultiply, int* ctableCount);
void destroyReadStruct();
virtual Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) = 0;
virtual void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) = 0;
virtual Result decode(int* rowsDecoded) = 0;
typedef SkCodec INHERITED;
};

View File

@ -101,65 +101,37 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void
int scaledSubsetWidth = info.width();
int scaledSubsetHeight = info.height();
const SkImageInfo scaledInfo = info.makeWH(scaledSize.width(), scaledSize.height());
{
// Although startScanlineDecode expects the bottom and top to match the
// SkImageInfo, startIncrementalDecode uses them to determine when to start
// and end calling the callback.
SkIRect incrementalSubset = SkIRect::MakeXYWH(scaledSubsetX, scaledSubsetY,
scaledSubsetWidth, scaledSubsetHeight);
codecOptions.fSubset = &incrementalSubset;
const SkCodec::Result startResult = this->codec()->startIncrementalDecode(
scaledInfo, pixels, rowBytes, &codecOptions,
options.fColorPtr, options.fColorCount);
if (SkCodec::kSuccess == startResult) {
int rowsDecoded;
const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded);
if (incResult == SkCodec::kSuccess) {
return SkCodec::kSuccess;
}
SkASSERT(SkCodec::kIncompleteInput == incResult);
// FIXME: Can zero initialized be read from SkCodec::fOptions?
this->codec()->fillIncompleteImage(scaledInfo, pixels, rowBytes,
options.fZeroInitialized, scaledSubsetHeight, rowsDecoded);
return SkCodec::kIncompleteInput;
} else if (startResult != SkCodec::kUnimplemented) {
return startResult;
}
// Otherwise fall down to use the old scanline decoder.
// codecOptions.fSubset will be reset below, so it will not continue to
// point to the object that is no longer on the stack.
}
// Start the scanline decode.
SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth,
scaledSize.height());
codecOptions.fSubset = &scanlineSubset;
SkCodec::Result result = this->codec()->startScanlineDecode(scaledInfo,
&codecOptions, options.fColorPtr, options.fColorCount);
SkCodec::Result result = this->codec()->startScanlineDecode(info.makeWH(scaledSize.width(),
scaledSize.height()), &codecOptions, options.fColorPtr, options.fColorCount);
if (SkCodec::kSuccess != result) {
return result;
}
// At this point, we are only concerned with subsetting. Either no scale was
// requested, or the this->codec() is handling the scale.
// Note that subsetting is only supported for kTopDown, so this code will not be
// reached for other orders.
SkASSERT(this->codec()->getScanlineOrder() == SkCodec::kTopDown_SkScanlineOrder);
if (!this->codec()->skipScanlines(scaledSubsetY)) {
this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
scaledSubsetHeight, 0);
return SkCodec::kIncompleteInput;
}
switch (this->codec()->getScanlineOrder()) {
case SkCodec::kTopDown_SkScanlineOrder:
case SkCodec::kNone_SkScanlineOrder: {
if (!this->codec()->skipScanlines(scaledSubsetY)) {
this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
scaledSubsetHeight, 0);
return SkCodec::kIncompleteInput;
}
int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes);
if (decodedLines != scaledSubsetHeight) {
return SkCodec::kIncompleteInput;
int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes);
if (decodedLines != scaledSubsetHeight) {
return SkCodec::kIncompleteInput;
}
return SkCodec::kSuccess;
}
default:
SkASSERT(false);
return SkCodec::kUnimplemented;
}
return SkCodec::kSuccess;
}
@ -202,70 +174,10 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
sampledOptions.fSubset = &subset;
}
// Since we guarantee that output dimensions are always at least one (even if the sampleSize
// is greater than a given dimension), the input sampleSize is not always the sampleSize that
// we use in practice.
const int sampleX = subsetWidth / info.width();
const int sampleY = subsetHeight / info.height();
const int samplingOffsetY = get_start_coord(sampleY);
const int startY = samplingOffsetY + subsetY;
int dstHeight = info.height();
const SkImageInfo nativeInfo = info.makeWH(nativeSize.width(), nativeSize.height());
{
// Although startScanlineDecode expects the bottom and top to match the
// SkImageInfo, startIncrementalDecode uses them to determine when to start
// and end calling the callback.
SkIRect incrementalSubset;
incrementalSubset.fTop = startY;
incrementalSubset.fBottom = startY + (dstHeight - 1) * sampleY + 1;
if (sampledOptions.fSubset) {
incrementalSubset.fLeft = sampledOptions.fSubset->fLeft;
incrementalSubset.fRight = sampledOptions.fSubset->fRight;
} else {
incrementalSubset.fLeft = 0;
incrementalSubset.fRight = nativeSize.width();
}
SkCodec::Options incrementalOptions = sampledOptions;
incrementalOptions.fSubset = &incrementalSubset;
const SkCodec::Result startResult = this->codec()->startIncrementalDecode(nativeInfo,
pixels, rowBytes, &incrementalOptions, options.fColorPtr, options.fColorCount);
if (SkCodec::kSuccess == startResult) {
SkSampler* sampler = this->codec()->getSampler(true);
if (!sampler) {
return SkCodec::kUnimplemented;
}
if (sampler->setSampleX(sampleX) != info.width()) {
return SkCodec::kInvalidScale;
}
if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) {
return SkCodec::kInvalidScale;
}
sampler->setSampleY(sampleY);
int rowsDecoded;
const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded);
if (incResult == SkCodec::kSuccess) {
return SkCodec::kSuccess;
}
SkASSERT(incResult == SkCodec::kIncompleteInput);
const int lastRowInOutput = (rowsDecoded - startY) / sampleY;
// FIXME: Should this be info or nativeInfo? Does it make a difference?
this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
info.height(), lastRowInOutput);
return SkCodec::kIncompleteInput;
} else if (startResult != SkCodec::kUnimplemented) {
return startResult;
} // kUnimplemented means use the old method.
}
// Start the scanline decode.
SkCodec::Result result = this->codec()->startScanlineDecode(nativeInfo,
&sampledOptions, options.fColorPtr, options.fColorCount);
SkCodec::Result result = this->codec()->startScanlineDecode(
info.makeWH(nativeSize.width(), nativeSize.height()), &sampledOptions,
options.fColorPtr, options.fColorCount);
if (SkCodec::kSuccess != result) {
return result;
}
@ -275,6 +187,11 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
return SkCodec::kUnimplemented;
}
// Since we guarantee that output dimensions are always at least one (even if the sampleSize
// is greater than a given dimension), the input sampleSize is not always the sampleSize that
// we use in practice.
const int sampleX = subsetWidth / info.width();
const int sampleY = subsetHeight / info.height();
if (sampler->setSampleX(sampleX) != info.width()) {
return SkCodec::kInvalidScale;
}
@ -282,6 +199,9 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
return SkCodec::kInvalidScale;
}
const int samplingOffsetY = get_start_coord(sampleY);
const int startY = samplingOffsetY + subsetY;
int dstHeight = info.height();
switch(this->codec()->getScanlineOrder()) {
case SkCodec::kTopDown_SkScanlineOrder: {
if (!this->codec()->skipScanlines(startY)) {
@ -346,6 +266,30 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
}
return SkCodec::kIncompleteInput;
}
case SkCodec::kNone_SkScanlineOrder: {
const int linesNeeded = subsetHeight - samplingOffsetY;
SkAutoTMalloc<uint8_t> storage(linesNeeded * rowBytes);
uint8_t* storagePtr = storage.get();
if (!this->codec()->skipScanlines(startY)) {
this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
dstHeight, 0);
return SkCodec::kIncompleteInput;
}
int scanlines = this->codec()->getScanlines(storagePtr, linesNeeded, rowBytes);
for (int y = 0; y < dstHeight; y++) {
memcpy(pixels, storagePtr, info.minRowBytes());
storagePtr += sampleY * rowBytes;
pixels = SkTAddOffset<void>(pixels, rowBytes);
}
if (scanlines < dstHeight) {
// this->codec() has already handled filling uninitialized memory.
return SkCodec::kIncompleteInput;
}
return SkCodec::kSuccess;
}
default:
SkASSERT(false);
return SkCodec::kUnimplemented;

View File

@ -20,30 +20,6 @@ public:
return this->onSetSampleX(sampleX);
}
/**
* Update the sampler to sample every sampleY'th row.
*/
void setSampleY(int sampleY) {
fSampleY = sampleY;
}
/**
* Retrieve the value set for sampleY.
*/
int sampleY() const {
return fSampleY;
}
/**
* Based on fSampleY, return whether this row belongs in the output.
*
* @param row Row of the image, starting with the first row used in the
* output.
*/
bool rowNeeded(int row) const {
return row % fSampleY == 0;
}
/**
* Fill the remainder of the destination with a single color
*
@ -78,13 +54,8 @@ public:
virtual void fill(const SkImageInfo& info, void* dst, size_t rowBytes,
uint32_t colorOrIndex, SkCodec::ZeroInitialized zeroInit) {}
SkSampler()
: fSampleY(1)
{}
virtual ~SkSampler() {}
private:
int fSampleY;
virtual int onSetSampleX(int) = 0;
};

View File

@ -1,155 +0,0 @@
/*
* 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(data.get()));
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");
}

View File

@ -83,62 +83,6 @@ SkIRect generate_random_subset(SkRandom* rand, int w, int h) {
return rect;
}
static void test_incremental_decode(skiatest::Reporter* r, SkCodec* codec, const SkImageInfo& info,
const SkMD5::Digest& goodDigest) {
SkBitmap bm;
bm.allocPixels(info);
SkAutoLockPixels autoLockPixels(bm);
REPORTER_ASSERT(r, SkCodec::kSuccess == codec->startIncrementalDecode(info, bm.getPixels(),
bm.rowBytes()));
REPORTER_ASSERT(r, SkCodec::kSuccess == codec->incrementalDecode());
compare_to_good_digest(r, goodDigest, bm);
}
// Test in stripes, similar to DM's kStripe_Mode
static void test_in_stripes(skiatest::Reporter* r, SkCodec* codec, const SkImageInfo& info,
const SkMD5::Digest& goodDigest) {
SkBitmap bm;
bm.allocPixels(info);
bm.eraseColor(SK_ColorYELLOW);
const int height = info.height();
// Note that if numStripes does not evenly divide height there will be an extra
// stripe.
const int numStripes = 4;
if (numStripes > height) {
// Image is too small.
return;
}
const int stripeHeight = height / numStripes;
// Iterate through the image twice. Once to decode odd stripes, and once for even.
for (int oddEven = 1; oddEven >= 0; oddEven--) {
for (int y = oddEven * stripeHeight; y < height; y += 2 * stripeHeight) {
SkIRect subset = SkIRect::MakeLTRB(0, y, info.width(),
SkTMin(y + stripeHeight, height));
SkCodec::Options options;
options.fSubset = &subset;
if (SkCodec::kSuccess != codec->startIncrementalDecode(info, bm.getAddr(0, y),
bm.rowBytes(), &options)) {
ERRORF(r, "failed to start incremental decode!\ttop: %i\tbottom%i\n",
subset.top(), subset.bottom());
return;
}
if (SkCodec::kSuccess != codec->incrementalDecode()) {
ERRORF(r, "failed incremental decode starting from line %i\n", y);
return;
}
}
}
compare_to_good_digest(r, goodDigest, bm);
}
template<typename Codec>
static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const SkImageInfo& info,
const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* digest,
@ -247,14 +191,12 @@ static bool supports_partial_scanlines(const char path[]) {
return false;
}
// FIXME: Break up this giant function
static void check(skiatest::Reporter* r,
const char path[],
SkISize size,
bool supportsScanlineDecoding,
bool supportsSubsetDecoding,
bool supportsIncomplete,
bool supportsNewScanlineDecoding = false) {
bool supportsIncomplete = true) {
SkAutoTDelete<SkStream> stream(resource(path));
if (!stream) {
@ -284,15 +226,12 @@ static void check(skiatest::Reporter* r,
test_codec(r, codec.get(), bm, info, size, expectedResult, &codecDigest, nullptr);
// Scanline decoding follows.
if (supportsNewScanlineDecoding && !isIncomplete) {
test_incremental_decode(r, codec, info, codecDigest);
test_in_stripes(r, codec, info, codecDigest);
}
// Need to call startScanlineDecode() first.
REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0);
REPORTER_ASSERT(r, !codec->skipScanlines(1));
REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
== 0);
REPORTER_ASSERT(r, codec->skipScanlines(1)
== 0);
const SkCodec::Result startResult = codec->startScanlineDecode(info);
if (supportsScanlineDecoding) {
bm.eraseColor(SK_ColorYELLOW);
@ -387,7 +326,7 @@ static void check(skiatest::Reporter* r,
}
// SkAndroidCodec tests
if (supportsScanlineDecoding || supportsSubsetDecoding || supportsNewScanlineDecoding) {
if (supportsScanlineDecoding || supportsSubsetDecoding) {
SkAutoTDelete<SkStream> stream(resource(path));
if (!stream) {
@ -425,23 +364,14 @@ static void check(skiatest::Reporter* r,
REPORTER_ASSERT(r, gen->getPixels(info, bm.getPixels(), bm.rowBytes()));
compare_to_good_digest(r, codecDigest, bm);
#if PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR < 5
// FIXME: With older versions of libpng, SkPngCodec requires being able to call
// SkStream::move(), which is not supported by SkFrontBufferedStream. (Android
// has a more recent version of libpng which uses png_process_data_pause to
// avoid calling move().)
if (!SkStrEndsWith(path, ".png"))
#endif
{
// Test using SkFrontBufferedStream, as Android does
SkStream* bufferedStream = SkFrontBufferedStream::Create(new SkMemoryStream(fullData),
SkCodec::MinBufferedBytesNeeded());
REPORTER_ASSERT(r, bufferedStream);
codec.reset(SkCodec::NewFromStream(bufferedStream));
REPORTER_ASSERT(r, codec);
if (codec) {
test_info(r, codec.get(), info, SkCodec::kSuccess, &codecDigest);
}
// Test using SkFrontBufferedStream, as Android does
SkStream* bufferedStream = SkFrontBufferedStream::Create(new SkMemoryStream(fullData),
SkCodec::MinBufferedBytesNeeded());
REPORTER_ASSERT(r, bufferedStream);
codec.reset(SkCodec::NewFromStream(bufferedStream));
REPORTER_ASSERT(r, codec);
if (codec) {
test_info(r, codec.get(), info, SkCodec::kSuccess, &codecDigest);
}
}
@ -453,16 +383,16 @@ static void check(skiatest::Reporter* r,
DEF_TEST(Codec, r) {
// WBMP
check(r, "mandrill.wbmp", SkISize::Make(512, 512), true, false, true);
check(r, "mandrill.wbmp", SkISize::Make(512, 512), true, false);
// WEBP
check(r, "baby_tux.webp", SkISize::Make(386, 395), false, true, true);
check(r, "color_wheel.webp", SkISize::Make(128, 128), false, true, true);
check(r, "yellow_rose.webp", SkISize::Make(400, 301), false, true, true);
check(r, "baby_tux.webp", SkISize::Make(386, 395), false, true);
check(r, "color_wheel.webp", SkISize::Make(128, 128), false, true);
check(r, "yellow_rose.webp", SkISize::Make(400, 301), false, true);
// BMP
check(r, "randPixels.bmp", SkISize::Make(8, 8), true, false, true);
check(r, "rle.bmp", SkISize::Make(320, 240), true, false, true);
check(r, "randPixels.bmp", SkISize::Make(8, 8), true, false);
check(r, "rle.bmp", SkISize::Make(320, 240), true, false);
// ICO
// FIXME: We are not ready to test incomplete ICOs
@ -470,7 +400,7 @@ DEF_TEST(Codec, r) {
// Decodes an embedded BMP image
check(r, "color_wheel.ico", SkISize::Make(128, 128), true, false, false);
// Decodes an embedded PNG image
check(r, "google_chrome.ico", SkISize::Make(256, 256), false, false, false, true);
check(r, "google_chrome.ico", SkISize::Make(256, 256), true, false, false);
// GIF
// FIXME: We are not ready to test incomplete GIFs
@ -480,30 +410,30 @@ DEF_TEST(Codec, r) {
check(r, "randPixels.gif", SkISize::Make(8, 8), true, false, false);
// JPG
check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false, true);
check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false, true);
check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false);
check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false);
// grayscale.jpg is too small to test incomplete
check(r, "grayscale.jpg", SkISize::Make(128, 128), true, false, false);
check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false, true);
check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false);
// randPixels.jpg is too small to test incomplete
check(r, "randPixels.jpg", SkISize::Make(8, 8), true, false, false);
// PNG
check(r, "arrow.png", SkISize::Make(187, 312), false, false, true, true);
check(r, "baby_tux.png", SkISize::Make(240, 246), false, false, true, true);
check(r, "color_wheel.png", SkISize::Make(128, 128), false, false, true, true);
// half-transparent-white-pixel.png is too small to test incomplete
check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), false, false, false, true);
check(r, "mandrill_128.png", SkISize::Make(128, 128), false, false, true, true);
check(r, "mandrill_16.png", SkISize::Make(16, 16), false, false, true, true);
check(r, "mandrill_256.png", SkISize::Make(256, 256), false, false, true, true);
check(r, "mandrill_32.png", SkISize::Make(32, 32), false, false, true, true);
check(r, "mandrill_512.png", SkISize::Make(512, 512), false, false, true, true);
check(r, "mandrill_64.png", SkISize::Make(64, 64), false, false, true, true);
check(r, "plane.png", SkISize::Make(250, 126), false, false, true, true);
check(r, "plane_interlaced.png", SkISize::Make(250, 126), false, false, true, true);
check(r, "randPixels.png", SkISize::Make(8, 8), false, false, true, true);
check(r, "yellow_rose.png", SkISize::Make(400, 301), false, false, true, true);
check(r, "arrow.png", SkISize::Make(187, 312), true, false, false);
check(r, "baby_tux.png", SkISize::Make(240, 246), true, false, false);
check(r, "color_wheel.png", SkISize::Make(128, 128), true, false, false);
check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true, false, false);
check(r, "mandrill_128.png", SkISize::Make(128, 128), true, false, false);
check(r, "mandrill_16.png", SkISize::Make(16, 16), true, false, false);
check(r, "mandrill_256.png", SkISize::Make(256, 256), true, false, false);
check(r, "mandrill_32.png", SkISize::Make(32, 32), true, false, false);
check(r, "mandrill_512.png", SkISize::Make(512, 512), true, false, false);
check(r, "mandrill_64.png", SkISize::Make(64, 64), true, false, false);
check(r, "plane.png", SkISize::Make(250, 126), true, false, false);
// FIXME: We are not ready to test incomplete interlaced pngs
check(r, "plane_interlaced.png", SkISize::Make(250, 126), true, false, false);
check(r, "randPixels.png", SkISize::Make(8, 8), true, false, false);
check(r, "yellow_rose.png", SkISize::Make(400, 301), true, false, false);
// RAW
// Disable RAW tests for Win32.
@ -514,6 +444,97 @@ DEF_TEST(Codec, r) {
#endif
}
// Test interlaced PNG in stripes, similar to DM's kStripe_Mode
DEF_TEST(Codec_stripes, r) {
const char * path = "plane_interlaced.png";
SkAutoTDelete<SkStream> stream(resource(path));
if (!stream) {
SkDebugf("Missing resource '%s'\n", path);
}
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
REPORTER_ASSERT(r, codec);
if (!codec) {
return;
}
switch (codec->getScanlineOrder()) {
case SkCodec::kBottomUp_SkScanlineOrder:
case SkCodec::kOutOfOrder_SkScanlineOrder:
ERRORF(r, "This scanline order will not match the original.");
return;
default:
break;
}
// Baseline for what the image should look like, using N32.
const SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
SkBitmap bm;
bm.allocPixels(info);
SkAutoLockPixels autoLockPixels(bm);
SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
SkMD5::Digest digest;
md5(bm, &digest);
// Now decode in stripes
const int height = info.height();
const int numStripes = 4;
int stripeHeight;
int remainingLines;
SkTDivMod(height, numStripes, &stripeHeight, &remainingLines);
bm.eraseColor(SK_ColorYELLOW);
result = codec->startScanlineDecode(info);
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
// Odd stripes
for (int i = 1; i < numStripes; i += 2) {
// Skip the even stripes
bool skipResult = codec->skipScanlines(stripeHeight);
REPORTER_ASSERT(r, skipResult);
int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight,
bm.rowBytes());
REPORTER_ASSERT(r, linesDecoded == stripeHeight);
}
// Even stripes
result = codec->startScanlineDecode(info);
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
for (int i = 0; i < numStripes; i += 2) {
int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight,
bm.rowBytes());
REPORTER_ASSERT(r, linesDecoded == stripeHeight);
// Skip the odd stripes
if (i + 1 < numStripes) {
bool skipResult = codec->skipScanlines(stripeHeight);
REPORTER_ASSERT(r, skipResult);
}
}
// Remainder at the end
if (remainingLines > 0) {
result = codec->startScanlineDecode(info);
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
bool skipResult = codec->skipScanlines(height - remainingLines);
REPORTER_ASSERT(r, skipResult);
int linesDecoded = codec->getScanlines(bm.getAddr(0, height - remainingLines),
remainingLines, bm.rowBytes());
REPORTER_ASSERT(r, linesDecoded == remainingLines);
}
compare_to_good_digest(r, digest, bm);
}
static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_t len) {
// Neither of these calls should return a codec. Bots should catch us if we leaked anything.
SkCodec* codec = SkCodec::NewFromStream(new SkMemoryStream(stream, len, false));
@ -656,41 +677,26 @@ static void test_invalid_parameters(skiatest::Reporter* r, const char path[]) {
return;
}
SkAutoTDelete<SkCodec> decoder(SkCodec::NewFromStream(stream.release()));
if (!decoder) {
SkDebugf("Missing codec for %s\n", path);
return;
}
const SkImageInfo info = decoder->getInfo().makeColorType(kIndex_8_SkColorType);
// This should return kSuccess because kIndex8 is supported.
SkPMColor colorStorage[256];
int colorCount;
SkCodec::Result result = decoder->startScanlineDecode(info, nullptr, colorStorage,
&colorCount);
if (SkCodec::kSuccess == result) {
// This should return kInvalidParameters because, in kIndex_8 mode, we must pass in a valid
// colorPtr and a valid colorCountPtr.
result = decoder->startScanlineDecode(info, nullptr, nullptr, nullptr);
REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
result = decoder->startScanlineDecode(info);
REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
} else if (SkCodec::kUnimplemented == result) {
// New method should be supported:
SkBitmap bm;
sk_sp<SkColorTable> colorTable(new SkColorTable(colorStorage, 256));
bm.allocPixels(info, nullptr, colorTable.get());
result = decoder->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes(), nullptr,
colorStorage, &colorCount);
REPORTER_ASSERT(r, SkCodec::kSuccess == result);
result = decoder->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
} else {
// The test is uninteresting if kIndex8 is not supported
ERRORF(r, "Should not call test_invalid_parameters for non-Index8 file: %s\n", path);
SkCodec::Result result = decoder->startScanlineDecode(
decoder->getInfo().makeColorType(kIndex_8_SkColorType), nullptr, colorStorage, &colorCount);
REPORTER_ASSERT(r, SkCodec::kSuccess == result);
// The rest of the test is uninteresting if kIndex8 is not supported
if (SkCodec::kSuccess != result) {
return;
}
// This should return kInvalidParameters because, in kIndex_8 mode, we must pass in a valid
// colorPtr and a valid colorCountPtr.
result = decoder->startScanlineDecode(
decoder->getInfo().makeColorType(kIndex_8_SkColorType), nullptr, nullptr, nullptr);
REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
result = decoder->startScanlineDecode(
decoder->getInfo().makeColorType(kIndex_8_SkColorType));
REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
}
DEF_TEST(Codec_Params, r) {
@ -1006,91 +1012,3 @@ DEF_TEST(Codec_wbmp_max_size, r) {
REPORTER_ASSERT(r, !codec);
}
// Only rewinds up to a limit.
class LimitedRewindingStream : public SkStream {
public:
static SkStream* Make(const char path[], size_t limit) {
SkStream* stream = resource(path);
if (!stream) {
return nullptr;
}
return new LimitedRewindingStream(stream, limit);
}
size_t read(void* buffer, size_t size) override {
const size_t bytes = fStream->read(buffer, size);
fPosition += bytes;
return bytes;
}
bool isAtEnd() const override {
return fStream->isAtEnd();
}
bool rewind() override {
if (fPosition <= fLimit && fStream->rewind()) {
fPosition = 0;
return true;
}
return false;
}
private:
SkAutoTDelete<SkStream> fStream;
const size_t fLimit;
size_t fPosition;
LimitedRewindingStream(SkStream* stream, size_t limit)
: fStream(stream)
, fLimit(limit)
, fPosition(0)
{
SkASSERT(fStream);
}
};
DEF_TEST(Codec_fallBack, r) {
// SkAndroidCodec needs to be able to fall back to scanline decoding
// if incremental decoding does not work. Make sure this does not
// require a rewind.
// Formats that currently do not support incremental decoding
auto files = {
"box.gif",
"CMYK.jpg",
"color_wheel.ico",
"mandrill.wbmp",
"randPixels.bmp",
};
for (auto file : files) {
SkStream* stream = LimitedRewindingStream::Make(file, 14);
if (!stream) {
SkDebugf("Missing resources (%s). Set --resourcePath.\n", file);
return;
}
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream));
if (!codec) {
ERRORF(r, "Failed to create codec for %s,", file);
continue;
}
SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
SkBitmap bm;
bm.allocPixels(info);
if (SkCodec::kUnimplemented != codec->startIncrementalDecode(info, bm.getPixels(),
bm.rowBytes())) {
ERRORF(r, "Is scanline decoding now implemented for %s?", file);
continue;
}
// Scanline decoding should not require a rewind.
SkCodec::Result result = codec->startScanlineDecode(info);
if (SkCodec::kSuccess != result) {
ERRORF(r, "Scanline decoding failed for %s with %i", file, result);
}
}
}

View File

@ -67,10 +67,7 @@ DEF_TEST(ColorSpaceParsePngICCProfile, r) {
}
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
if (!codec) {
ERRORF(r, "Failed to create codec from a PNG. Is PNG support missing (or too old)?");
return;
}
REPORTER_ASSERT(r, nullptr != codec);
#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6)
SkColorSpace* colorSpace = codec->getColorSpace();

View File

@ -161,10 +161,6 @@ static void test_encode(skiatest::Reporter* reporter, SkImage* image) {
REPORTER_ASSERT(reporter, origEncoded->size() > 0);
sk_sp<SkImage> decoded(SkImage::MakeFromEncoded(origEncoded));
if (!decoded) {
ERRORF(reporter, "failed to decode image!");
return;
}
REPORTER_ASSERT(reporter, decoded);
assert_equal(reporter, image, nullptr, decoded.get());
@ -522,10 +518,6 @@ static bool has_pixels(const SkPMColor pixels[], int count, SkPMColor expected)
}
static void test_read_pixels(skiatest::Reporter* reporter, SkImage* image) {
if (!image) {
ERRORF(reporter, "Failed to create image!");
return;
}
const SkPMColor expected = SkPreMultiplyColor(SK_ColorWHITE);
const SkPMColor notExpected = ~expected;
@ -611,10 +603,6 @@ static void check_legacy_bitmap(skiatest::Reporter* reporter, const SkImage* ima
}
static void test_legacy_bitmap(skiatest::Reporter* reporter, const SkImage* image, SkImage::LegacyBitmapMode mode) {
if (!image) {
ERRORF(reporter, "Failed to create image.");
return;
}
SkBitmap bitmap;
REPORTER_ASSERT(reporter, image->asLegacyBitmap(&bitmap, mode));
check_legacy_bitmap(reporter, image, bitmap, mode);
@ -666,10 +654,6 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageLegacyBitmap_Gpu, reporter, ctxInfo) {
#endif
static void test_peek(skiatest::Reporter* reporter, SkImage* image, bool expectPeekSuccess) {
if (!image) {
ERRORF(reporter, "Failed to create image!");
return;
}
SkPixmap pm;
bool success = image->peekPixels(&pm);
REPORTER_ASSERT(reporter, expectPeekSuccess == success);
@ -873,10 +857,6 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
for (auto testCase : testCases) {
sk_sp<SkImage> image(testCase.fImageFactory());
if (!image) {
ERRORF(reporter, "Failed to create image!");
continue;
}
// This isn't currently used in the implementation, just set any old values.
SkImage::DeferredTextureImageUsageParams params;