From e6dd004c1b8a81dc37a370570877b8b7d6dbe308 Mon Sep 17 00:00:00 2001 From: msarett Date: Fri, 9 Oct 2015 11:07:34 -0700 Subject: [PATCH] Fill incomplete images in SkCodec parent class Rather than implementing some sort of "fill" in every SkCodec subclass for incomplete images, let's make the parent class handle this situation. This includes an API change to SkCodec.h SkCodec::getScanlines() now returns the number of lines it read successfully, rather than an SkCodec::Result enum. getScanlines() most often fails on an incomplete input, in which case it is useful to know how many lines were successfully decoded - this provides more information than kIncomplete vs kSuccess. We do lose information when the API is used improperly, as we are no longer able to return kInvalidParameter or kScanlineNotStarted. Known Issues: Does not work for incomplete fFrameIsSubset gifs. Does not work for incomplete icos. BUG=skia: Review URL: https://codereview.chromium.org/1332053002 --- bench/subset/SubsetSingleBench.cpp | 12 +- bench/subset/SubsetTranslateBench.cpp | 12 +- bench/subset/SubsetZoomBench.cpp | 12 +- dm/DMSrcSink.cpp | 112 +--- gyp/codec.gyp | 1 + gyp/tools.gyp | 3 +- include/codec/SkCodec.h | 116 +++- include/codec/SkScaledCodec.h | 6 +- src/codec/SkBmpCodec.cpp | 18 +- src/codec/SkBmpCodec.h | 16 +- src/codec/SkBmpMaskCodec.cpp | 21 +- src/codec/SkBmpMaskCodec.h | 11 +- src/codec/SkBmpRLECodec.cpp | 53 +- src/codec/SkBmpRLECodec.h | 8 +- src/codec/SkBmpStandardCodec.cpp | 30 +- src/codec/SkBmpStandardCodec.h | 14 +- src/codec/SkCodec.cpp | 106 +++- src/codec/SkCodecPriv.h | 65 +- src/codec/SkCodec_libgif.cpp | 121 ++-- src/codec/SkCodec_libgif.h | 20 +- src/codec/SkCodec_libico.cpp | 12 +- src/codec/SkCodec_libico.h | 5 +- src/codec/SkCodec_libpng.cpp | 75 ++- src/codec/SkCodec_libpng.h | 8 +- src/codec/SkCodec_wbmp.cpp | 26 +- src/codec/SkCodec_wbmp.h | 15 +- src/codec/SkJpegCodec.cpp | 41 +- src/codec/SkJpegCodec.h | 8 +- src/codec/SkMaskSwizzler.h | 9 + src/codec/SkSampler.cpp | 102 +++ src/codec/SkSampler.h | 35 ++ src/codec/SkScaledCodec.cpp | 90 ++- src/codec/SkSwizzler.cpp | 79 --- src/codec/SkSwizzler.h | 48 +- src/codec/SkWebpCodec.cpp | 9 +- src/codec/SkWebpCodec.h | 2 +- tests/CodexTest.cpp | 151 +++-- tests/SwizzlerTest.cpp | 66 +- tools/SkBitmapRegionCanvas.cpp | 13 +- tools/SkBitmapRegionDecoderInterface.cpp | 8 +- tools/dm_flags.json | 760 +++++++++++++++++++++++ tools/dm_flags.py | 21 + 42 files changed, 1662 insertions(+), 678 deletions(-) create mode 100644 src/codec/SkSampler.cpp diff --git a/bench/subset/SubsetSingleBench.cpp b/bench/subset/SubsetSingleBench.cpp index 735effa100..b4e92e4e22 100644 --- a/bench/subset/SubsetSingleBench.cpp +++ b/bench/subset/SubsetSingleBench.cpp @@ -75,8 +75,8 @@ void SubsetSingleBench::onDraw(int n, SkCanvas* canvas) { SkImageInfo subsetInfo = info.makeWH(fSubsetWidth, fSubsetHeight); alloc_pixels(&bitmap, subsetInfo, colors, colorCount); - SkDEBUGCODE(result = ) codec->skipScanlines(fOffsetTop); - SkASSERT(result == SkCodec::kSuccess); + SkDEBUGCODE(int lines = ) codec->skipScanlines(fOffsetTop); + SkASSERT((uint32_t) lines == fOffsetTop); const uint32_t bpp = info.bytesPerPixel(); @@ -84,8 +84,8 @@ void SubsetSingleBench::onDraw(int n, SkCanvas* canvas) { case SkCodec::kTopDown_SkScanlineOrder: { SkAutoTDeleteArray row(new uint8_t[info.minRowBytes()]); for (uint32_t y = 0; y < fSubsetHeight; y++) { - SkDEBUGCODE(result = ) codec->getScanlines(row.get(), 1, 0); - SkASSERT(result == SkCodec::kSuccess); + SkDEBUGCODE(lines = ) codec->getScanlines(row.get(), 1, 0); + SkASSERT(lines == 1); memcpy(bitmap.getAddr(0, y), row.get() + fOffsetLeft * bpp, fSubsetWidth * bpp); @@ -99,9 +99,9 @@ void SubsetSingleBench::onDraw(int n, SkCanvas* canvas) { SkBitmap stripeBm; alloc_pixels(&stripeBm, stripeInfo, colors, colorCount); - SkDEBUGCODE(result = ) codec->getScanlines(stripeBm.getPixels(), fSubsetHeight, + SkDEBUGCODE(lines = ) codec->getScanlines(stripeBm.getPixels(), fSubsetHeight, stripeBm.rowBytes()); - SkASSERT(result == SkCodec::kSuccess); + SkASSERT((uint32_t) lines == fSubsetHeight); for (uint32_t y = 0; y < fSubsetHeight; y++) { memcpy(bitmap.getAddr(0, y), stripeBm.getAddr(fOffsetLeft, y), diff --git a/bench/subset/SubsetTranslateBench.cpp b/bench/subset/SubsetTranslateBench.cpp index d13f31a86d..f6f3d4b222 100644 --- a/bench/subset/SubsetTranslateBench.cpp +++ b/bench/subset/SubsetTranslateBench.cpp @@ -91,8 +91,8 @@ void SubsetTranslateBench::onDraw(int n, SkCanvas* canvas) { codec->startScanlineDecode(info, nullptr, get_colors(&bitmap), &colorCount); SkASSERT(SkCodec::kSuccess == result); - SkDEBUGCODE(result =) codec->skipScanlines(y); - SkASSERT(SkCodec::kSuccess == result); + SkDEBUGCODE(int lines =) codec->skipScanlines(y); + SkASSERT(y == lines); const uint32_t currSubsetWidth = x + (int) fSubsetWidth > info.width() ? @@ -104,8 +104,8 @@ void SubsetTranslateBench::onDraw(int n, SkCanvas* canvas) { switch (codec->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: for (uint32_t y = 0; y < currSubsetHeight; y++) { - SkDEBUGCODE(result =) codec->getScanlines(row.get(), 1, 0); - SkASSERT(SkCodec::kSuccess == result); + SkDEBUGCODE(lines =) codec->getScanlines(row.get(), 1, 0); + SkASSERT(1 == lines); memcpy(bitmap.getAddr(0, y), row.get() + x * bpp, currSubsetWidth * bpp); @@ -118,9 +118,9 @@ void SubsetTranslateBench::onDraw(int n, SkCanvas* canvas) { SkBitmap stripeBm; alloc_pixels(&stripeBm, stripeInfo, colors, colorCount); - SkDEBUGCODE(result =) codec->getScanlines(stripeBm.getPixels(), + SkDEBUGCODE(lines =) codec->getScanlines(stripeBm.getPixels(), currSubsetHeight, stripeBm.rowBytes()); - SkASSERT(SkCodec::kSuccess == result); + SkASSERT(currSubsetHeight == (uint32_t) lines); for (uint32_t subsetY = 0; subsetY < currSubsetHeight; subsetY++) { memcpy(bitmap.getAddr(0, subsetY), stripeBm.getAddr(x, subsetY), diff --git a/bench/subset/SubsetZoomBench.cpp b/bench/subset/SubsetZoomBench.cpp index 901fbb6082..3edc17f1cb 100644 --- a/bench/subset/SubsetZoomBench.cpp +++ b/bench/subset/SubsetZoomBench.cpp @@ -88,14 +88,14 @@ void SubsetZoomBench::onDraw(int n, SkCanvas* canvas) { uint32_t bpp = info.bytesPerPixel(); - SkDEBUGCODE(result = ) codec->skipScanlines(subsetStartY); - SkASSERT(SkCodec::kSuccess == result); + SkDEBUGCODE(int lines = ) codec->skipScanlines(subsetStartY); + SkASSERT(subsetStartY == lines); switch (codec->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: for (int y = 0; y < subsetHeight; y++) { - SkDEBUGCODE(result = ) codec->getScanlines(row.get(), 1, 0); - SkASSERT(SkCodec::kSuccess == result); + SkDEBUGCODE(lines = ) codec->getScanlines(row.get(), 1, 0); + SkASSERT(1 == lines); memcpy(bitmap.getAddr(0, y), row.get() + subsetStartX * bpp, subsetWidth * bpp); @@ -108,9 +108,9 @@ void SubsetZoomBench::onDraw(int n, SkCanvas* canvas) { SkBitmap stripeBm; alloc_pixels(&stripeBm, stripeInfo, colors, colorCount); - SkDEBUGCODE(result = ) codec->getScanlines(stripeBm.getPixels(), + SkDEBUGCODE(lines = ) codec->getScanlines(stripeBm.getPixels(), subsetHeight, stripeBm.rowBytes()); - SkASSERT(SkCodec::kSuccess == result); + SkASSERT(subsetHeight == lines); for (int y = 0; y < subsetHeight; y++) { memcpy(bitmap.getAddr(0, y), diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index 2ea115bc9a..88371a8e28 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -28,6 +28,8 @@ #include "SkStream.h" #include "SkTLogic.h" #include "SkXMLWriter.h" +#include "SkScaledCodec.h" +#include "SkSwizzler.h" DEFINE_bool(multiPage, false, "For document-type backends, render the source" " into multiple pages"); @@ -340,36 +342,30 @@ Error CodecSrc::draw(SkCanvas* canvas) const { return Error::Nonfatal("Could not start scanline decoder"); } - SkCodec::Result result = SkCodec::kUnimplemented; + void* dst = bitmap.getAddr(0, 0); + size_t rowBytes = bitmap.rowBytes(); + uint32_t height = decodeInfo.height(); switch (codec->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: case SkCodec::kBottomUp_SkScanlineOrder: case SkCodec::kNone_SkScanlineOrder: - result = codec->getScanlines(bitmap.getAddr(0, 0), - decodeInfo.height(), bitmap.rowBytes()); + // 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->nextScanline(); + int dstY = codec->outputScanline(y); void* dstPtr = bitmap.getAddr(0, dstY); - result = codec->getScanlines(dstPtr, 1, bitmap.rowBytes()); - if (SkCodec::kSuccess != result && SkCodec::kIncompleteInput != result) { - return SkStringPrintf("%s failed with error message %d", - fPath.c_str(), (int) result); - } + // 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, bitmap.rowBytes()); } break; } } - switch (result) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - return SkStringPrintf("%s failed with error message %d", - fPath.c_str(), (int) result); - } canvas->drawBitmap(bitmap, 0, 0); break; } @@ -429,41 +425,21 @@ Error CodecSrc::draw(SkCanvas* canvas) const { return "Error scanline decoder is nullptr"; } } - //skip to first line of subset - const SkCodec::Result skipResult = codec->skipScanlines(y); - switch (skipResult) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - return SkStringPrintf("%s failed after attempting to skip %d scanlines" - "with error message %d", fPath.c_str(), y, (int) skipResult); - } + // Skip to the first line of subset. We ignore the result value here. + // If the skip value fails, this will indicate an incomplete image. + // This means that the call to getScanlines() will also fail, but it + // will fill the buffer with a default value, so we can still draw the + // image. + codec->skipScanlines(y); + //create and set size of subsetBm SkBitmap subsetBm; SkIRect bounds = SkIRect::MakeWH(currentSubsetWidth, currentSubsetHeight); SkAssertResult(largestSubsetBm.extractSubset(&subsetBm, bounds)); SkAutoLockPixels autlockSubsetBm(subsetBm, true); - const SkCodec::Result subsetResult = - codec->getScanlines(buffer, currentSubsetHeight, rowBytes); - switch (subsetResult) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - return SkStringPrintf("%s failed with error message %d", - fPath.c_str(), (int) subsetResult); - } + codec->getScanlines(buffer, currentSubsetHeight, rowBytes); + const size_t bpp = decodeInfo.bytesPerPixel(); - /* - * we copy all the lines at once becuase when calling getScanlines for - * interlaced pngs the entire image must be read regardless of the number - * of lines requested. Reading an interlaced png in a loop, line-by-line, would - * decode the entire image height times, which is very slow - * it is aknowledged that copying each line as you read it in a loop - * may be faster for other types of images. Since this is a correctness test - * that's okay. - */ char* bufferRow = buffer; for (int subsetY = 0; subsetY < currentSubsetHeight; ++subsetY) { memcpy(subsetBm.getAddr(0, subsetY), bufferRow + x*bpp, @@ -493,31 +469,17 @@ Error CodecSrc::draw(SkCanvas* canvas) const { // to run this test for image types that do not have this scanline ordering. return Error::Nonfatal("Could not start top-down scanline decoder"); } + for (int i = 0; i < numStripes; i += 2) { // Skip a stripe const int linesToSkip = SkTMin(stripeHeight, height - i * stripeHeight); - SkCodec::Result result = codec->skipScanlines(linesToSkip); - switch (result) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - return SkStringPrintf("Cannot skip scanlines for %s.", fPath.c_str()); - } + codec->skipScanlines(linesToSkip); // Read a stripe const int startY = (i + 1) * stripeHeight; const int linesToRead = SkTMin(stripeHeight, height - startY); if (linesToRead > 0) { - result = codec->getScanlines(bitmap.getAddr(0, startY), - linesToRead, bitmap.rowBytes()); - switch (result) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - return SkStringPrintf("Cannot get scanlines for %s.", fPath.c_str()); - } + codec->getScanlines(bitmap.getAddr(0, startY), linesToRead, bitmap.rowBytes()); } } @@ -531,27 +493,12 @@ Error CodecSrc::draw(SkCanvas* canvas) const { // Read a stripe const int startY = i * stripeHeight; const int linesToRead = SkTMin(stripeHeight, height - startY); - SkCodec::Result result = codec->getScanlines(bitmap.getAddr(0, startY), - linesToRead, bitmap.rowBytes()); - switch (result) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - return SkStringPrintf("Cannot get scanlines for %s.", fPath.c_str()); - } + codec->getScanlines(bitmap.getAddr(0, startY), linesToRead, bitmap.rowBytes()); // Skip a stripe const int linesToSkip = SkTMin(stripeHeight, height - (i + 1) * stripeHeight); if (linesToSkip > 0) { - result = codec->skipScanlines(linesToSkip); - switch (result) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - return SkStringPrintf("Cannot skip scanlines for %s.", fPath.c_str()); - } + codec->skipScanlines(linesToSkip); } } canvas->drawBitmap(bitmap, 0, 0); @@ -592,8 +539,9 @@ Error CodecSrc::draw(SkCanvas* canvas) const { // And scale // FIXME: Should we have a version of getScaledDimensions that takes a subset // into account? - decodeInfo = decodeInfo.makeWH(SkScalarRoundToInt(preScaleW * fScale), - SkScalarRoundToInt(preScaleH * fScale)); + decodeInfo = decodeInfo.makeWH( + SkTMax(1, SkScalarRoundToInt(preScaleW * fScale)), + SkTMax(1, SkScalarRoundToInt(preScaleH * fScale))); size_t rowBytes = decodeInfo.minRowBytes(); if (!subsetBm.installPixels(decodeInfo, pixels, rowBytes, colorTable.get(), nullptr, nullptr)) { diff --git a/gyp/codec.gyp b/gyp/codec.gyp index 205ff917cc..1f3287ab52 100644 --- a/gyp/codec.gyp +++ b/gyp/codec.gyp @@ -46,6 +46,7 @@ '../src/codec/SkJpegUtility_codec.cpp', '../src/codec/SkMaskSwizzler.cpp', '../src/codec/SkMasks.cpp', + '../src/codec/SkSampler.cpp', '../src/codec/SkScaledCodec.cpp', '../src/codec/SkSwizzler.cpp', '../src/codec/SkWebpCodec.cpp', diff --git a/gyp/tools.gyp b/gyp/tools.gyp index 52d7e45273..accdd61681 100644 --- a/gyp/tools.gyp +++ b/gyp/tools.gyp @@ -60,7 +60,8 @@ '../tools/SkBitmapRegionSampler.cpp', ], 'include_dirs': [ - '../include/private' + '../include/private', + '../src/codec', ], 'dependencies': [ 'skia_lib.gyp:skia_lib', diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h index 7300a5c732..1b9cb0f5d7 100644 --- a/include/codec/SkCodec.h +++ b/include/codec/SkCodec.h @@ -131,10 +131,6 @@ public: * supported for this input. */ kCouldNotRewind, - /** - * startScanlineDecode() was not called before calling getScanlines. - */ - kScanlineDecodingNotStarted, /** * This method is not implemented by this codec. * FIXME: Perhaps this should be kUnsupported? @@ -281,8 +277,11 @@ public: * @param countLines Number of lines to write. * @param rowBytes Number of bytes per row. Must be large enough to hold * a scanline based on the SkImageInfo used to create this object. + * @return the number of lines successfully decoded. If this value is + * less than countLines, this will fill the remaining lines with a + * default value. */ - Result getScanlines(void* dst, int countLines, size_t rowBytes); + int getScanlines(void* dst, int countLines, size_t rowBytes); /** * Skip count scanlines. @@ -293,8 +292,15 @@ public: * NOTE: If skipped lines are the only lines with alpha, this default * will make reallyHasAlpha return true, when it could have returned * false. + * + * @return true if the scanlines were successfully skipped + * false on failure, possible reasons for failure include: + * An incomplete input image stream. + * Calling this function before calling startScanlineDecode(). + * If countLines is less than zero or so large that it moves + * the current scanline past the end of the image. */ - Result skipScanlines(int countLines); + bool skipScanlines(int countLines); /** * The order in which rows are output from the scanline decoder is not the @@ -365,15 +371,24 @@ public: /** * Returns the y-coordinate of the next row to be returned by the scanline - * decoder. This will be overridden in the case of - * kOutOfOrder_SkScanlineOrder and should be unnecessary in the case of - * kNone_SkScanlineOrder. + * decoder. + * + * This will equal fCurrScanline, except in the case of strangely + * encoded image types (bottom-up bmps, interlaced gifs). * * Results are undefined when not in scanline decoding mode. */ - int nextScanline() const { - return this->onNextScanline(); - } + int nextScanline() const { return this->outputScanline(fCurrScanline); } + + /** + * Returns the output y-coordinate of the row that corresponds to an input + * y-coordinate. The input y-coordinate represents where the scanline + * is located in the encoded data. + * + * This will equal inputScanline, except in the case of strangely + * encoded image types (bottom-up bmps, interlaced gifs). + */ + int outputScanline(int inputScanline) const; protected: SkCodec(const SkImageInfo&, SkStream*); @@ -394,9 +409,16 @@ protected: virtual SkEncodedFormat onGetEncodedFormat() const = 0; + /** + * @param rowsDecoded When the encoded image stream is incomplete, this function + * will return kIncompleteInput and rowsDecoded will be set to + * the number of scanlines that were successfully decoded. + * This will allow getPixels() to fill the uninitialized memory. + */ virtual Result onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options&, - SkPMColor ctable[], int* ctableCount) = 0; + SkPMColor ctable[], int* ctableCount, + int* rowsDecoded) = 0; virtual bool onGetValidSubset(SkIRect* /* desiredSubset */) const { // By default, subsets are not supported. @@ -426,6 +448,36 @@ protected: return true; } + /** + * On an incomplete input, getPixels() and getScanlines() will fill any uninitialized + * scanlines. This allows the subclass to indicate what value to fill with. + * + * @param colorType Destination color type. + * @param alphaType Destination alpha type. + * @return The value with which to fill uninitialized pixels. + * + * Note that we can interpret the return value as an SkPMColor, a 16-bit 565 color, + * an 8-bit gray color, or an 8-bit index into a color table, depending on the color + * type. + */ + uint32_t getFillValue(SkColorType colorType, SkAlphaType alphaType) const { + return this->onGetFillValue(colorType, alphaType); + } + + /** + * Some subclasses will override this function, but this is a useful default for the color + * types that we support. Note that for color types that do not use the full 32-bits, + * we will simply take the low bits of the fill value. + * + * kN32_SkColorType: Transparent or Black + * kRGB_565_SkColorType: Black + * kGray_8_SkColorType: Black + * kIndex_8_SkColorType: First color in color table + */ + virtual uint32_t onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const { + return kOpaque_SkAlphaType == alphaType ? SK_ColorBLACK : SK_ColorTRANSPARENT; + } + /** * Get method for the input stream */ @@ -442,11 +494,6 @@ protected: */ virtual SkScanlineOrder onGetScanlineOrder() const { return kTopDown_SkScanlineOrder; } - /** - * Most images will be kTopDown and will not need to override this function. - */ - virtual int onNextScanline() const { return fCurrScanline; } - /** * Update the next scanline. Used by interlaced png. */ @@ -456,6 +503,8 @@ protected: const SkCodec::Options& options() const { return fOptions; } + virtual int onOutputScanline(int inputScanline) const; + private: const SkImageInfo fSrcInfo; SkAutoTDelete fStream; @@ -485,20 +534,36 @@ private: } // Naive default version just calls onGetScanlines on temp memory. - virtual SkCodec::Result onSkipScanlines(int countLines) { + virtual bool onSkipScanlines(int countLines) { + // FIXME (msarett): Make this a pure virtual and always override this. SkAutoMalloc storage(fDstInfo.minRowBytes()); + // Note that we pass 0 to rowBytes so we continue to use the same memory. // Also note that while getScanlines checks that rowBytes is big enough, // onGetScanlines bypasses that check. // Calling the virtual method also means we do not double count // countLines. - return this->onGetScanlines(storage.get(), countLines, 0); + return countLines == this->onGetScanlines(storage.get(), countLines, 0); } - virtual SkCodec::Result onGetScanlines(void* dst, int countLines, - size_t rowBytes) { - return kUnimplemented; - } + virtual int onGetScanlines(void* dst, int countLines, size_t rowBytes) { return 0; } + + /** + * On an incomplete decode, getPixels() and getScanlines() will call this function + * to fill any uinitialized memory. + * + * @param dstInfo Contains the destination color type + * Contains the destination alpha type + * Contains the destination width + * The height stored in this info is unused + * @param dst Pointer to the start of destination pixel memory + * @param rowBytes Stride length in destination pixel memory + * @param zeroInit Indicates if memory is zero initialized + * @param linesRequested Number of lines that the client requested + * @param linesDecoded Number of lines that were successfully decoded + */ + void fillIncompleteImage(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, + ZeroInitialized zeroInit, int linesRequested, int linesDecoded); /** * Return an object which will allow forcing scanline decodes to sample in X. @@ -508,9 +573,8 @@ private: * * Only valid during scanline decoding. */ - virtual SkSampler* getSampler() { return nullptr; } + virtual SkSampler* getSampler(bool createIfNecessary) { return nullptr; } - // Needed to call getSampler and dimensionsSupported. friend class SkScaledCodec; }; #endif // SkCodec_DEFINED diff --git a/include/codec/SkScaledCodec.h b/include/codec/SkScaledCodec.h index 61208e316a..028706bc94 100644 --- a/include/codec/SkScaledCodec.h +++ b/include/codec/SkScaledCodec.h @@ -34,7 +34,7 @@ protected: SkISize onGetScaledDimensions(float desiredScale) const override; bool onDimensionsSupported(const SkISize&) override; - Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*) + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*) override; SkEncodedFormat onGetEncodedFormat() const override { return fCodec->getEncodedFormat(); @@ -44,6 +44,10 @@ protected: return fCodec->reallyHasAlpha(); } + uint32_t onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const override; + + SkScanlineOrder onGetScanlineOrder() const override; + private: SkAutoTDelete fCodec; diff --git a/src/codec/SkBmpCodec.cpp b/src/codec/SkBmpCodec.cpp index da3e0a2807..1aa43f583c 100644 --- a/src/codec/SkBmpCodec.cpp +++ b/src/codec/SkBmpCodec.cpp @@ -549,18 +549,6 @@ int32_t SkBmpCodec::getDstRow(int32_t y, int32_t height) const { return height - y - 1; } -/* - * Get the destination row to start filling from - * Used to fill the remainder of the image on incomplete input for bmps - * This is tricky since bmps may be kTopDown or kBottomUp. For kTopDown, - * we start filling from where we left off, but for kBottomUp we start - * filling at the top of the image. - */ -void* SkBmpCodec::getDstStartRow(void* dst, size_t dstRowBytes, int32_t y) const { - return (SkCodec::kTopDown_SkScanlineOrder == fRowOrder) ? - SkTAddOffset(dst, y * dstRowBytes) : dst; -} - /* * Compute the number of colors in the color table */ @@ -588,14 +576,10 @@ SkCodec::Result SkBmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, return prepareToDecode(dstInfo, options, inputColorPtr, inputColorCount); } -SkCodec::Result SkBmpCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { +int SkBmpCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { // Create a new image info representing the portion of the image to decode SkImageInfo rowInfo = this->dstInfo().makeWH(this->dstInfo().width(), count); // Decode the requested rows return this->decodeRows(rowInfo, dst, rowBytes, this->options()); } - -int SkBmpCodec::onNextScanline() const { - return this->getDstRow(this->INHERITED::onNextScanline(), this->dstInfo().height()); -} diff --git a/src/codec/SkBmpCodec.h b/src/codec/SkBmpCodec.h index 19f66037e8..65662ff1f8 100644 --- a/src/codec/SkBmpCodec.h +++ b/src/codec/SkBmpCodec.h @@ -80,15 +80,6 @@ protected: */ int32_t getDstRow(int32_t y, int32_t height) const; - /* - * Get the destination row to start filling from - * Used to fill the remainder of the image on incomplete input for bmps - * This is tricky since bmps may be kTopDown or kBottomUp. For kTopDown, - * we start filling from where we left off, but for kBottomUp we start - * filling at the top of the image. - */ - void* getDstStartRow(void* dst, size_t dstRowBytes, int32_t y) const; - /* * Compute the number of colors in the color table */ @@ -140,16 +131,15 @@ private: * number of rows to decode at this time. * @param dst Memory location to store output pixels * @param dstRowBytes Bytes in a row of the destination + * @return Number of rows successfully decoded */ - virtual Result decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + virtual int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts) = 0; Result onStartScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options&, SkPMColor inputColorPtr[], int* inputColorCount) override; - Result onGetScanlines(void* dst, int count, size_t rowBytes) override; - - int onNextScanline() const override; + int onGetScanlines(void* dst, int count, size_t rowBytes) override; // TODO(msarett): Override default skipping with something more clever. diff --git a/src/codec/SkBmpMaskCodec.cpp b/src/codec/SkBmpMaskCodec.cpp index 81067c7711..336698d319 100644 --- a/src/codec/SkBmpMaskCodec.cpp +++ b/src/codec/SkBmpMaskCodec.cpp @@ -29,7 +29,8 @@ SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, - int* inputColorCount) { + int* inputColorCount, + int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; @@ -49,7 +50,12 @@ SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo, return result; } - return this->decodeRows(dstInfo, dst, dstRowBytes, opts); + uint32_t rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); + if (rows != dstInfo.height()) { + *rowsDecoded = rows; + return kIncompleteInput; + } + return kSuccess; } bool SkBmpMaskCodec::initializeSwizzler(const SkImageInfo& dstInfo) { @@ -78,7 +84,7 @@ SkCodec::Result SkBmpMaskCodec::prepareToDecode(const SkImageInfo& dstInfo, /* * Performs the decoding */ -SkCodec::Result SkBmpMaskCodec::decodeRows(const SkImageInfo& dstInfo, +int SkBmpMaskCodec::decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts) { // Iterate over rows of the image @@ -88,12 +94,7 @@ SkCodec::Result SkBmpMaskCodec::decodeRows(const SkImageInfo& dstInfo, // Read a row of the input if (this->stream()->read(srcRow, fSrcRowBytes) != fSrcRowBytes) { SkCodecPrintf("Warning: incomplete input stream.\n"); - // Fill the destination image on failure - void* dstStart = this->getDstStartRow(dst, dstRowBytes, y); - uint32_t fillColor = get_fill_color_or_index(dstInfo.alphaType()); - SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, height - y, - fillColor, nullptr, opts.fZeroInitialized); - return kIncompleteInput; + return y; } // Decode the row in destination format @@ -103,5 +104,5 @@ SkCodec::Result SkBmpMaskCodec::decodeRows(const SkImageInfo& dstInfo, } // Finished decoding the entire image - return kSuccess; + return height; } diff --git a/src/codec/SkBmpMaskCodec.h b/src/codec/SkBmpMaskCodec.h index 1c44c7fb40..1c1d1d8c11 100644 --- a/src/codec/SkBmpMaskCodec.h +++ b/src/codec/SkBmpMaskCodec.h @@ -36,7 +36,7 @@ protected: Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, SkPMColor*, - int*) override; + int*, int*) override; SkCodec::Result prepareToDecode(const SkImageInfo& dstInfo, const SkCodec::Options& options, SkPMColor inputColorPtr[], @@ -45,10 +45,13 @@ protected: private: bool initializeSwizzler(const SkImageInfo& dstInfo); - SkSampler* getSampler() override { return fMaskSwizzler; } + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fMaskSwizzler); + return fMaskSwizzler; + } - Result decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, - const Options& opts) override; + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; SkAutoTDelete fMasks; // owned SkAutoTDelete fMaskSwizzler; diff --git a/src/codec/SkBmpRLECodec.cpp b/src/codec/SkBmpRLECodec.cpp index fe499c6a6d..37af47600b 100644 --- a/src/codec/SkBmpRLECodec.cpp +++ b/src/codec/SkBmpRLECodec.cpp @@ -38,7 +38,8 @@ SkCodec::Result SkBmpRLECodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, - int* inputColorCount) { + int* inputColorCount, + int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; @@ -54,7 +55,16 @@ SkCodec::Result SkBmpRLECodec::onGetPixels(const SkImageInfo& dstInfo, } // Perform the decode - return this->decodeRows(dstInfo, dst, dstRowBytes, opts); + uint32_t rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); + if (rows != dstInfo.height()) { + // We set rowsDecoded equal to the height because the background has already + // been filled. RLE encodings sometimes skip pixels, so we always start by + // filling the background. + *rowsDecoded = dstInfo.height(); + return kIncompleteInput; + } + + return kSuccess; } /* @@ -275,9 +285,8 @@ SkCodec::Result SkBmpRLECodec::prepareToDecode(const SkImageInfo& dstInfo, * Performs the bitmap decoding for RLE input format * RLE decoding is performed all at once, rather than a one row at a time */ -SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, - void* dst, size_t dstRowBytes, - const Options& opts) { +int SkBmpRLECodec::decodeRows(const SkImageInfo& info, void* dst, size_t dstRowBytes, + const Options& opts) { // Set RLE flags static const uint8_t RLE_ESCAPE = 0; static const uint8_t RLE_EOL = 0; @@ -300,24 +309,23 @@ SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, // Because of the need for transparent pixels, kN32 is the only color // type that makes sense for the destination format. SkASSERT(kN32_SkColorType == dstInfo.colorType()); - SkSwizzler::Fill(dst, dstInfo, dstRowBytes, height, SK_ColorTRANSPARENT, - NULL, opts.fZeroInitialized); + SkSampler::Fill(dstInfo, dst, dstRowBytes, SK_ColorTRANSPARENT, opts.fZeroInitialized); while (true) { // If we have reached a row that is beyond the requested height, we have // succeeded. if (y >= height) { - // It would be better to check for the EOF marker before returning + // It would be better to check for the EOF marker before indicating // success, but we may be performing a scanline decode, which - // may require us to stop before decoding the full height. - return kSuccess; + // would require us to stop before decoding the full height. + return height; } // Every entry takes at least two bytes if ((int) fRLEBytes - fCurrRLEByte < 2) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < 2) { - return kIncompleteInput; + return y; } } @@ -342,7 +350,7 @@ SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, if ((int) fRLEBytes - fCurrRLEByte < 2) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < 2) { - return kIncompleteInput; + return y; } } // Modify x and y @@ -352,7 +360,7 @@ SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, y += dy; if (x > width || y > height) { SkCodecPrintf("Warning: invalid RLE input.\n"); - return kInvalidInput; + return y - dy; } break; } @@ -368,14 +376,14 @@ SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, // image. if (x + numPixels > width) { SkCodecPrintf("Warning: invalid RLE input.\n"); - return kInvalidInput; + return y; } // Also abort if there are not enough bytes // remaining in the stream to set numPixels. if ((int) fRLEBytes - fCurrRLEByte < SkAlign2(rowBytes)) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < SkAlign2(rowBytes)) { - return kIncompleteInput; + return y; } } // Set numPixels number of pixels @@ -411,7 +419,7 @@ SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, } default: SkASSERT(false); - return kInvalidInput; + return y; } } // Skip a byte if necessary to maintain alignment @@ -434,7 +442,7 @@ SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, if ((int) fRLEBytes - fCurrRLEByte < 2) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < 2) { - return kIncompleteInput; + return y; } } @@ -467,6 +475,9 @@ SkCodec::Result SkBmpRLECodec::decodeRows(const SkImageInfo& info, } } +// FIXME: Make SkBmpRLECodec have no knowledge of sampling. +// Or it should do all sampling natively. +// It currently is a hybrid that needs to know what SkScaledCodec is doing. class SkBmpRLESampler : public SkSampler { public: SkBmpRLESampler(SkBmpRLECodec* codec) @@ -476,7 +487,7 @@ public: } private: - int onSetSampleX(int sampleX) { + int onSetSampleX(int sampleX) override { return fCodec->setSampleX(sampleX); } @@ -484,15 +495,15 @@ private: SkBmpRLECodec* fCodec; }; -SkSampler* SkBmpRLECodec::getSampler() { - if (!fSampler) { +SkSampler* SkBmpRLECodec::getSampler(bool createIfNecessary) { + if (!fSampler && createIfNecessary) { fSampler.reset(new SkBmpRLESampler(this)); } return fSampler; } -int SkBmpRLECodec::setSampleX(int sampleX) { +int SkBmpRLECodec::setSampleX(int sampleX){ fSampleX = sampleX; return get_scaled_dimension(this->getInfo().width(), sampleX); } diff --git a/src/codec/SkBmpRLECodec.h b/src/codec/SkBmpRLECodec.h index 8721969a85..fcf910d8e5 100644 --- a/src/codec/SkBmpRLECodec.h +++ b/src/codec/SkBmpRLECodec.h @@ -46,7 +46,7 @@ protected: Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, SkPMColor*, - int*) override; + int*, int*) override; SkCodec::Result prepareToDecode(const SkImageInfo& dstInfo, const SkCodec::Options& options, SkPMColor inputColorPtr[], @@ -84,10 +84,10 @@ private: const SkImageInfo& dstInfo, uint32_t x, uint32_t y, uint8_t red, uint8_t green, uint8_t blue); - Result decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, - const Options& opts) override; + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; - SkSampler* getSampler() override; + SkSampler* getSampler(bool createIfNecessary) override; SkAutoTUnref fColorTable; // owned const uint32_t fNumColors; diff --git a/src/codec/SkBmpStandardCodec.cpp b/src/codec/SkBmpStandardCodec.cpp index 540b9f3b2b..938fe8c788 100644 --- a/src/codec/SkBmpStandardCodec.cpp +++ b/src/codec/SkBmpStandardCodec.cpp @@ -36,7 +36,8 @@ SkCodec::Result SkBmpStandardCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, - int* inputColorCount) { + int* inputColorCount, + int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; @@ -54,9 +55,10 @@ SkCodec::Result SkBmpStandardCodec::onGetPixels(const SkImageInfo& dstInfo, if (kSuccess != result) { return result; } - result = this->decodeRows(dstInfo, dst, dstRowBytes, opts); - if (kSuccess != result) { - return result; + uint32_t rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); + if (rows != dstInfo.height()) { + *rowsDecoded = rows; + return kIncompleteInput; } if (fInIco) { return this->decodeIcoMask(dstInfo, dst, dstRowBytes); @@ -227,7 +229,7 @@ SkCodec::Result SkBmpStandardCodec::prepareToDecode(const SkImageInfo& dstInfo, /* * Performs the bitmap decoding for standard input format */ -SkCodec::Result SkBmpStandardCodec::decodeRows(const SkImageInfo& dstInfo, +int SkBmpStandardCodec::decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts) { // Iterate over rows of the image @@ -236,13 +238,7 @@ SkCodec::Result SkBmpStandardCodec::decodeRows(const SkImageInfo& dstInfo, // Read a row of the input if (this->stream()->read(fSrcBuffer.get(), fSrcRowBytes) != fSrcRowBytes) { SkCodecPrintf("Warning: incomplete input stream.\n"); - // Fill the destination image on failure - void* dstStart = this->getDstStartRow(dst, dstRowBytes, y); - const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); - uint32_t fillColorOrIndex = get_fill_color_or_index(dstInfo.alphaType()); - SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, dstInfo.height() - y, - fillColorOrIndex, colorPtr, opts.fZeroInitialized); - return kIncompleteInput; + return y; } // Decode the row in destination format @@ -253,7 +249,7 @@ SkCodec::Result SkBmpStandardCodec::decodeRows(const SkImageInfo& dstInfo, } // Finished decoding the entire image - return kSuccess; + return height; } // TODO (msarett): This function will need to be modified in order to perform row by row decodes @@ -294,3 +290,11 @@ SkCodec::Result SkBmpStandardCodec::decodeIcoMask(const SkImageInfo& dstInfo, } return kSuccess; } + +uint32_t SkBmpStandardCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const { + const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); + if (colorPtr) { + return get_color_table_fill_value(colorType, colorPtr, 0); + } + return INHERITED::onGetFillValue(colorType, alphaType); +} diff --git a/src/codec/SkBmpStandardCodec.h b/src/codec/SkBmpStandardCodec.h index 0263570b85..d687eaad28 100644 --- a/src/codec/SkBmpStandardCodec.h +++ b/src/codec/SkBmpStandardCodec.h @@ -44,7 +44,7 @@ protected: Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, SkPMColor*, - int*) override; + int*, int*) override; bool onInIco() const override { return fInIco; @@ -54,7 +54,13 @@ protected: const SkCodec::Options& options, SkPMColor inputColorPtr[], int* inputColorCount) override; - SkSampler* getSampler() override { return fSwizzler; } + + uint32_t onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const override; + + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler); + return fSwizzler; + } private: @@ -66,8 +72,8 @@ private: bool initializeSwizzler(const SkImageInfo& dstInfo, const Options& opts); - Result decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, - const Options& opts) override; + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; Result decodeIcoMask(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes); diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp index 1a901a97ac..0047d599ac 100644 --- a/src/codec/SkCodec.cpp +++ b/src/codec/SkCodec.cpp @@ -167,11 +167,26 @@ SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t return kInvalidScale; } - const Result result = this->onGetPixels(info, pixels, rowBytes, *options, ctable, ctableCount); + // On an incomplete decode, the subclass will specify the number of scanlines that it decoded + // successfully. + int rowsDecoded = 0; + const Result result = this->onGetPixels(info, pixels, rowBytes, *options, ctable, ctableCount, + &rowsDecoded); if ((kIncompleteInput == result || kSuccess == result) && ctableCount) { SkASSERT(*ctableCount >= 0 && *ctableCount <= 256); } + + // A return value of kIncompleteInput indicates a truncated image stream. + // In this case, we will fill any uninitialized memory with a default value. + // Some subclasses will take care of filling any uninitialized memory on + // their own. They indicate that all of the memory has been filled by + // setting rowsDecoded equal to the height. + if (kIncompleteInput == result && rowsDecoded != info.height()) { + this->fillIncompleteImage(info, pixels, rowBytes, options->fZeroInitialized, info.height(), + rowsDecoded); + } + return result; } @@ -233,36 +248,101 @@ SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo) { return this->startScanlineDecode(dstInfo, nullptr, nullptr, nullptr); } -SkCodec::Result SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) { +int SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) { if (fCurrScanline < 0) { - return kScanlineDecodingNotStarted; + return 0; } SkASSERT(!fDstInfo.isEmpty()); - if (countLines <= 0 || fCurrScanline + countLines > fDstInfo.height()) { - return kInvalidParameters; + return 0; } - const Result result = this->onGetScanlines(dst, countLines, rowBytes); + const int linesDecoded = this->onGetScanlines(dst, countLines, rowBytes); + if (linesDecoded < countLines) { + this->fillIncompleteImage(this->dstInfo(), dst, rowBytes, this->options().fZeroInitialized, + countLines, linesDecoded); + } fCurrScanline += countLines; - return result; + return linesDecoded; } -SkCodec::Result SkCodec::skipScanlines(int countLines) { +bool SkCodec::skipScanlines(int countLines) { if (fCurrScanline < 0) { - return kScanlineDecodingNotStarted; + return false; } SkASSERT(!fDstInfo.isEmpty()); - if (fCurrScanline + countLines > fDstInfo.height()) { + if (countLines < 0 || fCurrScanline + countLines > fDstInfo.height()) { // Arguably, we could just skip the scanlines which are remaining, - // and return kSuccess. We choose to return invalid so the client + // and return true. We choose to return false so the client // can catch their bug. - return SkCodec::kInvalidParameters; + return false; } - const Result result = this->onSkipScanlines(countLines); + bool result = this->onSkipScanlines(countLines); fCurrScanline += countLines; return result; } + +int SkCodec::outputScanline(int inputScanline) const { + SkASSERT(0 <= inputScanline && inputScanline < this->getInfo().height()); + return this->onOutputScanline(inputScanline); +} + +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; + default: + // This case indicates an interlaced gif and is implemented by SkGifCodec. + SkASSERT(false); + return 0; + } +} + +static void fill_proc(const SkImageInfo& info, void* dst, size_t rowBytes, + uint32_t colorOrIndex, SkCodec::ZeroInitialized zeroInit, SkSampler* sampler) { + if (sampler) { + sampler->fill(info, dst, rowBytes, colorOrIndex, zeroInit); + } else { + SkSampler::Fill(info, dst, rowBytes, colorOrIndex, zeroInit); + } +} + +void SkCodec::fillIncompleteImage(const SkImageInfo& info, void* dst, size_t rowBytes, + ZeroInitialized zeroInit, int linesRequested, int linesDecoded) { + + void* fillDst; + const uint32_t fillValue = this->getFillValue(info.colorType(), info.alphaType()); + const int linesRemaining = linesRequested - linesDecoded; + SkSampler* sampler = this->getSampler(false); + + switch (this->getScanlineOrder()) { + case kTopDown_SkScanlineOrder: + case kNone_SkScanlineOrder: { + const SkImageInfo fillInfo = info.makeWH(info.width(), linesRemaining); + fillDst = SkTAddOffset(dst, linesDecoded * rowBytes); + fill_proc(fillInfo, fillDst, rowBytes, fillValue, zeroInit, sampler); + break; + } + case kBottomUp_SkScanlineOrder: { + fillDst = dst; + const SkImageInfo fillInfo = info.makeWH(info.width(), linesRemaining); + fill_proc(fillInfo, fillDst, rowBytes, fillValue, zeroInit, sampler); + break; + } + case kOutOfOrder_SkScanlineOrder: { + SkASSERT(1 == linesRequested || this->getInfo().height() == linesRequested); + const SkImageInfo fillInfo = info.makeWH(info.width(), 1); + for (int srcY = linesDecoded; srcY < linesRequested; srcY++) { + fillDst = SkTAddOffset(dst, this->outputScanline(srcY) * rowBytes); + fill_proc(fillInfo, fillDst, rowBytes, fillValue, zeroInit, sampler); + } + break; + } + } +} diff --git a/src/codec/SkCodecPriv.h b/src/codec/SkCodecPriv.h index 2769cec1cd..0442625dd2 100644 --- a/src/codec/SkCodecPriv.h +++ b/src/codec/SkCodecPriv.h @@ -8,6 +8,7 @@ #ifndef SkCodecPriv_DEFINED #define SkCodecPriv_DEFINED +#include "SkColorPriv.h" #include "SkColorTable.h" #include "SkImageInfo.h" #include "SkSwizzler.h" @@ -34,7 +35,7 @@ * returns a scaled dimension based on the original dimension and the sampleSize * NOTE: we round down here for scaled dimension to match the behavior of SkImageDecoder */ -static int get_scaled_dimension(int srcDimension, int sampleSize) { +inline int get_scaled_dimension(int srcDimension, int sampleSize) { if (sampleSize > srcDimension) { return 1; } @@ -47,7 +48,7 @@ static int get_scaled_dimension(int srcDimension, int sampleSize) { * * This does not need to be called and is not called when sampleFactor == 1. */ -static int get_start_coord(int sampleFactor) { return sampleFactor / 2; }; +inline int get_start_coord(int sampleFactor) { return sampleFactor / 2; }; /* * Given a coordinate in the original image, this returns the corresponding @@ -57,7 +58,7 @@ static int get_start_coord(int sampleFactor) { return sampleFactor / 2; }; * * This does not need to be called and is not called when sampleFactor == 1. */ -static int get_dst_coord(int srcCoord, int sampleFactor) { return srcCoord / sampleFactor; }; +inline int get_dst_coord(int srcCoord, int sampleFactor) { return srcCoord / sampleFactor; }; /* * When scaling, we will discard certain y-coordinates (rows) and @@ -67,7 +68,7 @@ static int get_dst_coord(int srcCoord, int sampleFactor) { return srcCoord / sam * * This does not need to be called and is not called when sampleFactor == 1. */ -static bool is_coord_necessary(int srcCoord, int sampleFactor, int scaledDim) { +inline bool is_coord_necessary(int srcCoord, int sampleFactor, int scaledDim) { // Get the first coordinate that we want to keep int startCoord = get_start_coord(sampleFactor); @@ -80,7 +81,7 @@ static bool is_coord_necessary(int srcCoord, int sampleFactor, int scaledDim) { return ((srcCoord - startCoord) % sampleFactor) == 0; } -static inline bool valid_alpha(SkAlphaType dstAlpha, SkAlphaType srcAlpha) { +inline bool valid_alpha(SkAlphaType dstAlpha, SkAlphaType srcAlpha) { // Check for supported alpha types if (srcAlpha != dstAlpha) { if (kOpaque_SkAlphaType == srcAlpha) { @@ -110,7 +111,7 @@ static inline bool valid_alpha(SkAlphaType dstAlpha, SkAlphaType srcAlpha) { * - always support N32 * - otherwise match the src color type */ -static bool conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) { +inline bool conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) { if (dst.profileType() != src.profileType()) { return false; } @@ -134,15 +135,34 @@ static bool conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) /* * If there is a color table, get a pointer to the colors, otherwise return nullptr */ -static const SkPMColor* get_color_ptr(SkColorTable* colorTable) { +inline const SkPMColor* get_color_ptr(SkColorTable* colorTable) { return nullptr != colorTable ? colorTable->readColors() : nullptr; } +/* + * Given that the encoded image uses a color table, return the fill value + */ +inline uint32_t get_color_table_fill_value(SkColorType colorType, const SkPMColor* colorPtr, + uint8_t fillIndex) { + SkASSERT(nullptr != colorPtr); + switch (colorType) { + case kN32_SkColorType: + return colorPtr[fillIndex]; + case kRGB_565_SkColorType: + return SkPixel32ToPixel16(colorPtr[fillIndex]); + case kIndex_8_SkColorType: + return fillIndex; + default: + SkASSERT(false); + return 0; + } +} + /* * * Copy the codec color table back to the client when kIndex8 color type is requested */ -static inline void copy_color_table(const SkImageInfo& dstInfo, SkColorTable* colorTable, +inline void copy_color_table(const SkImageInfo& dstInfo, SkColorTable* colorTable, SkPMColor* inputColorPtr, int* inputColorCount) { if (kIndex_8_SkColorType == dstInfo.colorType()) { SkASSERT(nullptr != inputColorPtr); @@ -155,21 +175,21 @@ static inline void copy_color_table(const SkImageInfo& dstInfo, SkColorTable* co /* * Compute row bytes for an image using pixels per byte */ -static inline size_t compute_row_bytes_ppb(int width, uint32_t pixelsPerByte) { +inline size_t compute_row_bytes_ppb(int width, uint32_t pixelsPerByte) { return (width + pixelsPerByte - 1) / pixelsPerByte; } /* * Compute row bytes for an image using bytes per pixel */ -static inline size_t compute_row_bytes_bpp(int width, uint32_t bytesPerPixel) { +inline size_t compute_row_bytes_bpp(int width, uint32_t bytesPerPixel) { return width * bytesPerPixel; } /* * Compute row bytes for an image */ -static inline size_t compute_row_bytes(int width, uint32_t bitsPerPixel) { +inline size_t compute_row_bytes(int width, uint32_t bitsPerPixel) { if (bitsPerPixel < 16) { SkASSERT(0 == 8 % bitsPerPixel); const uint32_t pixelsPerByte = 8 / bitsPerPixel; @@ -181,28 +201,11 @@ static inline size_t compute_row_bytes(int width, uint32_t bitsPerPixel) { } } -/* - * On incomplete images, get the color to fill with - */ -static inline SkPMColor get_fill_color_or_index(SkAlphaType alphaType) { - // This condition works properly for all supported output color types. - // kIndex8: The low 8-bits of both possible return values is 0, which is - // our desired default index. - // kGray8: The low 8-bits of both possible return values is 0, which is - // black, our desired fill value. - // kRGB565: The low 16-bits of both possible return values is 0, which is - // black, our desired fill value. - // kN32: Return black for opaque images and transparent for non-opaque - // images. - return kOpaque_SkAlphaType == alphaType ? - SK_ColorBLACK : SK_ColorTRANSPARENT; -} - /* * Get a byte from a buffer * This method is unsafe, the caller is responsible for performing a check */ -static inline uint8_t get_byte(uint8_t* buffer, uint32_t i) { +inline uint8_t get_byte(uint8_t* buffer, uint32_t i) { return buffer[i]; } @@ -210,7 +213,7 @@ static inline uint8_t get_byte(uint8_t* buffer, uint32_t i) { * Get a short from a buffer * This method is unsafe, the caller is responsible for performing a check */ -static inline uint16_t get_short(uint8_t* buffer, uint32_t i) { +inline uint16_t get_short(uint8_t* buffer, uint32_t i) { uint16_t result; memcpy(&result, &(buffer[i]), 2); #ifdef SK_CPU_BENDIAN @@ -224,7 +227,7 @@ static inline uint16_t get_short(uint8_t* buffer, uint32_t i) { * Get an int from a buffer * This method is unsafe, the caller is responsible for performing a check */ -static inline uint32_t get_int(uint8_t* buffer, uint32_t i) { +inline uint32_t get_int(uint8_t* buffer, uint32_t i) { uint32_t result; memcpy(&result, &(buffer[i]), 4); #ifdef SK_CPU_BENDIAN diff --git a/src/codec/SkCodec_libgif.cpp b/src/codec/SkCodec_libgif.cpp index 7b380473fc..856f69ba3c 100644 --- a/src/codec/SkCodec_libgif.cpp +++ b/src/codec/SkCodec_libgif.cpp @@ -95,14 +95,14 @@ static uint32_t find_trans_index(const SavedImage& image) { return SK_MaxU32; } -static inline uint32_t ceil_div(uint32_t a, uint32_t b) { +inline uint32_t ceil_div(uint32_t a, uint32_t b) { return (a + b - 1) / b; } /* * Gets the output row corresponding to the encoded row for interlaced gifs */ -static uint32_t get_output_row_interlaced(uint32_t encodedRow, uint32_t height) { +inline uint32_t get_output_row_interlaced(uint32_t encodedRow, uint32_t height) { SkASSERT(encodedRow < height); // First pass if (encodedRow * 8 < height) { @@ -460,11 +460,8 @@ SkCodec::Result SkGifCodec::initializeSwizzler(const SkImageInfo& dstInfo, return kUnimplemented; } -SkCodec::Result SkGifCodec::readRow() { - if (GIF_ERROR == DGifGetLine(fGif, fSrcBuffer.get(), fFrameRect.width())) { - return kIncompleteInput; - } - return kSuccess; +bool SkGifCodec::readRow() { + return GIF_ERROR != DGifGetLine(fGif, fSrcBuffer.get(), fFrameRect.width()); } /* @@ -474,7 +471,8 @@ SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, - int* inputColorCount) { + int* inputColorCount, + int* rowsDecoded) { Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, opts); if (kSuccess != result) { return result; @@ -492,9 +490,9 @@ SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, } // Fill the background - const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); - SkSwizzler::Fill(dst, dstInfo, dstRowBytes, this->getInfo().height(), - fFillIndex, colorPtr, opts.fZeroInitialized); + SkSampler::Fill(dstInfo, dst, dstRowBytes, + this->getFillValue(dstInfo.colorType(), dstInfo.alphaType()), + opts.fZeroInitialized); // Modify the dst pointer const int32_t dstBytesPerPixel = SkColorTypeBytesPerPixel(dstInfo.colorType()); @@ -506,49 +504,30 @@ SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, } } - // Check the interlace flag and iterate over rows of the input - uint32_t width = fFrameRect.width(); + // Iterate over rows of the input uint32_t height = fFrameRect.height(); - if (fGif->Image.Interlace) { - // In interlace mode, the rows of input are rearranged in - // the output image. We a helper function to help us - // rearrange the decoded rows. - for (uint32_t y = 0; y < height; y++) { - if (kSuccess != this->readRow()) { - // Recover from error by filling remainder of image - memset(fSrcBuffer.get(), fFillIndex, width); - for (; y < height; y++) { - void* dstRow = SkTAddOffset(dst, - dstRowBytes * get_output_row_interlaced(y, height)); - fSwizzler->swizzle(dstRow, fSrcBuffer.get()); - } - return gif_error("Could not decode line.\n", kIncompleteInput); - } - void* dstRow = SkTAddOffset(dst, - dstRowBytes * get_output_row_interlaced(y, height)); - fSwizzler->swizzle(dstRow, fSrcBuffer.get()); - } - } else { - // Standard mode - void* dstRow = dst; - for (uint32_t y = 0; y < height; y++) { - if (kSuccess != this->readRow()) { - const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); - SkSwizzler::Fill(dstRow, dstInfo, dstRowBytes, - height - y, fFillIndex, colorPtr, opts.fZeroInitialized); - return gif_error("Could not decode line\n", kIncompleteInput); - } - fSwizzler->swizzle(dstRow, fSrcBuffer.get()); - dstRow = SkTAddOffset(dstRow, dstRowBytes); + for (uint32_t y = 0; y < height; y++) { + if (!this->readRow()) { + *rowsDecoded = y; + return gif_error("Could not decode line.\n", kIncompleteInput); } + void* dstRow = SkTAddOffset(dst, dstRowBytes * this->outputScanline(y)); + fSwizzler->swizzle(dstRow, fSrcBuffer.get()); } return kSuccess; } +// FIXME: This is similar to the implementation for bmp and png. Can we share more code or +// possibly make this non-virtual? +uint32_t SkGifCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const { + const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); + return get_color_table_fill_value(colorType, colorPtr, fFillIndex); +} + SkCodec::Result SkGifCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options& opts, SkPMColor inputColorPtr[], int* inputColorCount) { - Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, - this->options()); + + Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, this->options()); if (kSuccess != result) { return result; } @@ -568,58 +547,56 @@ SkCodec::Result SkGifCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, return kSuccess; } -SkCodec::Result SkGifCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { +int SkGifCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { + int rowsBeforeFrame = 0; + int rowsAfterFrame = 0; + int rowsInFrame = count; if (fFrameIsSubset) { // Fill the requested rows - const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); - SkSwizzler::Fill(dst, this->dstInfo(), rowBytes, count, fFillIndex, - colorPtr, this->options().fZeroInitialized); + SkImageInfo fillInfo = this->dstInfo().makeWH(this->dstInfo().width(), count); + uint32_t fillValue = this->onGetFillValue(this->dstInfo().colorType(), + this->dstInfo().alphaType()); + SkSampler::Fill(fillInfo, dst, rowBytes, fillValue, this->options().fZeroInitialized); // Do nothing for rows before the image frame - int rowsBeforeFrame = SkTMax(0, fFrameRect.top() - this->INHERITED::onNextScanline()); - count = SkTMax(0, count - rowsBeforeFrame); + rowsBeforeFrame = SkTMax(0, fFrameRect.top() - this->INHERITED::nextScanline()); + rowsInFrame = SkTMax(0, rowsInFrame - rowsBeforeFrame); dst = SkTAddOffset(dst, rowBytes * rowsBeforeFrame); // Do nothing for rows after the image frame - int rowsAfterFrame = SkTMax(0, - this->INHERITED::onNextScanline() + count - fFrameRect.bottom()); - count = SkTMax(0, count - rowsAfterFrame); + rowsAfterFrame = SkTMax(0, + this->INHERITED::nextScanline() + rowsInFrame - fFrameRect.bottom()); + rowsInFrame = SkTMax(0, rowsInFrame - rowsAfterFrame); // Adjust dst pointer for left offset int offset = SkColorTypeBytesPerPixel(this->dstInfo().colorType()) * fFrameRect.left(); dst = SkTAddOffset(dst, offset); } - for (int i = 0; i < count; i++) { - if (kSuccess != this->readRow()) { - const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); - SkSwizzler::Fill(dst, this->dstInfo().makeWH(fFrameRect.width(), - this->dstInfo().height()), rowBytes, count - i, fFillIndex, colorPtr, - this->options().fZeroInitialized); - return kIncompleteInput; + for (int i = 0; i < rowsInFrame; i++) { + if (!this->readRow()) { + return i + rowsBeforeFrame; } fSwizzler->swizzle(dst, fSrcBuffer.get()); dst = SkTAddOffset(dst, rowBytes); } - return kSuccess; + + return count; } SkCodec::SkScanlineOrder SkGifCodec::onGetScanlineOrder() const { if (fGif->Image.Interlace) { return kOutOfOrder_SkScanlineOrder; - } else { - return kTopDown_SkScanlineOrder; } + return kTopDown_SkScanlineOrder; } -int SkGifCodec::onNextScanline() const { - int nextScanline = this->INHERITED::onNextScanline(); +int SkGifCodec::onOutputScanline(int inputScanline) const { if (fGif->Image.Interlace) { - if (nextScanline < fFrameRect.top() || nextScanline >= fFrameRect.bottom()) { - return nextScanline; + if (inputScanline < fFrameRect.top() || inputScanline >= fFrameRect.bottom()) { + return inputScanline; } - return get_output_row_interlaced(nextScanline - fFrameRect.top(), fFrameRect.height()); + return get_output_row_interlaced(inputScanline - fFrameRect.top(), fFrameRect.height()); } - return nextScanline; + return inputScanline; } - diff --git a/src/codec/SkCodec_libgif.h b/src/codec/SkCodec_libgif.h index f777e58094..10fdac97f9 100644 --- a/src/codec/SkCodec_libgif.h +++ b/src/codec/SkCodec_libgif.h @@ -60,7 +60,7 @@ protected: * Performs the full gif decode */ Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, - SkPMColor*, int32_t*) override; + SkPMColor*, int*, int*) override; SkEncodedFormat onGetEncodedFormat() const override { return kGIF_SkEncodedFormat; @@ -68,6 +68,10 @@ protected: bool onRewind() override; + uint32_t onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const override; + + int onOutputScanline(int inputScanline) const; + private: /* @@ -129,23 +133,23 @@ private: */ Result initializeSwizzler(const SkImageInfo& dstInfo, ZeroInitialized zeroInit); - SkSampler* getSampler() override { return fSwizzler; } + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler); + return fSwizzler; + } /* - * @return kSuccess if the read is successful and kIncompleteInput if the - * read fails. + * @return true if the read is successful and false if the read fails. */ - Result readRow(); + bool readRow(); Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& opts, SkPMColor inputColorPtr[], int* inputColorCount) override; - Result onGetScanlines(void* dst, int count, size_t rowBytes) override; + int onGetScanlines(void* dst, int count, size_t rowBytes) override; SkScanlineOrder onGetScanlineOrder() const override; - int onNextScanline() const override; - /* * This function cleans up the gif object after the decode completes * It is used in a SkAutoTCallIProc template diff --git a/src/codec/SkCodec_libico.cpp b/src/codec/SkCodec_libico.cpp index 62562a19d5..8c5a1b3412 100644 --- a/src/codec/SkCodec_libico.cpp +++ b/src/codec/SkCodec_libico.cpp @@ -113,7 +113,7 @@ SkCodec* SkIcoCodec::NewFromStream(SkStream* stream) { for (uint32_t i = 0; i < numImages; i++) { uint32_t offset = directoryEntries.get()[i].offset; uint32_t size = directoryEntries.get()[i].size; - + // Ensure that the offset is valid if (offset < bytesRead) { SkCodecPrintf("Warning: invalid ico offset.\n"); @@ -242,8 +242,8 @@ bool SkIcoCodec::onDimensionsSupported(const SkISize& dim) { */ SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, - const Options& opts, SkPMColor* ct, - int* ptr) { + const Options& opts, SkPMColor* colorTable, + int* colorCount, int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; @@ -279,7 +279,11 @@ SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, break; } SkImageInfo info = dstInfo.makeAlphaType(embeddedAlpha); - result = embeddedCodec->getPixels(info, dst, dstRowBytes, &opts, ct, ptr); + result = embeddedCodec->getPixels(info, dst, dstRowBytes, &opts, colorTable, + colorCount); + // The embedded codec will handle filling incomplete images, so we will indicate + // that all of the rows are initialized. + *rowsDecoded = info.height(); // On a fatal error, keep trying to find an image to decode if (kInvalidConversion == result || kInvalidInput == result || diff --git a/src/codec/SkCodec_libico.h b/src/codec/SkCodec_libico.h index a815e300dd..92675f4d74 100644 --- a/src/codec/SkCodec_libico.h +++ b/src/codec/SkCodec_libico.h @@ -40,9 +40,8 @@ protected: /* * Initiates the Ico decode */ - Result onGetPixels(const SkImageInfo& dstInfo, void* dst, - size_t dstRowBytes, const Options&, SkPMColor*, int*) - override; + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, + SkPMColor*, int*, int*) override; SkEncodedFormat onGetEncodedFormat() const override { return kICO_SkEncodedFormat; diff --git a/src/codec/SkCodec_libpng.cpp b/src/codec/SkCodec_libpng.cpp index ee8d49263f..e828e24999 100644 --- a/src/codec/SkCodec_libpng.cpp +++ b/src/codec/SkCodec_libpng.cpp @@ -466,7 +466,8 @@ bool SkPngCodec::onRewind() { SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst, size_t dstRowBytes, const Options& options, - SkPMColor ctable[], int* ctableCount) { + SkPMColor ctable[], int* ctableCount, + int* rowsDecoded) { if (!conversion_possible(requestedInfo, this->getInfo())) { return kInvalidConversion; } @@ -483,14 +484,32 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* } // 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. + SkAutoMalloc storage; if (setjmp(png_jmpbuf(fPng_ptr))) { - SkCodecPrintf("setjmp long jump!\n"); - return kInvalidInput; + // 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 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(s)? Chromium uses png_process_data. + *rowsDecoded = row; + return kIncompleteInput; } bool hasAlpha = false; // FIXME: We could split these out based on subclass. - SkAutoMalloc storage; void* dstRow = dst; if (fNumberPasses > 1) { const int width = requestedInfo.width(); @@ -520,7 +539,7 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* } else { storage.reset(requestedInfo.width() * SkSwizzler::BytesPerPixel(fSrcConfig)); uint8_t* srcRow = static_cast(storage.get()); - for (int y = 0; y < requestedInfo.height(); y++) { + for (; row < requestedInfo.height(); row++) { png_read_rows(fPng_ptr, &srcRow, png_bytepp_NULL, 1); // FIXME: Only call IsOpaque once, outside the loop. Same for onGetScanlines. hasAlpha |= !SkSwizzler::IsOpaque(fSwizzler->swizzle(dstRow, srcRow)); @@ -549,6 +568,14 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* return kSuccess; } +uint32_t SkPngCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const { + const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); + if (colorPtr) { + return get_color_table_fill_value(colorType, colorPtr, 0); + } + return INHERITED::onGetFillValue(colorType, alphaType); +} + bool SkPngCodec::onReallyHasAlpha() const { switch (fAlphaState) { case kOpaque_AlphaState: @@ -603,15 +630,17 @@ public: return kSuccess; } - Result onGetScanlines(void* dst, int count, size_t rowBytes) override { + 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 kInvalidInput; + return row; } void* dstRow = dst; bool hasAlpha = false; - for (int i = 0; i < count; i++) { + for (; row < count; row++) { png_read_rows(this->png_ptr(), &fSrcRow, png_bytepp_NULL, 1); hasAlpha |= !SkSwizzler::IsOpaque(this->swizzler()->swizzle(dstRow, fSrcRow)); dstRow = SkTAddOffset(dstRow, rowBytes); @@ -626,23 +655,22 @@ public: // Otherwise, the AlphaState is unchanged. } - return kSuccess; + return row; } - Result onSkipScanlines(int count) override { - // FIXME: Could we use the return value of setjmp to specify the type of - // error? + 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 kInvalidInput; + return false; } //there is a potential tradeoff of memory vs speed created by putting this in a loop. //calling png_read_rows in a loop is insignificantly slower than calling it once with count //as png_read_rows has it's own loop which calls png_read_row count times. - for (int i = 0; i < count; i++) { + for (int row = 0; row < count; row++) { png_read_rows(this->png_ptr(), &fSrcRow, png_bytepp_NULL, 1); } - return SkCodec::kSuccess; + return true; } AlphaState alphaInScanlineDecode() const override { @@ -694,7 +722,7 @@ public: return SkCodec::kSuccess; } - Result onGetScanlines(void* dst, int count, size_t dstRowBytes) override { + 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) { @@ -707,7 +735,7 @@ public: // 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->onNextScanline(); + const int currScanline = this->nextScanline(); // This method would never be called if currScanline is -1 SkASSERT(currScanline != -1); @@ -719,12 +747,15 @@ public: if (setjmp(png_jmpbuf(this->png_ptr()))) { SkCodecPrintf("setjmp long jump!\n"); - return kInvalidInput; + // 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; } SkAutoMalloc storage(count * fSrcRowBytes); uint8_t* storagePtr = static_cast(storage.get()); uint8_t* srcRow; - const int startRow = this->onNextScanline(); + 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++){ @@ -760,12 +791,12 @@ public: // Otherwise, the AlphaState is unchanged. } - return kSuccess; + return count; } - SkCodec::Result onSkipScanlines(int count) override { + bool onSkipScanlines(int count) override { // The non-virtual version will update fCurrScanline. - return SkCodec::kSuccess; + return true; } AlphaState alphaInScanlineDecode() const override { diff --git a/src/codec/SkCodec_libpng.h b/src/codec/SkCodec_libpng.h index 3a16cc6813..6bdf58065b 100644 --- a/src/codec/SkCodec_libpng.h +++ b/src/codec/SkCodec_libpng.h @@ -32,16 +32,20 @@ public: virtual ~SkPngCodec(); protected: - Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*) + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*) override; SkEncodedFormat onGetEncodedFormat() const override { return kPNG_SkEncodedFormat; } bool onRewind() override; + uint32_t onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const override; bool onReallyHasAlpha() const final; // Helper to set up swizzler and color table. Also calls png_read_update_info. Result initializeSwizzler(const SkImageInfo& requestedInfo, const Options&, SkPMColor*, int* ctableCount); - SkSampler* getSampler() override { return fSwizzler; } + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler); + return fSwizzler; + } SkPngCodec(const SkImageInfo&, SkStream*, png_structp, png_infop, int, int); diff --git a/src/codec/SkCodec_wbmp.cpp b/src/codec/SkCodec_wbmp.cpp index d7f446bb57..14b720988f 100644 --- a/src/codec/SkCodec_wbmp.cpp +++ b/src/codec/SkCodec_wbmp.cpp @@ -86,11 +86,8 @@ SkSwizzler* SkWbmpCodec::initializeSwizzler(const SkImageInfo& info, opts.fZeroInitialized); } -SkCodec::Result SkWbmpCodec::readRow(uint8_t* row) { - if (this->stream()->read(row, fSrcRowBytes) != fSrcRowBytes) { - return kIncompleteInput; - } - return kSuccess; +bool SkWbmpCodec::readRow(uint8_t* row) { + return this->stream()->read(row, fSrcRowBytes) == fSrcRowBytes; } SkWbmpCodec::SkWbmpCodec(const SkImageInfo& info, SkStream* stream) @@ -109,7 +106,8 @@ SkCodec::Result SkWbmpCodec::onGetPixels(const SkImageInfo& info, size_t rowBytes, const Options& options, SkPMColor ctable[], - int* ctableCount) { + int* ctableCount, + int* rowsDecoded) { if (options.fSubset) { // Subsets are not supported. return kUnimplemented; @@ -133,9 +131,9 @@ SkCodec::Result SkWbmpCodec::onGetPixels(const SkImageInfo& info, SkAutoTMalloc src(fSrcRowBytes); void* dstRow = dst; for (int y = 0; y < size.height(); ++y) { - Result rowResult = this->readRow(src.get()); - if (kSuccess != rowResult) { - return rowResult; + if (!this->readRow(src.get())) { + *rowsDecoded = y; + return kIncompleteInput; } swizzler->swizzle(dstRow, src.get()); dstRow = SkTAddOffset(dstRow, rowBytes); @@ -158,17 +156,16 @@ SkCodec* SkWbmpCodec::NewFromStream(SkStream* stream) { return new SkWbmpCodec(info, streamDeleter.detach()); } -SkCodec::Result SkWbmpCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { +int SkWbmpCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { void* dstRow = dst; for (int y = 0; y < count; ++y) { - Result rowResult = this->readRow(fSrcBuffer.get()); - if (kSuccess != rowResult) { - return rowResult; + if (!this->readRow(fSrcBuffer.get())) { + return y; } fSwizzler->swizzle(dstRow, fSrcBuffer.get()); dstRow = SkTAddOffset(dstRow, dstRowBytes); } - return kSuccess; + return count; } SkCodec::Result SkWbmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, @@ -201,4 +198,3 @@ SkCodec::Result SkWbmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, return kSuccess; } - diff --git a/src/codec/SkCodec_wbmp.h b/src/codec/SkCodec_wbmp.h index 976a5a22f0..f54dd0f166 100644 --- a/src/codec/SkCodec_wbmp.h +++ b/src/codec/SkCodec_wbmp.h @@ -25,7 +25,7 @@ public: protected: SkEncodedFormat onGetEncodedFormat() const override; Result onGetPixels(const SkImageInfo&, void*, size_t, - const Options&, SkPMColor[], int*) override; + const Options&, SkPMColor[], int*, int*) override; bool onRewind() override; private: /* @@ -33,24 +33,27 @@ private: */ SkSwizzler* initializeSwizzler(const SkImageInfo& info, const SkPMColor* ctable, const Options& opts); - SkSampler* getSampler() override { return fSwizzler; } + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler || !createIfNecessary); + return fSwizzler; + } /* * Read a src row from the encoded stream */ - Result readRow(uint8_t* row); + bool readRow(uint8_t* row); SkWbmpCodec(const SkImageInfo&, SkStream*); - const size_t fSrcRowBytes; + const size_t fSrcRowBytes; // Used for scanline decodes: - SkAutoTUnref fColorTable; SkAutoTDelete fSwizzler; + SkAutoTUnref fColorTable; SkAutoTMalloc fSrcBuffer; // FIXME: Override onSkipScanlines to avoid swizzling. - Result onGetScanlines(void* dst, int count, size_t dstRowBytes) override; + int onGetScanlines(void* dst, int count, size_t dstRowBytes) override; Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options, SkPMColor inputColorTable[], int* inputColorCount) override; diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp index 30c1a39f06..196543b682 100644 --- a/src/codec/SkJpegCodec.cpp +++ b/src/codec/SkJpegCodec.cpp @@ -325,7 +325,8 @@ bool SkJpegCodec::onDimensionsSupported(const SkISize& size) { */ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, - const Options& options, SkPMColor*, int*) { + const Options& options, SkPMColor*, int*, + int* rowsDecoded) { if (options.fSubset) { // Subsets are not supported. return kUnimplemented; @@ -358,20 +359,11 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, JSAMPLE* dstRow = (JSAMPLE*) dst; for (uint32_t y = 0; y < dstHeight; y++) { // Read rows of the image - uint32_t rowsDecoded = jpeg_read_scanlines(dinfo, &dstRow, 1); + uint32_t lines = jpeg_read_scanlines(dinfo, &dstRow, 1); // If we cannot read enough rows, assume the input is incomplete - if (rowsDecoded != 1) { - // Fill the remainder of the image with black. This error handling - // behavior is unspecified but SkCodec consistently uses black as - // the fill color for opaque images. If the destination is kGray, - // the low 8 bits of SK_ColorBLACK will be used. Conveniently, - // these are zeros, which is the representation for black in kGray. - // If the destination is kRGB_565, the low 16 bits of SK_ColorBLACK - // will be used. Conveniently, these are zeros, which is the - // representation for black in kRGB_565. - SkSwizzler::Fill(dstRow, dstInfo, dstRowBytes, dstHeight - y, - SK_ColorBLACK, nullptr, options.fZeroInitialized); + if (lines != 1) { + *rowsDecoded = y; return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput); } @@ -388,9 +380,9 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, return kSuccess; } -SkSampler* SkJpegCodec::getSampler() { - if (fSwizzler) { - SkASSERT(fSrcRow && static_cast(fStorage.get()) == fSrcRow); +SkSampler* SkJpegCodec::getSampler(bool createIfNecessary) { + if (!createIfNecessary || fSwizzler) { + SkASSERT(!fSwizzler || (fSrcRow && static_cast(fStorage.get()) == fSrcRow)); return fSwizzler; } @@ -452,7 +444,7 @@ SkCodec::Result SkJpegCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, return kSuccess; } -SkCodec::Result SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { +int SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { // Set the jump location for libjpeg errors if (setjmp(fDecoderMgr->getJmpBuf())) { return fDecoderMgr->returnFailure("setjmp", kInvalidInput); @@ -471,10 +463,8 @@ SkCodec::Result SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowByte // Read row of the image uint32_t rowsDecoded = jpeg_read_scanlines(fDecoderMgr->dinfo(), &dstRow, 1); if (rowsDecoded != 1) { - SkSwizzler::Fill(dstRow, this->dstInfo(), rowBytes, count - y, - SK_ColorBLACK, nullptr, this->options().fZeroInitialized); fDecoderMgr->dinfo()->output_scanline = this->dstInfo().height(); - return kIncompleteInput; + return y; } // Convert to RGBA if necessary @@ -490,7 +480,7 @@ SkCodec::Result SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowByte dstRow = SkTAddOffset(dstRow, rowBytes); } } - return kSuccess; + return count; } #ifndef TURBO_HAS_SKIP @@ -504,14 +494,11 @@ SkCodec::Result SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowByte } #endif -SkCodec::Result SkJpegCodec::onSkipScanlines(int count) { +bool SkJpegCodec::onSkipScanlines(int count) { // Set the jump location for libjpeg errors if (setjmp(fDecoderMgr->getJmpBuf())) { - return fDecoderMgr->returnFailure("setjmp", kInvalidInput); + return fDecoderMgr->returnFalse("setjmp"); } - jpeg_skip_scanlines(fDecoderMgr->dinfo(), count); - - return kSuccess; + return count == jpeg_skip_scanlines(fDecoderMgr->dinfo(), count); } - diff --git a/src/codec/SkJpegCodec.h b/src/codec/SkJpegCodec.h index 6377c9d469..67680d66e8 100644 --- a/src/codec/SkJpegCodec.h +++ b/src/codec/SkJpegCodec.h @@ -50,7 +50,7 @@ protected: * Initiates the jpeg decode */ Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, - SkPMColor*, int*) override; + SkPMColor*, int*, int*) override; SkEncodedFormat onGetEncodedFormat() const override { return kJPEG_SkEncodedFormat; @@ -103,11 +103,11 @@ private: bool setOutputColorSpace(const SkImageInfo& dst); // scanline decoding - SkSampler* getSampler() override; + SkSampler* getSampler(bool createIfNecessary) override; Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options, SkPMColor ctable[], int* ctableCount) override; - Result onGetScanlines(void* dst, int count, size_t rowBytes) override; - Result onSkipScanlines(int count) override; + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + bool onSkipScanlines(int count) override; SkAutoTDelete fDecoderMgr; // We will save the state of the decompress struct after reading the header. diff --git a/src/codec/SkMaskSwizzler.h b/src/codec/SkMaskSwizzler.h index fbc951a070..0513d838c7 100644 --- a/src/codec/SkMaskSwizzler.h +++ b/src/codec/SkMaskSwizzler.h @@ -35,6 +35,15 @@ public: */ SkSwizzler::ResultAlpha swizzle(void* dst, const uint8_t* SK_RESTRICT src); + /** + * Implement fill using a custom width. + */ + void fill(const SkImageInfo& info, void* dst, size_t rowBytes, uint32_t colorOrIndex, + SkCodec::ZeroInitialized zeroInit) override { + const SkImageInfo fillInfo = info.makeWH(fDstWidth, info.height()); + SkSampler::Fill(fillInfo, dst, rowBytes, colorOrIndex, zeroInit); + } + private: /* diff --git a/src/codec/SkSampler.cpp b/src/codec/SkSampler.cpp new file mode 100644 index 0000000000..c69d003c0f --- /dev/null +++ b/src/codec/SkSampler.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkCodec.h" +#include "SkCodecPriv.h" +#include "SkSampler.h" +#include "SkUtils.h" + +void SkSampler::Fill(const SkImageInfo& info, void* dst, size_t rowBytes, + uint32_t colorOrIndex, SkCodec::ZeroInitialized zeroInit) { + SkASSERT(dst != nullptr); + + // Calculate bytes to fill. We use getSafeSize since the last row may not be padded. + const size_t bytesToFill = info.getSafeSize(rowBytes); + const int width = info.width(); + const int numRows = info.height(); + + // Use the proper memset routine to fill the remaining bytes + switch (info.colorType()) { + case kN32_SkColorType: { + // If memory is zero initialized, we may not need to fill + uint32_t color = colorOrIndex; + if (SkCodec::kYes_ZeroInitialized == zeroInit && 0 == color) { + return; + } + + // We must fill row by row in the case of unaligned row bytes + if (SkIsAlign4((size_t) dst) && SkIsAlign4(rowBytes)) { + sk_memset32((uint32_t*) dst, color, + (uint32_t) bytesToFill / sizeof(SkPMColor)); + } else { + // We must fill row by row in the case of unaligned row bytes. This is an + // unlikely, slow case. + SkCodecPrintf("Warning: Strange number of row bytes, fill will be slow.\n"); + uint32_t* dstRow = (uint32_t*) dst; + for (int row = 0; row < numRows; row++) { + for (int col = 0; col < width; col++) { + dstRow[col] = color; + } + dstRow = SkTAddOffset(dstRow, rowBytes); + } + } + break; + } + case kRGB_565_SkColorType: { + // If the destination is k565, the caller passes in a 16-bit color. + // We will not assert that the high bits of colorOrIndex must be zeroed. + // This allows us to take advantage of the fact that the low 16 bits of an + // SKPMColor may be a valid a 565 color. For example, the low 16 + // bits of SK_ColorBLACK are identical to the 565 representation + // for black. + + // If memory is zero initialized, we may not need to fill + uint16_t color = (uint16_t) colorOrIndex; + if (SkCodec::kYes_ZeroInitialized == zeroInit && 0 == color) { + return; + } + + if (SkIsAlign2((size_t) dst) && SkIsAlign2(rowBytes)) { + sk_memset16((uint16_t*) dst, color, (uint32_t) bytesToFill / sizeof(uint16_t)); + } else { + // We must fill row by row in the case of unaligned row bytes. This is an + // unlikely, slow case. + SkCodecPrintf("Warning: Strange number of row bytes, fill will be slow.\n"); + uint16_t* dstRow = (uint16_t*) dst; + for (int row = 0; row < numRows; row++) { + for (int col = 0; col < width; col++) { + dstRow[col] = color; + } + dstRow = SkTAddOffset(dstRow, rowBytes); + } + } + break; + } + case kIndex_8_SkColorType: + // On an index destination color type, always assume the input is an index. + // Fall through + case kGray_8_SkColorType: + // If the destination is kGray, the caller passes in an 8-bit color. + // We will not assert that the high bits of colorOrIndex must be zeroed. + // This allows us to take advantage of the fact that the low 8 bits of an + // SKPMColor may be a valid a grayscale color. For example, the low 8 + // bits of SK_ColorBLACK are identical to the grayscale representation + // for black. + + // If memory is zero initialized, we may not need to fill + if (SkCodec::kYes_ZeroInitialized == zeroInit && 0 == (uint8_t) colorOrIndex) { + return; + } + + memset(dst, (uint8_t) colorOrIndex, bytesToFill); + break; + default: + SkCodecPrintf("Error: Unsupported dst color type for fill(). Doing nothing.\n"); + SkASSERT(false); + break; + } +} diff --git a/src/codec/SkSampler.h b/src/codec/SkSampler.h index d7b4c98f23..afabc1f9eb 100644 --- a/src/codec/SkSampler.h +++ b/src/codec/SkSampler.h @@ -19,8 +19,43 @@ public: return this->onSetSampleX(sampleX); } + /** + * Fill the remainder of the destination with a single color + * + * @param info + * Contains the color type of the rows to fill. + * Contains the width of the destination rows to fill + * Contains the number of rows that we need to fill. + * + * @param dst + * The destination row to fill from. + * + * @param rowBytes + * Stride in bytes of the destination. + * + * @param colorOrIndex + * If colorType is kN32, colorOrIndex is treated as a 32-bit color. + * If colorType is k565, colorOrIndex is treated as a 16-bit color. + * If colorType is kGray, colorOrIndex is treated as an 8-bit color. + * If colorType is kIndex, colorOrIndex is treated as an 8-bit index. + * Other SkColorTypes are not supported. + * + * @param zeroInit + * Indicates whether memory is already zero initialized. + * + */ + static void Fill(const SkImageInfo& info, void* dst, size_t rowBytes, + uint32_t colorOrIndex, SkCodec::ZeroInitialized zeroInit); + + /** + * Allow subclasses to implement unique versions of fill(). + */ + virtual void fill(const SkImageInfo& info, void* dst, size_t rowBytes, + uint32_t colorOrIndex, SkCodec::ZeroInitialized zeroInit) {} + virtual ~SkSampler() {} private: + virtual int onSetSampleX(int) = 0; }; diff --git a/src/codec/SkScaledCodec.cpp b/src/codec/SkScaledCodec.cpp index fc51613f31..6b50a09f26 100644 --- a/src/codec/SkScaledCodec.cpp +++ b/src/codec/SkScaledCodec.cpp @@ -201,7 +201,8 @@ void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcD SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst, size_t rowBytes, const Options& options, - SkPMColor ctable[], int* ctableCount) { + SkPMColor ctable[], int* ctableCount, + int* rowsDecoded) { if (options.fSubset) { // Subsets are not supported. @@ -209,6 +210,9 @@ SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, voi } if (fCodec->dimensionsSupported(requestedInfo.dimensions())) { + // Make sure that the parent class does not fill on an incomplete decode, since + // fCodec will take care of filling the uninitialized lines. + *rowsDecoded = requestedInfo.height(); return fCodec->getPixels(requestedInfo, dst, rowBytes, &options, ctable, ctableCount); } @@ -237,7 +241,7 @@ SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, voi return result; } - SkSampler* sampler = fCodec->getSampler(); + SkSampler* sampler = fCodec->getSampler(true); if (!sampler) { return kUnimplemented; } @@ -248,61 +252,97 @@ SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, voi switch(fCodec->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: { - result = fCodec->skipScanlines(Y0); - if (kSuccess != result && kIncompleteInput != result) { - return result; + if (!fCodec->skipScanlines(Y0)) { + *rowsDecoded = 0; + return kIncompleteInput; } for (int y = 0; y < dstHeight; y++) { - result = fCodec->getScanlines(dst, 1, rowBytes); - if (kSuccess != result && kIncompleteInput != result) { - return result; + if (1 != fCodec->getScanlines(dst, 1, rowBytes)) { + // The failed call to getScanlines() will take care of + // filling the failed row, so we indicate that we have + // decoded (y + 1) rows. + *rowsDecoded = y + 1; + return kIncompleteInput; } if (y < dstHeight - 1) { - result = fCodec->skipScanlines(sampleY - 1); - if (kSuccess != result && kIncompleteInput != result) { - return result; + if (!fCodec->skipScanlines(sampleY - 1)) { + *rowsDecoded = y + 1; + return kIncompleteInput; } } dst = SkTAddOffset(dst, rowBytes); } - return result; + return kSuccess; } case SkCodec::kBottomUp_SkScanlineOrder: case SkCodec::kOutOfOrder_SkScanlineOrder: { - for (int y = 0; y < srcHeight; y++) { + Result result = kSuccess; + int y; + for (y = 0; y < srcHeight; y++) { int srcY = fCodec->nextScanline(); if (is_coord_necessary(srcY, sampleY, dstHeight)) { void* dstPtr = SkTAddOffset(dst, rowBytes * get_dst_coord(srcY, sampleY)); - result = fCodec->getScanlines(dstPtr, 1, rowBytes); - if (kSuccess != result && kIncompleteInput != result) { - return result; + if (1 != fCodec->getScanlines(dstPtr, 1, rowBytes)) { + result = kIncompleteInput; + break; } } else { - result = fCodec->skipScanlines(1); - if (kSuccess != result && kIncompleteInput != result) { - return result; + if (!fCodec->skipScanlines(1)) { + result = kIncompleteInput; + break; } } } + + // We handle filling uninitialized memory here instead of in the parent class. + // The parent class does not know that we are sampling. + if (kIncompleteInput == result) { + const uint32_t fillValue = fCodec->getFillValue(requestedInfo.colorType(), + requestedInfo.alphaType()); + for (; y < srcHeight; y++) { + int srcY = fCodec->outputScanline(y); + if (is_coord_necessary(srcY, sampleY, dstHeight)) { + void* dstRow = SkTAddOffset(dst, + rowBytes * get_dst_coord(srcY, sampleY)); + SkSampler::Fill(requestedInfo.makeWH(requestedInfo.width(), 1), dstRow, + rowBytes, fillValue, options.fZeroInitialized); + } + } + *rowsDecoded = dstHeight; + } return result; } case SkCodec::kNone_SkScanlineOrder: { SkAutoMalloc storage(srcHeight * rowBytes); uint8_t* storagePtr = static_cast(storage.get()); - result = fCodec->getScanlines(storagePtr, srcHeight, rowBytes); - if (kSuccess != result && kIncompleteInput != result) { - return result; - } + int scanlines = fCodec->getScanlines(storagePtr, srcHeight, rowBytes); storagePtr += Y0 * rowBytes; - for (int y = 0; y < dstHeight; y++) { + scanlines -= Y0; + int y = 0; + while (y < dstHeight && scanlines > 0) { memcpy(dst, storagePtr, rowBytes); storagePtr += sampleY * rowBytes; dst = SkTAddOffset(dst, rowBytes); + scanlines -= sampleY; + y++; } - return result; + if (y < dstHeight) { + // fCodec has already handled filling uninitialized memory. + *rowsDecoded = dstHeight; + return kIncompleteInput; + } + return kSuccess; } default: SkASSERT(false); return kUnimplemented; } } + +uint32_t SkScaledCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const { + return fCodec->onGetFillValue(colorType, alphaType); +} + +SkCodec::SkScanlineOrder SkScaledCodec::onGetScanlineOrder() const { + return fCodec->onGetScanlineOrder(); +} diff --git a/src/codec/SkSwizzler.cpp b/src/codec/SkSwizzler.cpp index 214655b85b..8d13e56bd2 100644 --- a/src/codec/SkSwizzler.cpp +++ b/src/codec/SkSwizzler.cpp @@ -10,7 +10,6 @@ #include "SkScaledCodec.h" #include "SkSwizzler.h" #include "SkTemplates.h" -#include "SkUtils.h" SkSwizzler::ResultAlpha SkSwizzler::GetResult(uint8_t zeroAlpha, uint8_t maxAlpha) { @@ -718,81 +717,3 @@ SkSwizzler::ResultAlpha SkSwizzler::swizzle(void* dst, const uint8_t* SK_RESTRIC return fRowProc(dst, src, fDstWidth, fDeltaSrc, fSampleX * fDeltaSrc, fX0 * fDeltaSrc, fColorTable); } - -void SkSwizzler::Fill(void* dstStartRow, const SkImageInfo& dstInfo, size_t dstRowBytes, - uint32_t numRows, uint32_t colorOrIndex, const SkPMColor* colorTable, - SkCodec::ZeroInitialized zeroInit) { - SkASSERT(dstStartRow != nullptr); - SkASSERT(numRows <= (uint32_t) dstInfo.height()); - - // Calculate bytes to fill. We use getSafeSize since the last row may not be padded. - const size_t bytesToFill = dstInfo.makeWH(dstInfo.width(), numRows).getSafeSize(dstRowBytes); - - // Use the proper memset routine to fill the remaining bytes - switch(dstInfo.colorType()) { - case kN32_SkColorType: - // Assume input is an index if we have a color table - uint32_t color; - if (nullptr != colorTable) { - color = colorTable[(uint8_t) colorOrIndex]; - // Otherwise, assume the input is a color - } else { - color = colorOrIndex; - } - - // If memory is zero initialized, we may not need to fill - if (SkCodec::kYes_ZeroInitialized == zeroInit && 0 == color) { - return; - } - - // We must fill row by row in the case of unaligned row bytes - if (SkIsAlign4((size_t) dstStartRow) && SkIsAlign4(dstRowBytes)) { - sk_memset32((uint32_t*) dstStartRow, color, - (uint32_t) bytesToFill / sizeof(SkPMColor)); - } else { - // This is an unlikely, slow case - SkCodecPrintf("Warning: Strange number of row bytes, fill will be slow.\n"); - uint32_t* dstRow = (uint32_t*) dstStartRow; - for (uint32_t row = 0; row < numRows; row++) { - for (int32_t col = 0; col < dstInfo.width(); col++) { - dstRow[col] = color; - } - dstRow = SkTAddOffset(dstRow, dstRowBytes); - } - } - break; - case kRGB_565_SkColorType: - // If the destination is k565, the caller passes in a 16-bit color. - // We will not assert that the high bits of colorOrIndex must be zeroed. - // This allows us to take advantage of the fact that the low 16 bits of an - // SKPMColor may be a valid a 565 color. For example, the low 16 - // bits of SK_ColorBLACK are identical to the 565 representation - // for black. - // If we ever want to fill with colorOrIndex != 0, we will probably need - // to implement this with sk_memset16(). - SkASSERT((uint16_t) colorOrIndex == (uint8_t) colorOrIndex); - // Fall through - case kIndex_8_SkColorType: - // On an index destination color type, always assume the input is an index. - // Fall through - case kGray_8_SkColorType: - // If the destination is kGray, the caller passes in an 8-bit color. - // We will not assert that the high bits of colorOrIndex must be zeroed. - // This allows us to take advantage of the fact that the low 8 bits of an - // SKPMColor may be a valid a grayscale color. For example, the low 8 - // bits of SK_ColorBLACK are identical to the grayscale representation - // for black. - - // If memory is zero initialized, we may not need to fill - if (SkCodec::kYes_ZeroInitialized == zeroInit && 0 == (uint8_t) colorOrIndex) { - return; - } - - memset(dstStartRow, (uint8_t) colorOrIndex, bytesToFill); - break; - default: - SkCodecPrintf("Error: Unsupported dst color type for fill(). Doing nothing.\n"); - SkASSERT(false); - break; - } -} diff --git a/src/codec/SkSwizzler.h b/src/codec/SkSwizzler.h index 9bffccbb47..d7f6337553 100644 --- a/src/codec/SkSwizzler.h +++ b/src/codec/SkSwizzler.h @@ -129,44 +129,6 @@ public: static SkSwizzler* CreateSwizzler(SrcConfig, const SkPMColor* ctable, const SkImageInfo& dstInfo, SkCodec::ZeroInitialized); - /** - * Fill the remainder of the destination with a single color - * - * @param dstStartRow - * The destination row to fill from. - * - * @param numRows - * The number of rows to fill. - * - * @param colorOrIndex - * @param colorTable - * If dstInfo.colorType() is kIndex8, colorOrIndex is assumed to be a uint8_t - * index, and colorTable is ignored. Each 8-bit pixel will be set to (uint8_t) - * index. - * - * If dstInfo.colorType() is kN32, colorOrIndex is treated differently depending on - * whether colorTable is nullptr: - * - * A nullptr colorTable means colorOrIndex is treated as an SkPMColor (premul or - * unpremul, depending on dstInfo.alphaType()). Each 4-byte pixel will be set to - * colorOrIndex. - - * A non-nullptr colorTable means colorOrIndex is treated as a uint8_t index into - * the colorTable. i.e. each 4-byte pixel will be set to - * colorTable[(uint8_t) colorOrIndex]. - * - * If dstInfo.colorType() is kGray, colorOrIndex is always treated as an 8-bit color. - * - * Other SkColorTypes are not supported. - * - * @param zeroInit - * Indicates whether memory is already zero initialized. - * - */ - static void Fill(void* dstStartRow, const SkImageInfo& dstInfo, size_t dstRowBytes, - uint32_t numRows, uint32_t colorOrIndex, const SkPMColor* colorTable, - SkCodec::ZeroInitialized zeroInit); - /** * Swizzle a line. Generally this will be called height times, once * for each row of source. @@ -181,6 +143,15 @@ public: */ ResultAlpha swizzle(void* dst, const uint8_t* SK_RESTRICT src); + /** + * Implement fill using a custom width. + */ + void fill(const SkImageInfo& info, void* dst, size_t rowBytes, uint32_t colorOrIndex, + SkCodec::ZeroInitialized zeroInit) override { + const SkImageInfo fillInfo = info.makeWH(fDstWidth, info.height()); + SkSampler::Fill(fillInfo, dst, rowBytes, colorOrIndex, zeroInit); + } + private: /** @@ -214,5 +185,6 @@ private: SkSwizzler(RowProc proc, const SkPMColor* ctable, int deltaSrc, int srcWidth); int onSetSampleX(int) override; + }; #endif // SkSwizzler_DEFINED diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp index ccffda9276..a0fab0a153 100644 --- a/src/codec/SkWebpCodec.cpp +++ b/src/codec/SkWebpCodec.cpp @@ -158,7 +158,8 @@ bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { } SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, - const Options& options, SkPMColor*, int*) { + const Options& options, SkPMColor*, int*, + int* rowsDecoded) { if (!webp_conversion_possible(dstInfo, this->getInfo())) { return kInvalidConversion; } @@ -234,10 +235,8 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, while (true) { const size_t bytesRead = stream()->read(buffer, BUFFER_SIZE); if (0 == bytesRead) { - // FIXME: Maybe this is an incomplete image? How to decide? Based - // on the number of rows decoded? We can know the number of rows - // decoded using WebPIDecGetRGB. - return kInvalidInput; + WebPIDecGetRGB(idec, rowsDecoded, NULL, NULL, NULL); + return kIncompleteInput; } switch (WebPIAppend(idec, buffer, bytesRead)) { diff --git a/src/codec/SkWebpCodec.h b/src/codec/SkWebpCodec.h index d60acede7b..97b3920ce9 100644 --- a/src/codec/SkWebpCodec.h +++ b/src/codec/SkWebpCodec.h @@ -21,7 +21,7 @@ public: static SkCodec* NewFromStream(SkStream*); static bool IsWebp(SkStream*); protected: - Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*) + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*) override; SkEncodedFormat onGetEncodedFormat() const override { return kWEBP_SkEncodedFormat; } diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp index 12b42e3ebb..91fb6897b5 100644 --- a/tests/CodexTest.cpp +++ b/tests/CodexTest.cpp @@ -8,6 +8,7 @@ #include "Resources.h" #include "SkBitmap.h" #include "SkCodec.h" +#include "SkData.h" #include "SkMD5.h" #include "SkRandom.h" #include "SkScaledCodec.h" @@ -75,14 +76,15 @@ SkIRect generate_random_subset(SkRandom* rand, int w, int h) { } static void test_codec(skiatest::Reporter* r, SkCodec* codec, SkBitmap& bm, const SkImageInfo& info, - const SkISize& size, bool supports565, SkMD5::Digest* digest, - const SkMD5::Digest* goodDigest) { + const SkISize& size, bool supports565, SkCodec::Result expectedResult, + SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) { + REPORTER_ASSERT(r, info.dimensions() == size); bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + REPORTER_ASSERT(r, result == expectedResult); md5(bm, digest); if (goodDigest) { @@ -92,16 +94,16 @@ static void test_codec(skiatest::Reporter* r, SkCodec* codec, SkBitmap& bm, cons { // Test decoding to 565 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); - SkCodec::Result expected = (supports565 && info.alphaType() == kOpaque_SkAlphaType) ? - SkCodec::kSuccess : SkCodec::kInvalidConversion; - test_info(r, codec, info565, expected, nullptr); + SkCodec::Result expected565 = (supports565 && info.alphaType() == kOpaque_SkAlphaType) ? + expectedResult : SkCodec::kInvalidConversion; + test_info(r, codec, info565, expected565, nullptr); } // Verify that re-decoding gives the same result. It is interesting to check this after // a decode to 565, since choosing to decode to 565 may result in some of the decode // options being modified. These options should return to their defaults on another // decode to kN32, so the new digest should match the old digest. - test_info(r, codec, info, SkCodec::kSuccess, digest); + test_info(r, codec, info, expectedResult, digest); { // Check alpha type conversions @@ -121,7 +123,7 @@ static void test_codec(skiatest::Reporter* r, SkCodec* codec, SkBitmap& bm, cons otherAt = kPremul_SkAlphaType; } // The other non-opaque alpha type should always succeed, but not match. - test_info(r, codec, info.makeAlphaType(otherAt), SkCodec::kSuccess, nullptr); + test_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr); } } } @@ -147,14 +149,24 @@ static void check(skiatest::Reporter* r, SkISize size, bool supportsScanlineDecoding, bool supportsSubsetDecoding, - bool supports565 = true) { + bool supports565 = true, + bool supportsIncomplete = true) { SkAutoTDelete stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } - SkAutoTDelete codec(SkCodec::NewFromStream(stream.detach())); + + SkAutoTDelete codec(nullptr); + bool isIncomplete = supportsIncomplete; + if (isIncomplete) { + size_t size = stream->getLength(); + SkAutoTUnref data((SkData::NewFromStream(stream, 2 * size / 3))); + codec.reset(SkCodec::NewFromData(data)); + } else { + codec.reset(SkCodec::NewFromStream(stream.detach())); + } if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; @@ -164,14 +176,15 @@ static void check(skiatest::Reporter* r, SkMD5::Digest codecDigest; SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); SkBitmap bm; - test_codec(r, codec, bm, info, size, supports565, &codecDigest, nullptr); + SkCodec::Result expectedResult = isIncomplete ? SkCodec::kIncompleteInput : SkCodec::kSuccess; + test_codec(r, codec, bm, info, size, supports565, expectedResult, &codecDigest, nullptr); // Scanline decoding follows. // Need to call startScanlineDecode() first. REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) - == SkCodec::kScanlineDecodingNotStarted); + == 0); REPORTER_ASSERT(r, codec->skipScanlines(1) - == SkCodec::kScanlineDecodingNotStarted); + == 0); const SkCodec::Result startResult = codec->startScanlineDecode(info); if (supportsScanlineDecoding) { @@ -180,8 +193,10 @@ static void check(skiatest::Reporter* r, REPORTER_ASSERT(r, startResult == SkCodec::kSuccess); for (int y = 0; y < info.height(); y++) { - SkCodec::Result result = codec->getScanlines(bm.getAddr(0, y), 1, 0); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0); + if (!isIncomplete) { + REPORTER_ASSERT(r, 1 == lines); + } } // verify that scanline decoding gives the same result. if (SkCodec::kTopDown_SkScanlineOrder == codec->getScanlineOrder()) { @@ -190,19 +205,21 @@ static void check(skiatest::Reporter* r, // Cannot continue to decode scanlines beyond the end REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) - == SkCodec::kInvalidParameters); + == 0); // Interrupting a scanline decode with a full decode starts from // scratch REPORTER_ASSERT(r, codec->startScanlineDecode(info) == SkCodec::kSuccess); - REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) - == SkCodec::kSuccess); + const int lines = codec->getScanlines(bm.getAddr(0, 0), 1, 0); + if (!isIncomplete) { + REPORTER_ASSERT(r, lines == 1); + } REPORTER_ASSERT(r, codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes()) - == SkCodec::kSuccess); + == expectedResult); REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) - == SkCodec::kScanlineDecodingNotStarted); + == 0); REPORTER_ASSERT(r, codec->skipScanlines(1) - == SkCodec::kScanlineDecodingNotStarted); + == 0); } else { REPORTER_ASSERT(r, startResult == SkCodec::kUnimplemented); } @@ -232,7 +249,7 @@ static void check(skiatest::Reporter* r, &opts, nullptr, nullptr); if (supportsSubsetDecoding) { - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + REPORTER_ASSERT(r, result == expectedResult); // Webp is the only codec that supports subsets, and it will have modified the subset // to have even left/top. REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); @@ -250,7 +267,15 @@ static void check(skiatest::Reporter* r, SkDebugf("Missing resource '%s'\n", path); return; } - SkAutoTDelete codec(SkScaledCodec::NewFromStream(stream.detach())); + + SkAutoTDelete codec(nullptr); + if (isIncomplete) { + size_t size = stream->getLength(); + SkAutoTUnref data((SkData::NewFromStream(stream, 2 * size / 3))); + codec.reset(SkScaledCodec::NewFromData(data)); + } else { + codec.reset(SkScaledCodec::NewFromStream(stream.detach())); + } if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; @@ -258,7 +283,13 @@ static void check(skiatest::Reporter* r, SkBitmap bm; SkMD5::Digest scaledCodecDigest; - test_codec(r, codec, bm, info, size, supports565, &scaledCodecDigest, &codecDigest); + test_codec(r, codec, bm, info, size, supports565, expectedResult, &scaledCodecDigest, + &codecDigest); + } + + // If we've just tested incomplete decodes, let's run the same test again on full decodes. + if (isIncomplete) { + check(r, path, size, supportsScanlineDecoding, supportsSubsetDecoding, supports565, false); } } @@ -275,39 +306,45 @@ DEF_TEST(Codec, r) { check(r, "randPixels.bmp", SkISize::Make(8, 8), true, false); // ICO + // FIXME: We are not ready to test incomplete ICOs // These two tests examine interestingly different behavior: // Decodes an embedded BMP image - check(r, "color_wheel.ico", SkISize::Make(128, 128), false, false); + check(r, "color_wheel.ico", SkISize::Make(128, 128), false, false, true, false); // Decodes an embedded PNG image - check(r, "google_chrome.ico", SkISize::Make(256, 256), false, false); + check(r, "google_chrome.ico", SkISize::Make(256, 256), false, false, true, false); // GIF - check(r, "box.gif", SkISize::Make(200, 55), true, false); - check(r, "color_wheel.gif", SkISize::Make(128, 128), true, false); - check(r, "randPixels.gif", SkISize::Make(8, 8), true, false); + // FIXME: We are not ready to test incomplete GIFs + check(r, "box.gif", SkISize::Make(200, 55), true, false, true, false); + check(r, "color_wheel.gif", SkISize::Make(128, 128), true, false, true, false); + // randPixels.gif is too small to test incomplete + check(r, "randPixels.gif", SkISize::Make(8, 8), true, false, true, false); // JPG check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false, false); check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false); - check(r, "grayscale.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, true, false); check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false); - check(r, "randPixels.jpg", SkISize::Make(8, 8), true, false); + // randPixels.jpg is too small to test incomplete + check(r, "randPixels.jpg", SkISize::Make(8, 8), true, false, true, false); // PNG - check(r, "arrow.png", SkISize::Make(187, 312), true, false); - check(r, "baby_tux.png", SkISize::Make(240, 246), true, false); - check(r, "color_wheel.png", SkISize::Make(128, 128), true, false); - check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true, false); - check(r, "mandrill_128.png", SkISize::Make(128, 128), true, false); - check(r, "mandrill_16.png", SkISize::Make(16, 16), true, false); - check(r, "mandrill_256.png", SkISize::Make(256, 256), true, false); - check(r, "mandrill_32.png", SkISize::Make(32, 32), true, false); - check(r, "mandrill_512.png", SkISize::Make(512, 512), true, false); - check(r, "mandrill_64.png", SkISize::Make(64, 64), true, false); - check(r, "plane.png", SkISize::Make(250, 126), true, false); - check(r, "plane_interlaced.png", SkISize::Make(250, 126), true, false); - check(r, "randPixels.png", SkISize::Make(8, 8), true, false); - check(r, "yellow_rose.png", SkISize::Make(400, 301), true, false); + check(r, "arrow.png", SkISize::Make(187, 312), true, false, true, false); + check(r, "baby_tux.png", SkISize::Make(240, 246), true, false, true, false); + check(r, "color_wheel.png", SkISize::Make(128, 128), true, false, true, false); + check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true, false, true, false); + check(r, "mandrill_128.png", SkISize::Make(128, 128), true, false, true, false); + check(r, "mandrill_16.png", SkISize::Make(16, 16), true, false, true, false); + check(r, "mandrill_256.png", SkISize::Make(256, 256), true, false, true, false); + check(r, "mandrill_32.png", SkISize::Make(32, 32), true, false, true, false); + check(r, "mandrill_512.png", SkISize::Make(512, 512), true, false, true, false); + check(r, "mandrill_64.png", SkISize::Make(64, 64), true, false, true, false); + check(r, "plane.png", SkISize::Make(250, 126), true, false, true, false); + // FIXME: We are not ready to test incomplete interlaced pngs + check(r, "plane_interlaced.png", SkISize::Make(250, 126), true, false, true, false); + check(r, "randPixels.png", SkISize::Make(8, 8), true, false, true, false); + check(r, "yellow_rose.png", SkISize::Make(400, 301), true, false, true, false); } // Test interlaced PNG in stripes, similar to DM's kStripe_Mode @@ -361,12 +398,12 @@ DEF_TEST(Codec_stripes, r) { // Odd stripes for (int i = 1; i < numStripes; i += 2) { // Skip the even stripes - result = codec->skipScanlines(stripeHeight); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + bool skipResult = codec->skipScanlines(stripeHeight); + REPORTER_ASSERT(r, skipResult); - result = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight, + int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight, bm.rowBytes()); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + REPORTER_ASSERT(r, linesDecoded == stripeHeight); } // Even stripes @@ -374,14 +411,14 @@ DEF_TEST(Codec_stripes, r) { REPORTER_ASSERT(r, result == SkCodec::kSuccess); for (int i = 0; i < numStripes; i += 2) { - result = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight, + int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight, bm.rowBytes()); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + REPORTER_ASSERT(r, linesDecoded == stripeHeight); // Skip the odd stripes if (i + 1 < numStripes) { - result = codec->skipScanlines(stripeHeight); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + bool skipResult = codec->skipScanlines(stripeHeight); + REPORTER_ASSERT(r, skipResult); } } @@ -390,12 +427,12 @@ DEF_TEST(Codec_stripes, r) { result = codec->startScanlineDecode(info); REPORTER_ASSERT(r, result == SkCodec::kSuccess); - result = codec->skipScanlines(height - remainingLines); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + bool skipResult = codec->skipScanlines(height - remainingLines); + REPORTER_ASSERT(r, skipResult); - result = codec->getScanlines(bm.getAddr(0, height - remainingLines), + int linesDecoded = codec->getScanlines(bm.getAddr(0, height - remainingLines), remainingLines, bm.rowBytes()); - REPORTER_ASSERT(r, result == SkCodec::kSuccess); + REPORTER_ASSERT(r, linesDecoded == remainingLines); } compare_to_good_digest(r, digest, bm); diff --git a/tests/SwizzlerTest.cpp b/tests/SwizzlerTest.cpp index 256a4b2bcd..95aaf38639 100644 --- a/tests/SwizzlerTest.cpp +++ b/tests/SwizzlerTest.cpp @@ -9,8 +9,10 @@ #include "Test.h" // These are the values that we will look for to indicate that the fill was successful -static const uint8_t kFillIndex = 0x1; -static const uint32_t kFillColor = 0x22334455; +static const uint8_t kFillIndex = 0x11; +static const uint8_t kFillGray = 0x22; +static const uint16_t kFill565 = 0x3344; +static const uint32_t kFillColor = 0x55667788; static void check_fill(skiatest::Reporter* r, const SkImageInfo& imageInfo, @@ -18,8 +20,7 @@ static void check_fill(skiatest::Reporter* r, uint32_t endRow, size_t rowBytes, uint32_t offset, - uint32_t colorOrIndex, - SkPMColor* colorTable) { + uint32_t colorOrIndex) { // Calculate the total size of the image in bytes. Use the smallest possible size. // The offset value tells us to adjust the pointer from the memory we allocate in order @@ -34,16 +35,15 @@ static void check_fill(skiatest::Reporter* r, // Adjust the pointer in order to test on different memory alignments uint8_t* imageData = storage.get() + offset; uint8_t* imageStart = imageData + rowBytes * startRow; - - // Fill image with the fill value starting at the indicated row - SkSwizzler::Fill(imageStart, imageInfo, rowBytes, endRow - startRow + 1, colorOrIndex, - colorTable, SkCodec::kNo_ZeroInitialized); + const SkImageInfo fillInfo = imageInfo.makeWH(imageInfo.width(), endRow - startRow + 1); + SkSampler::Fill(fillInfo, imageStart, rowBytes, colorOrIndex, SkCodec::kNo_ZeroInitialized); // Ensure that the pixels are filled properly // The bots should catch any memory corruption uint8_t* indexPtr = imageData + startRow * rowBytes; uint8_t* grayPtr = indexPtr; uint32_t* colorPtr = (uint32_t*) indexPtr; + uint16_t* color565Ptr = (uint16_t*) indexPtr; for (uint32_t y = startRow; y <= endRow; y++) { for (int32_t x = 0; x < imageInfo.width(); x++) { switch (imageInfo.colorType()) { @@ -54,8 +54,10 @@ static void check_fill(skiatest::Reporter* r, REPORTER_ASSERT(r, kFillColor == colorPtr[x]); break; case kGray_8_SkColorType: - // We always fill kGray with black - REPORTER_ASSERT(r, (uint8_t) kFillColor == grayPtr[x]); + REPORTER_ASSERT(r, kFillGray == grayPtr[x]); + break; + case kRGB_565_SkColorType: + REPORTER_ASSERT(r, kFill565 == color565Ptr[x]); break; default: REPORTER_ASSERT(r, false); @@ -69,12 +71,6 @@ static void check_fill(skiatest::Reporter* r, // Test Fill() with different combinations of dimensions, alignment, and padding DEF_TEST(SwizzlerFill, r) { - // Set up a color table - SkPMColor colorTable[kFillIndex + 1]; - colorTable[kFillIndex] = kFillColor; - // Apart from the fill index, we will leave the other colors in the color table uninitialized. - // If we incorrectly try to fill with this uninitialized memory, the bots will catch it. - // Test on an invalid width and representative widths const uint32_t widths[] = { 0, 10, 50 }; @@ -83,48 +79,44 @@ DEF_TEST(SwizzlerFill, r) { const uint32_t heights[] = { 1, 5, 10 }; // Test on interesting possibilities for row padding - const uint32_t paddings[] = { 0, 1, 2, 3, 4 }; + const uint32_t paddings[] = { 0, 4 }; // Iterate over test dimensions for (uint32_t width : widths) { for (uint32_t height : heights) { // Create image info objects - const SkImageInfo colorInfo = SkImageInfo::MakeN32(width, height, - kUnknown_SkAlphaType); - const SkImageInfo indexInfo = colorInfo.makeColorType(kIndex_8_SkColorType); + const SkImageInfo colorInfo = SkImageInfo::MakeN32(width, height, kUnknown_SkAlphaType); const SkImageInfo grayInfo = colorInfo.makeColorType(kGray_8_SkColorType); + const SkImageInfo indexInfo = colorInfo.makeColorType(kIndex_8_SkColorType); + const SkImageInfo color565Info = colorInfo.makeColorType(kRGB_565_SkColorType); for (uint32_t padding : paddings) { // Calculate row bytes - size_t colorRowBytes = SkColorTypeBytesPerPixel(kN32_SkColorType) * width + - padding; - size_t indexRowBytes = width + padding; - size_t grayRowBytes = indexRowBytes; + const size_t colorRowBytes = SkColorTypeBytesPerPixel(kN32_SkColorType) * width + + padding; + const size_t indexRowBytes = width + padding; + const size_t grayRowBytes = indexRowBytes; + const size_t color565RowBytes = + SkColorTypeBytesPerPixel(kRGB_565_SkColorType) * width + padding; // If there is padding, we can invent an offset to change the memory alignment - for (uint32_t offset = 0; offset <= padding; offset++) { + for (uint32_t offset = 0; offset <= padding; offset += 4) { // Test all possible start rows with all possible end rows for (uint32_t startRow = 0; startRow < height; startRow++) { for (uint32_t endRow = startRow; endRow < height; endRow++) { - // Fill with an index that we use to look up a color + // Test fill with each color type check_fill(r, colorInfo, startRow, endRow, colorRowBytes, offset, - kFillIndex, colorTable); - - // Fill with a color - check_fill(r, colorInfo, startRow, endRow, colorRowBytes, offset, - kFillColor, nullptr); - - // Fill with an index + kFillColor); check_fill(r, indexInfo, startRow, endRow, indexRowBytes, offset, - kFillIndex, nullptr); - - // Fill a grayscale image + kFillIndex); check_fill(r, grayInfo, startRow, endRow, grayRowBytes, offset, - kFillColor, nullptr); + kFillGray); + check_fill(r, color565Info, startRow, endRow, color565RowBytes, offset, + kFill565); } } } diff --git a/tools/SkBitmapRegionCanvas.cpp b/tools/SkBitmapRegionCanvas.cpp index c54d936edc..086ac19864 100644 --- a/tools/SkBitmapRegionCanvas.cpp +++ b/tools/SkBitmapRegionCanvas.cpp @@ -128,22 +128,13 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY, } // Skip the unneeded rows - if (SkCodec::kSuccess != fDecoder->skipScanlines(imageSubsetY)) { + if (!fDecoder->skipScanlines(imageSubsetY)) { SkDebugf("Error: Failed to skip scanlines.\n"); return nullptr; } // Decode the necessary rows - SkCodec::Result result = fDecoder->getScanlines(tmp.getAddr(0, 0), imageSubsetHeight, - tmp.rowBytes()); - switch (result) { - case SkCodec::kSuccess: - case SkCodec::kIncompleteInput: - break; - default: - SkDebugf("Error: Failed to get scanlines.\n"); - return nullptr; - } + fDecoder->getScanlines(tmp.getAddr(0, 0), imageSubsetHeight, tmp.rowBytes()); // Calculate the size of the output const int outWidth = get_scaled_dimension(inputWidth, sampleSize); diff --git a/tools/SkBitmapRegionDecoderInterface.cpp b/tools/SkBitmapRegionDecoderInterface.cpp index 5c769d676e..47de31f4ec 100644 --- a/tools/SkBitmapRegionDecoderInterface.cpp +++ b/tools/SkBitmapRegionDecoderInterface.cpp @@ -29,12 +29,12 @@ SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegi return new SkBitmapRegionSampler(decoder, width, height); } case kCanvas_Strategy: { - SkCodec* decoder = SkCodec::NewFromStream(stream); - if (nullptr == decoder) { + SkAutoTDelete codec(SkCodec::NewFromStream(stream)); + if (nullptr == codec) { SkDebugf("Error: Failed to create decoder.\n"); return nullptr; } - switch (decoder->getScanlineOrder()) { + switch (codec->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: case SkCodec::kNone_SkScanlineOrder: break; @@ -42,7 +42,7 @@ SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegi SkDebugf("Error: Scanline ordering not supported.\n"); return nullptr; } - return new SkBitmapRegionCanvas(decoder); + return new SkBitmapRegionCanvas(codec.detach()); } default: SkASSERT(false); diff --git a/tools/dm_flags.json b/tools/dm_flags.json index 5dc0b5295e..11baa6d469 100644 --- a/tools/dm_flags.json +++ b/tools/dm_flags.json @@ -125,6 +125,82 @@ "image", "decode", "Hopstarter-Mac-Folders-Apple.ico", + "_", + "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", "gpu", "skp", "_", @@ -275,6 +351,82 @@ "image", "decode", "Hopstarter-Mac-Folders-Apple.ico", + "_", + "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", "--match", "~WritePixels", "~tabl_mozilla_0", @@ -398,6 +550,82 @@ "image", "decode", "Hopstarter-Mac-Folders-Apple.ico", + "_", + "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", "--match", "~tabl_mozilla_0", "~desk_yahoonews_0", @@ -523,6 +751,82 @@ "image", "decode", "Hopstarter-Mac-Folders-Apple.ico", + "_", + "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", "--match", "~tabl_mozilla_0", "~desk_yahoonews_0" @@ -645,6 +949,82 @@ "image", "decode", "Hopstarter-Mac-Folders-Apple.ico", + "_", + "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", "--match", "~tabl_mozilla_0", "~desk_yahoonews_0" @@ -765,6 +1145,82 @@ "Hopstarter-Mac-Folders-Apple.ico", "_", "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", + "_", + "image", "_", "interlaced1.png", "_", @@ -910,6 +1366,82 @@ "Hopstarter-Mac-Folders-Apple.ico", "_", "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", + "_", + "image", "_", "interlaced1.png", "_", @@ -1051,6 +1583,82 @@ "image", "decode", "Hopstarter-Mac-Folders-Apple.ico", + "_", + "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", "pdf", "_", "_", @@ -1217,6 +1825,82 @@ "image", "decode", "Hopstarter-Mac-Folders-Apple.ico", + "_", + "image", + "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", "pdf", "_", "_", @@ -1386,6 +2070,82 @@ "_", "image", "decode", + "inc0.gif", + "_", + "image", + "decode", + "inc1.gif", + "_", + "image", + "decode", + "incInterlaced.gif", + "_", + "image", + "decode", + "inc0.jpg", + "_", + "image", + "decode", + "incGray.jpg", + "_", + "image", + "decode", + "inc0.wbmp", + "_", + "image", + "decode", + "inc1.wbmp", + "_", + "image", + "decode", + "inc0.webp", + "_", + "image", + "decode", + "inc1.webp", + "_", + "image", + "decode", + "inc0.ico", + "_", + "image", + "decode", + "inc1.ico", + "_", + "image", + "decode", + "inc0.png", + "_", + "image", + "decode", + "inc1.png", + "_", + "image", + "decode", + "inc2.png", + "_", + "image", + "decode", + "inc12.png", + "_", + "image", + "decode", + "inc13.png", + "_", + "image", + "decode", + "inc14.png", + "_", + "image", + "subset", + "inc0.webp", + "_", + "image", + "subset", + "inc1.webp", + "_", + "image", + "decode", "_", "_", "image", diff --git a/tools/dm_flags.py b/tools/dm_flags.py index 64b4cec93e..e8f39182fd 100755 --- a/tools/dm_flags.py +++ b/tools/dm_flags.py @@ -112,6 +112,27 @@ def get_args(bot): # New ico files that fail on SkImageDecoder blacklist.extend('_ image decode Hopstarter-Mac-Folders-Apple.ico'.split(' ')) + # Incomplete image tests that fail on SkImageDecoder + blacklist.extend('_ image decode inc0.gif'.split(' ')) + blacklist.extend('_ image decode inc1.gif'.split(' ')) + blacklist.extend('_ image decode incInterlaced.gif'.split(' ')) + blacklist.extend('_ image decode inc0.jpg'.split(' ')) + blacklist.extend('_ image decode incGray.jpg'.split(' ')) + blacklist.extend('_ image decode inc0.wbmp'.split(' ')) + blacklist.extend('_ image decode inc1.wbmp'.split(' ')) + blacklist.extend('_ image decode inc0.webp'.split(' ')) + blacklist.extend('_ image decode inc1.webp'.split(' ')) + blacklist.extend('_ image decode inc0.ico'.split(' ')) + blacklist.extend('_ image decode inc1.ico'.split(' ')) + blacklist.extend('_ image decode inc0.png'.split(' ')) + blacklist.extend('_ image decode inc1.png'.split(' ')) + blacklist.extend('_ image decode inc2.png'.split(' ')) + blacklist.extend('_ image decode inc12.png'.split(' ')) + blacklist.extend('_ image decode inc13.png'.split(' ')) + blacklist.extend('_ image decode inc14.png'.split(' ')) + blacklist.extend('_ image subset inc0.webp'.split(' ')) + blacklist.extend('_ image subset inc1.webp'.split(' ')) + # Leon doesn't care about this, so why run it? if 'Win' in bot: blacklist.extend('_ image decode _'.split(' '))