/* * 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; }
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(); }
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(); }
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(); } }
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(©Subset) || 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(); }
/* * 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; }
// 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)); } }
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(); }