Example #1
0
/*
 * Read enough of the stream to initialize the SkGifCodec.
 * Returns a bool representing success or failure.
 *
 * @param codecOut
 * If it returned true, and codecOut was not nullptr,
 * codecOut will be set to a new SkGifCodec.
 *
 * @param gifOut
 * If it returned true, and codecOut was nullptr,
 * gifOut must be non-nullptr and gifOut will be set to a new
 * GifFileType pointer.
 *
 * @param stream
 * Deleted on failure.
 * codecOut will take ownership of it in the case where we created a codec.
 * Ownership is unchanged when we returned a gifOut.
 *
 */
bool SkGifCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, GifFileType** gifOut) {
    SkAutoTDelete<SkStream> streamDeleter(stream);

    // Read gif header, logical screen descriptor, and global color table
    SkAutoTCallVProc<GifFileType, CloseGif> gif(open_gif(stream));

    if (nullptr == gif) {
        gif_error("DGifOpen failed.\n");
        return false;
    }

    // Read through gif extensions to get to the image data.  Set the
    // transparent index based on the extension data.
    uint32_t transIndex;
    SkCodec::Result result = ReadUpToFirstImage(gif, &transIndex);
    if (kSuccess != result){
        return false;
    }

    // Read the image descriptor
    if (GIF_ERROR == DGifGetImageDesc(gif)) {
        return false;
    }
    // If reading the image descriptor is successful, the image count will be
    // incremented.
    SkASSERT(gif->ImageCount >= 1);

    if (nullptr != codecOut) {
        SkISize size;
        SkIRect frameRect;
        if (!GetDimensions(gif, &size, &frameRect)) {
            gif_error("Invalid gif size.\n");
            return false;
        }
        bool frameIsSubset = (size != frameRect.size());

        // Determine the encoded alpha type.  The transIndex might be valid if it less
        // than 256.  We are not certain that the index is valid until we process the color
        // table, since some gifs have color tables with less than 256 colors.  If
        // there might be a valid transparent index, we must indicate that the image has
        // alpha.
        // In the case where we must support alpha, we indicate kBinary, since every
        // pixel will either be fully opaque or fully transparent.
        SkEncodedInfo::Alpha alpha = (transIndex < 256) ? SkEncodedInfo::kBinary_Alpha :
                SkEncodedInfo::kOpaque_Alpha;

        // Return the codec
        // Use kPalette since Gifs are encoded with a color table.
        // Use 8-bits per component, since this is the output we get from giflib.
        // FIXME: Gifs can actually be encoded with 4-bits per pixel.  Can we support this?
        SkEncodedInfo info = SkEncodedInfo::Make(SkEncodedInfo::kPalette_Color, alpha, 8);
        *codecOut = new SkGifCodec(size.width(), size.height(), info, streamDeleter.release(),
                gif.release(), transIndex, frameRect, frameIsSubset);
    } else {
        SkASSERT(nullptr != gifOut);
        streamDeleter.release();
        *gifOut = gif.release();
    }
    return true;
}
Example #2
0
sk_sp<SkSpecialImage> SkImageSource::onFilterImage(SkSpecialImage* source, const Context& ctx,
                                                   SkIPoint* offset) const {
    SkRect dstRect;
    ctx.ctm().mapRect(&dstRect, fDstRect);

    SkRect bounds = SkRect::MakeIWH(fImage->width(), fImage->height());
    if (fSrcRect == bounds) {
        int iLeft = dstRect.fLeft;
        int iTop = dstRect.fTop;
        // TODO: this seems to be a very noise-prone way to determine this (esp. the floating-point
        // widths & heights).
        if (dstRect.width() == bounds.width() && dstRect.height() == bounds.height() &&
            iLeft == dstRect.fLeft && iTop == dstRect.fTop) {
            // The dest is just an un-scaled integer translation of the entire image; return it
            offset->fX = iLeft;
            offset->fY = iTop;

            return SkSpecialImage::MakeFromImage(SkIRect::MakeWH(fImage->width(), fImage->height()),
                                                 fImage, ctx.outputProperties().colorSpace(),
                                                 &source->props());
        }
    }

    const SkIRect dstIRect = dstRect.roundOut();

    sk_sp<SkSpecialSurface> surf(source->makeSurface(ctx.outputProperties(), dstIRect.size()));
    if (!surf) {
        return nullptr;
    }

    SkCanvas* canvas = surf->getCanvas();
    SkASSERT(canvas);

    // TODO: it seems like this clear shouldn't be necessary (see skbug.com/5075)
    canvas->clear(0x0);

    SkPaint paint;

    // Subtract off the integer component of the translation (will be applied in offset, below).
    dstRect.offset(-SkIntToScalar(dstIRect.fLeft), -SkIntToScalar(dstIRect.fTop));
    paint.setBlendMode(SkBlendMode::kSrc);
    // FIXME: this probably shouldn't be necessary, but drawImageRect asserts
    // None filtering when it's translate-only
    paint.setFilterQuality(
        fSrcRect.width() == dstRect.width() && fSrcRect.height() == dstRect.height() ?
               kNone_SkFilterQuality : fFilterQuality);
    canvas->drawImageRect(fImage.get(), fSrcRect, dstRect, &paint,
                          SkCanvas::kStrict_SrcRectConstraint);

    offset->fX = dstIRect.fLeft;
    offset->fY = dstIRect.fTop;
    return surf->makeImageSnapshot();
}
Example #3
0
sk_sp<SkSpecialImage> SkDropShadowImageFilter::onFilterImage(SkSpecialImage* source,
                                                             const Context& ctx,
                                                             SkIPoint* offset) const {
    SkIPoint inputOffset = SkIPoint::Make(0, 0);
    sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
    if (!input) {
        return nullptr;
    }

    const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(),
                                                  input->width(), input->height());
    SkIRect bounds;
    if (!this->applyCropRect(ctx, inputBounds, &bounds)) {
        return nullptr;
    }

    sk_sp<SkSpecialSurface> surf(source->makeSurface(ctx.outputProperties(), bounds.size()));
    if (!surf) {
        return nullptr;
    }

    SkCanvas* canvas = surf->getCanvas();
    SkASSERT(canvas);

    canvas->clear(0x0);

    SkVector sigma = SkVector::Make(fSigmaX, fSigmaY);
    ctx.ctm().mapVectors(&sigma, 1);
    sigma.fX = SkMaxScalar(0, sigma.fX);
    sigma.fY = SkMaxScalar(0, sigma.fY);

    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setImageFilter(SkBlurImageFilter::Make(sigma.fX, sigma.fY, nullptr));
    paint.setColorFilter(SkColorFilter::MakeModeFilter(fColor, SkBlendMode::kSrcIn));

    SkVector offsetVec = SkVector::Make(fDx, fDy);
    ctx.ctm().mapVectors(&offsetVec, 1);

    canvas->translate(SkIntToScalar(inputOffset.fX - bounds.fLeft),
                      SkIntToScalar(inputOffset.fY - bounds.fTop));
    input->draw(canvas, offsetVec.fX, offsetVec.fY, &paint);

    if (fShadowMode == kDrawShadowAndForeground_ShadowMode) {
        input->draw(canvas, 0, 0, nullptr);
    }
    offset->fX = bounds.fLeft;
    offset->fY = bounds.fTop;
    return surf->makeImageSnapshot();
}
Example #4
0
sk_sp<SkSpecialImage> SkOffsetImageFilter::onFilterImage(SkSpecialImage* source,
                                                         const Context& ctx,
                                                         SkIPoint* offset) const {
    SkIPoint srcOffset = SkIPoint::Make(0, 0);
    sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &srcOffset));
    if (!input) {
        return nullptr;
    }

    SkIPoint vec = map_offset_vector(ctx.ctm(), fOffset);

    if (!this->cropRectIsSet()) {
        offset->fX = Sk32_sat_add(srcOffset.fX, vec.fX);
        offset->fY = Sk32_sat_add(srcOffset.fY, vec.fY);
        return input;
    } else {
        SkIRect bounds;
        SkIRect srcBounds = SkIRect::MakeWH(input->width(), input->height());
        srcBounds.offset(srcOffset);
        if (!this->applyCropRect(ctx, srcBounds, &bounds)) {
            return nullptr;
        }

        sk_sp<SkSpecialSurface> surf(source->makeSurface(ctx.outputProperties(), bounds.size()));
        if (!surf) {
            return nullptr;
        }

        SkCanvas* canvas = surf->getCanvas();
        SkASSERT(canvas);

        // TODO: it seems like this clear shouldn't be necessary (see skbug.com/5075)
        canvas->clear(0x0);

        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrc);
        canvas->translate(SkIntToScalar(srcOffset.fX - bounds.fLeft),
                          SkIntToScalar(srcOffset.fY - bounds.fTop));

        input->draw(canvas, vec.fX, vec.fY, &paint);

        offset->fX = bounds.fLeft;
        offset->fY = bounds.fTop;
        return surf->makeImageSnapshot();
    }
}
Example #5
0
SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const {
    if (!is_valid_sample_size(sampleSize)) {
        return SkISize::Make(0, 0);
    }

    // We require that the input subset is a subset that is supported by SkAndroidCodec.
    // We test this by calling getSupportedSubset() and verifying that no modifications
    // are made to the subset.
    SkIRect copySubset = subset;
    if (!this->getSupportedSubset(&copySubset) || copySubset != subset) {
        return SkISize::Make(0, 0);
    }

    // If the subset is the entire image, for consistency, use getSampledDimensions().
    if (fInfo.dimensions() == subset.size()) {
        return this->getSampledDimensions(sampleSize);
    }

    // This should perhaps call a virtual function, but currently both of our subclasses
    // want the same implementation.
    return SkISize::Make(get_scaled_dimension(subset.width(), sampleSize),
                get_scaled_dimension(subset.height(), sampleSize));
}
SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels,
        size_t rowBytes, const AndroidOptions& options) {
    // Create an Options struct for the codec.
    SkCodec::Options codecOptions;
    codecOptions.fZeroInitialized = options.fZeroInitialized;
    codecOptions.fPremulBehavior = SkTransferFunctionBehavior::kIgnore;

    SkIRect* subset = options.fSubset;
    if (!subset || subset->size() == this->codec()->getInfo().dimensions()) {
        if (this->codec()->dimensionsSupported(info.dimensions())) {
            return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions);
        }

        // If the native codec does not support the requested scale, scale by sampling.
        return this->sampledDecode(info, pixels, rowBytes, options);
    }

    // We are performing a subset decode.
    int sampleSize = options.fSampleSize;
    SkISize scaledSize = this->getSampledDimensions(sampleSize);
    if (!this->codec()->dimensionsSupported(scaledSize)) {
        // If the native codec does not support the requested scale, scale by sampling.
        return this->sampledDecode(info, pixels, rowBytes, options);
    }

    // Calculate the scaled subset bounds.
    int scaledSubsetX = subset->x() / sampleSize;
    int scaledSubsetY = subset->y() / sampleSize;
    int scaledSubsetWidth = info.width();
    int scaledSubsetHeight = info.height();

    const SkImageInfo scaledInfo = info.makeWH(scaledSize.width(), scaledSize.height());

    {
        // Although startScanlineDecode expects the bottom and top to match the
        // SkImageInfo, startIncrementalDecode uses them to determine which rows to
        // decode.
        SkIRect incrementalSubset = SkIRect::MakeXYWH(scaledSubsetX, scaledSubsetY,
                                                      scaledSubsetWidth, scaledSubsetHeight);
        codecOptions.fSubset = &incrementalSubset;
        const SkCodec::Result startResult = this->codec()->startIncrementalDecode(
                scaledInfo, pixels, rowBytes, &codecOptions);
        if (SkCodec::kSuccess == startResult) {
            int rowsDecoded;
            const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded);
            if (incResult == SkCodec::kSuccess) {
                return SkCodec::kSuccess;
            }
            SkASSERT(SkCodec::kIncompleteInput == incResult);

            // FIXME: Can zero initialized be read from SkCodec::fOptions?
            this->codec()->fillIncompleteImage(scaledInfo, pixels, rowBytes,
                    options.fZeroInitialized, scaledSubsetHeight, rowsDecoded);
            return SkCodec::kIncompleteInput;
        } else if (startResult != SkCodec::kUnimplemented) {
            return startResult;
        }
        // Otherwise fall down to use the old scanline decoder.
        // codecOptions.fSubset will be reset below, so it will not continue to
        // point to the object that is no longer on the stack.
    }

    // Start the scanline decode.
    SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth,
            scaledSize.height());
    codecOptions.fSubset = &scanlineSubset;

    SkCodec::Result result = this->codec()->startScanlineDecode(scaledInfo,
            &codecOptions);
    if (SkCodec::kSuccess != result) {
        return result;
    }

    // At this point, we are only concerned with subsetting.  Either no scale was
    // requested, or the this->codec() is handling the scale.
    // Note that subsetting is only supported for kTopDown, so this code will not be
    // reached for other orders.
    SkASSERT(this->codec()->getScanlineOrder() == SkCodec::kTopDown_SkScanlineOrder);
    if (!this->codec()->skipScanlines(scaledSubsetY)) {
        this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
                scaledSubsetHeight, 0);
        return SkCodec::kIncompleteInput;
    }

    int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes);
    if (decodedLines != scaledSubsetHeight) {
        return SkCodec::kIncompleteInput;
    }
    return SkCodec::kSuccess;
}
sk_sp<SkSpecialImage> SkTileImageFilter::onFilterImage(SkSpecialImage* source,
                                                       const Context& ctx,
                                                       SkIPoint* offset) const {
    SkIPoint inputOffset = SkIPoint::Make(0, 0);
    sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
    if (!input) {
        return nullptr;
    }

    SkRect dstRect;
    ctx.ctm().mapRect(&dstRect, fDstRect);
    if (!dstRect.intersect(SkRect::Make(ctx.clipBounds()))) {
        return nullptr;
    }

    const SkIRect dstIRect = dstRect.roundOut();
    if (!fSrcRect.width() || !fSrcRect.height() || !dstIRect.width() || !dstIRect.height()) {
        return nullptr;
    }

    SkRect srcRect;
    ctx.ctm().mapRect(&srcRect, fSrcRect);
    SkIRect srcIRect;
    srcRect.roundOut(&srcIRect);
    srcIRect.offset(-inputOffset);
    const SkIRect inputBounds = SkIRect::MakeWH(input->width(), input->height());

    if (!SkIRect::Intersects(srcIRect, inputBounds)) {
        return nullptr;
    }

    // We create an SkImage here b.c. it needs to be a tight fit for the tiling
    sk_sp<SkImage> subset;
    if (inputBounds.contains(srcIRect)) {
        subset = input->makeTightSubset(srcIRect);
        if (!subset) {
            return nullptr;
        }
    } else {
        sk_sp<SkSurface> surf(input->makeTightSurface(ctx.outputProperties(), srcIRect.size()));
        if (!surf) {
            return nullptr;
        }

        SkCanvas* canvas = surf->getCanvas();
        SkASSERT(canvas);

        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kSrc);

        input->draw(canvas, 
                    SkIntToScalar(inputOffset.x()), SkIntToScalar(inputOffset.y()),
                    &paint);

        subset = surf->makeImageSnapshot();
    }
    SkASSERT(subset->width() == srcIRect.width());
    SkASSERT(subset->height() == srcIRect.height());

    sk_sp<SkSpecialSurface> surf(source->makeSurface(ctx.outputProperties(), dstIRect.size()));
    if (!surf) {
        return nullptr;
    }

    SkCanvas* canvas = surf->getCanvas();
    SkASSERT(canvas);

    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kSrc);
    paint.setShader(subset->makeShader(SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
    canvas->translate(-dstRect.fLeft, -dstRect.fTop);
    canvas->drawRect(dstRect, paint);
    offset->fX = dstIRect.fLeft;
    offset->fY = dstIRect.fTop;
    return surf->makeImageSnapshot();
}
Example #8
0
/*
 * Read enough of the stream to initialize the SkGifCodec.
 * Returns a bool representing success or failure.
 *
 * @param codecOut
 * If it returned true, and codecOut was not nullptr,
 * codecOut will be set to a new SkGifCodec.
 *
 * @param gifOut
 * If it returned true, and codecOut was nullptr,
 * gifOut must be non-nullptr and gifOut will be set to a new
 * GifFileType pointer.
 *
 * @param stream
 * Deleted on failure.
 * codecOut will take ownership of it in the case where we created a codec.
 * Ownership is unchanged when we returned a gifOut.
 *
 */
bool SkGifCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, GifFileType** gifOut) {
    SkAutoTDelete<SkStream> streamDeleter(stream);

    // Read gif header, logical screen descriptor, and global color table
    SkAutoTCallVProc<GifFileType, CloseGif> gif(open_gif(stream));

    if (nullptr == gif) {
        gif_error("DGifOpen failed.\n");
        return false;
    }

    // Read through gif extensions to get to the image data.  Set the
    // transparent index based on the extension data.
    uint32_t transIndex;
    SkCodec::Result result = ReadUpToFirstImage(gif, &transIndex);
    if (kSuccess != result){
        return false;
    }

    // Read the image descriptor
    if (GIF_ERROR == DGifGetImageDesc(gif)) {
        return false;
    }
    // If reading the image descriptor is successful, the image count will be
    // incremented.
    SkASSERT(gif->ImageCount >= 1);

    if (nullptr != codecOut) {
        SkISize size;
        SkIRect frameRect;
        if (!GetDimensions(gif, &size, &frameRect)) {
            gif_error("Invalid gif size.\n");
            return false;
        }
        bool frameIsSubset = (size != frameRect.size());

        // Determine the recommended alpha type.  The transIndex might be valid if it less
        // than 256.  We are not certain that the index is valid until we process the color
        // table, since some gifs have color tables with less than 256 colors.  If
        // there might be a valid transparent index, we must indicate that the image has
        // alpha.
        // In the case where we must support alpha, we have the option to set the
        // suggested alpha type to kPremul or kUnpremul.  Both are valid since the alpha
        // component will always be 0xFF or the entire 32-bit pixel will be set to zero.
        // We prefer kPremul because we support kPremul, and it is more efficient to use
        // kPremul directly even when kUnpremul is supported.
        SkAlphaType alphaType = (transIndex < 256) ? kPremul_SkAlphaType : kOpaque_SkAlphaType;

        // Return the codec
        // kIndex is the most natural color type for gifs, so we set this as
        // the default.
        SkImageInfo imageInfo = SkImageInfo::Make(size.width(), size.height(), kIndex_8_SkColorType,
                alphaType);
        *codecOut = new SkGifCodec(imageInfo, streamDeleter.detach(), gif.detach(), transIndex,
                frameRect, frameIsSubset);
    } else {
        SkASSERT(nullptr != gifOut);
        streamDeleter.detach();
        *gifOut = gif.detach();
    }
    return true;
}
Example #9
0
// Basic test of the SkSpecialImage public API (e.g., peekTexture, peekPixels & draw)
static void test_image(const sk_sp<SkSpecialImage>& img, skiatest::Reporter* reporter,
                       GrContext* context, bool isGPUBacked,
                       int offset, int size) {
    const SkIRect subset = img->subset();
    REPORTER_ASSERT(reporter, offset == subset.left());
    REPORTER_ASSERT(reporter, offset == subset.top());
    REPORTER_ASSERT(reporter, kSmallerSize == subset.width());
    REPORTER_ASSERT(reporter, kSmallerSize == subset.height());

    //--------------
    // Test that isTextureBacked reports the correct backing type
    REPORTER_ASSERT(reporter, isGPUBacked == img->isTextureBacked());

#if SK_SUPPORT_GPU
    //--------------
    // Test asTextureProxyRef - as long as there is a context this should succeed
    if (context) {
        sk_sp<GrTextureProxy> proxy(img->asTextureProxyRef(context));
        REPORTER_ASSERT(reporter, proxy);
    }
#endif

    //--------------
    // Test getROPixels - this should always succeed regardless of backing store
    SkBitmap bitmap;
    REPORTER_ASSERT(reporter, img->getROPixels(&bitmap));
    if (context) {
        REPORTER_ASSERT(reporter, kSmallerSize == bitmap.width());
        REPORTER_ASSERT(reporter, kSmallerSize == bitmap.height());
    } else {
        REPORTER_ASSERT(reporter, size == bitmap.width());
        REPORTER_ASSERT(reporter, size == bitmap.height());
    }

    //--------------
    // Test that draw restricts itself to the subset
    SkImageFilter::OutputProperties outProps(img->getColorSpace());
    sk_sp<SkSpecialSurface> surf(img->makeSurface(outProps, SkISize::Make(kFullSize, kFullSize),
                                                  kPremul_SkAlphaType));

    SkCanvas* canvas = surf->getCanvas();

    canvas->clear(SK_ColorBLUE);
    img->draw(canvas, SkIntToScalar(kPad), SkIntToScalar(kPad), nullptr);

    SkBitmap bm;
    bm.allocN32Pixels(kFullSize, kFullSize, false);

    bool result = canvas->readPixels(bm.info(), bm.getPixels(), bm.rowBytes(), 0, 0);
    SkASSERT_RELEASE(result);

    // Only the center (red) portion should've been drawn into the canvas
    REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kPad-1, kPad-1));
    REPORTER_ASSERT(reporter, SK_ColorRED  == bm.getColor(kPad, kPad));
    REPORTER_ASSERT(reporter, SK_ColorRED  == bm.getColor(kSmallerSize+kPad-1,
                                                          kSmallerSize+kPad-1));
    REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kSmallerSize+kPad,
                                                          kSmallerSize+kPad));

    //--------------
    // Test that asImage & makeTightSurface return appropriately sized objects
    // of the correct backing type
    SkIRect newSubset = SkIRect::MakeWH(subset.width(), subset.height());
    {
        sk_sp<SkImage> tightImg(img->asImage(&newSubset));

        REPORTER_ASSERT(reporter, tightImg->width() == subset.width());
        REPORTER_ASSERT(reporter, tightImg->height() == subset.height());
        REPORTER_ASSERT(reporter, isGPUBacked == tightImg->isTextureBacked());
        SkPixmap tmpPixmap;
        REPORTER_ASSERT(reporter, isGPUBacked != !!tightImg->peekPixels(&tmpPixmap));
    }
    {
        SkImageFilter::OutputProperties outProps(img->getColorSpace());
        sk_sp<SkSurface> tightSurf(img->makeTightSurface(outProps, subset.size()));

        REPORTER_ASSERT(reporter, tightSurf->width() == subset.width());
        REPORTER_ASSERT(reporter, tightSurf->height() == subset.height());
        REPORTER_ASSERT(reporter, isGPUBacked ==
                     !!tightSurf->getTextureHandle(SkSurface::kDiscardWrite_BackendHandleAccess));
        SkPixmap tmpPixmap;
        REPORTER_ASSERT(reporter, isGPUBacked != !!tightSurf->peekPixels(&tmpPixmap));
    }
}
Example #10
0
SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels,
        size_t rowBytes, const AndroidOptions& options) {
    // Create an Options struct for the codec.
    SkCodec::Options codecOptions;
    codecOptions.fZeroInitialized = options.fZeroInitialized;

    SkIRect* subset = options.fSubset;
    if (!subset || subset->size() == this->codec()->getInfo().dimensions()) {
        if (this->codec()->dimensionsSupported(info.dimensions())) {
            return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions,
                    options.fColorPtr, options.fColorCount);
        }

        // If the native codec does not support the requested scale, scale by sampling.
        return this->sampledDecode(info, pixels, rowBytes, options);
    }

    // We are performing a subset decode.
    int sampleSize = options.fSampleSize;
    SkISize scaledSize = this->getSampledDimensions(sampleSize);
    if (!this->codec()->dimensionsSupported(scaledSize)) {
        // If the native codec does not support the requested scale, scale by sampling.
        return this->sampledDecode(info, pixels, rowBytes, options);
    }

    // Calculate the scaled subset bounds.
    int scaledSubsetX = subset->x() / sampleSize;
    int scaledSubsetY = subset->y() / sampleSize;
    int scaledSubsetWidth = info.width();
    int scaledSubsetHeight = info.height();

    // Start the scanline decode.
    SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth,
            scaledSize.height());
    codecOptions.fSubset = &scanlineSubset;
    SkCodec::Result result = this->codec()->startScanlineDecode(info.makeWH(scaledSize.width(),
            scaledSize.height()), &codecOptions, options.fColorPtr, options.fColorCount);
    if (SkCodec::kSuccess != result) {
        return result;
    }

    // At this point, we are only concerned with subsetting.  Either no scale was
    // requested, or the this->codec() is handling the scale.
    switch (this->codec()->getScanlineOrder()) {
        case SkCodec::kTopDown_SkScanlineOrder:
        case SkCodec::kNone_SkScanlineOrder: {
            if (!this->codec()->skipScanlines(scaledSubsetY)) {
                this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
                        scaledSubsetHeight, 0);
                return SkCodec::kIncompleteInput;
            }

            int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes);
            if (decodedLines != scaledSubsetHeight) {
                return SkCodec::kIncompleteInput;
            }
            return SkCodec::kSuccess;
        }
        default:
            SkASSERT(false);
            return SkCodec::kUnimplemented;
    }
}
sk_sp<SkSpecialImage> SkMergeImageFilter::onFilterImage(SkSpecialImage* source, const Context& ctx,
                                                        SkIPoint* offset) const {
    int inputCount = this->countInputs();
    if (inputCount < 1) {
        return nullptr;
    }

    SkIRect bounds;
    bounds.setEmpty();

    SkAutoTDeleteArray<sk_sp<SkSpecialImage>> inputs(new sk_sp<SkSpecialImage>[inputCount]);
    SkAutoTDeleteArray<SkIPoint> offsets(new SkIPoint[inputCount]);

    // Filter all of the inputs.
    for (int i = 0; i < inputCount; ++i) {
        offsets[i].setZero();
        inputs[i] = this->filterInput(i, source, ctx, &offsets[i]);
        if (!inputs[i]) {
            continue;
        }
        const SkIRect inputBounds = SkIRect::MakeXYWH(offsets[i].fX, offsets[i].fY,
                                                      inputs[i]->width(), inputs[i]->height());
        bounds.join(inputBounds);
    }
    if (bounds.isEmpty()) {
        return nullptr;
    }

    // Apply the crop rect to the union of the inputs' bounds.
    // Note that the crop rect can only reduce the bounds, since this
    // filter does not affect transparent black.
    bool embiggen = false;
    this->getCropRect().applyTo(bounds, ctx.ctm(), embiggen, &bounds);
    if (!bounds.intersect(ctx.clipBounds())) {
        return nullptr;
    }

    const int x0 = bounds.left();
    const int y0 = bounds.top();

    sk_sp<SkSpecialSurface> surf(source->makeSurface(ctx.outputProperties(), bounds.size()));
    if (!surf) {
        return nullptr;
    }

    SkCanvas* canvas = surf->getCanvas();
    SkASSERT(canvas);

    canvas->clear(0x0);

    // Composite all of the filter inputs.
    for (int i = 0; i < inputCount; ++i) {
        if (!inputs[i]) {
            continue;
        }

        SkPaint paint;
        if (fModes) {
            paint.setBlendMode((SkBlendMode)fModes[i]);
        }

        inputs[i]->draw(canvas,
                        SkIntToScalar(offsets[i].x() - x0), SkIntToScalar(offsets[i].y() - y0),
                        &paint);
    }

    offset->fX = bounds.left();
    offset->fY = bounds.top();
    return surf->makeImageSnapshot();
}