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:
parent
a4fa4f6eb7
commit
d8d6855345
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 = ⊂
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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");
|
||||
}
|
@ -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 = ⊂
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user