LCOV - code coverage report
Current view: top level - gfx/skia/skia/src/core - SkImageFilter.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 246 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 29 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright 2012 The Android Open Source Project
       3             :  *
       4             :  * Use of this source code is governed by a BSD-style license that can be
       5             :  * found in the LICENSE file.
       6             :  */
       7             : 
       8             : #include "SkImageFilter.h"
       9             : 
      10             : #include "SkCanvas.h"
      11             : #include "SkColorSpace_Base.h"
      12             : #include "SkFuzzLogging.h"
      13             : #include "SkImageFilterCache.h"
      14             : #include "SkLocalMatrixImageFilter.h"
      15             : #include "SkMatrixImageFilter.h"
      16             : #include "SkReadBuffer.h"
      17             : #include "SkRect.h"
      18             : #include "SkSpecialImage.h"
      19             : #include "SkSpecialSurface.h"
      20             : #include "SkValidationUtils.h"
      21             : #include "SkWriteBuffer.h"
      22             : #if SK_SUPPORT_GPU
      23             : #include "GrContext.h"
      24             : #include "GrFixedClip.h"
      25             : #include "GrRenderTargetContext.h"
      26             : #include "GrTextureProxy.h"
      27             : #include "SkGr.h"
      28             : #endif
      29             : 
      30             : #ifndef SK_IGNORE_TO_STRING
      31           0 : void SkImageFilter::CropRect::toString(SkString* str) const {
      32           0 :     if (!fFlags) {
      33           0 :         return;
      34             :     }
      35             : 
      36           0 :     str->appendf("cropRect (");
      37           0 :     if (fFlags & CropRect::kHasLeft_CropEdge) {
      38           0 :         str->appendf("%.2f, ", fRect.fLeft);
      39             :     } else {
      40           0 :         str->appendf("X, ");
      41             :     }
      42           0 :     if (fFlags & CropRect::kHasTop_CropEdge) {
      43           0 :         str->appendf("%.2f, ", fRect.fTop);
      44             :     } else {
      45           0 :         str->appendf("X, ");
      46             :     }
      47           0 :     if (fFlags & CropRect::kHasWidth_CropEdge) {
      48           0 :         str->appendf("%.2f, ", fRect.width());
      49             :     } else {
      50           0 :         str->appendf("X, ");
      51             :     }
      52           0 :     if (fFlags & CropRect::kHasHeight_CropEdge) {
      53           0 :         str->appendf("%.2f", fRect.height());
      54             :     } else {
      55           0 :         str->appendf("X");
      56             :     }
      57           0 :     str->appendf(") ");
      58             : }
      59             : #endif
      60             : 
      61           0 : void SkImageFilter::CropRect::applyTo(const SkIRect& imageBounds,
      62             :                                       const SkMatrix& ctm,
      63             :                                       bool embiggen,
      64             :                                       SkIRect* cropped) const {
      65           0 :     *cropped = imageBounds;
      66           0 :     if (fFlags) {
      67             :         SkRect devCropR;
      68           0 :         ctm.mapRect(&devCropR, fRect);
      69           0 :         SkIRect devICropR = devCropR.roundOut();
      70             : 
      71             :         // Compute the left/top first, in case we need to modify the right/bottom for a missing edge
      72           0 :         if (fFlags & kHasLeft_CropEdge) {
      73           0 :             if (embiggen || devICropR.fLeft > cropped->fLeft) {
      74           0 :                 cropped->fLeft = devICropR.fLeft;
      75             :             }
      76             :         } else {
      77           0 :             devICropR.fRight = cropped->fLeft + devICropR.width();
      78             :         }
      79           0 :         if (fFlags & kHasTop_CropEdge) {
      80           0 :             if (embiggen || devICropR.fTop > cropped->fTop) {
      81           0 :                 cropped->fTop = devICropR.fTop;
      82             :             }
      83             :         } else {
      84           0 :             devICropR.fBottom = cropped->fTop + devICropR.height();
      85             :         }
      86           0 :         if (fFlags & kHasWidth_CropEdge) {
      87           0 :             if (embiggen || devICropR.fRight < cropped->fRight) {
      88           0 :                 cropped->fRight = devICropR.fRight;
      89             :             }
      90             :         }
      91           0 :         if (fFlags & kHasHeight_CropEdge) {
      92           0 :             if (embiggen || devICropR.fBottom < cropped->fBottom) {
      93           0 :                 cropped->fBottom = devICropR.fBottom;
      94             :             }
      95             :         }
      96             :     }
      97           0 : }
      98             : 
      99             : ///////////////////////////////////////////////////////////////////////////////////////////////////
     100             : 
     101           0 : static int32_t next_image_filter_unique_id() {
     102             :     static int32_t gImageFilterUniqueID;
     103             : 
     104             :     // Never return 0.
     105             :     int32_t id;
     106           0 :     do {
     107           0 :         id = sk_atomic_inc(&gImageFilterUniqueID) + 1;
     108           0 :     } while (0 == id);
     109           0 :     return id;
     110             : }
     111             : 
     112           0 : void SkImageFilter::Common::allocInputs(int count) {
     113           0 :     fInputs.reset(count);
     114           0 : }
     115             : 
     116           0 : bool SkImageFilter::Common::unflatten(SkReadBuffer& buffer, int expectedCount) {
     117           0 :     const int count = buffer.readInt();
     118           0 :     if (!buffer.validate(count >= 0)) {
     119           0 :         return false;
     120             :     }
     121           0 :     if (!buffer.validate(expectedCount < 0 || count == expectedCount)) {
     122           0 :         return false;
     123             :     }
     124             : 
     125             :     SkFUZZF(("allocInputs: %d\n", count));
     126           0 :     this->allocInputs(count);
     127           0 :     for (int i = 0; i < count; i++) {
     128           0 :         if (buffer.readBool()) {
     129           0 :             fInputs[i] = sk_sp<SkImageFilter>(buffer.readImageFilter());
     130             :         }
     131           0 :         if (!buffer.isValid()) {
     132           0 :             return false;
     133             :         }
     134             :     }
     135             :     SkRect rect;
     136           0 :     buffer.readRect(&rect);
     137           0 :     if (!buffer.isValid() || !buffer.validate(SkIsValidRect(rect))) {
     138           0 :         return false;
     139             :     }
     140             : 
     141           0 :     uint32_t flags = buffer.readUInt();
     142           0 :     fCropRect = CropRect(rect, flags);
     143           0 :     if (buffer.isVersionLT(SkReadBuffer::kImageFilterNoUniqueID_Version)) {
     144             : 
     145           0 :         (void) buffer.readUInt();
     146             :     }
     147           0 :     return buffer.isValid();
     148             : }
     149             : 
     150             : ///////////////////////////////////////////////////////////////////////////////////////////////////
     151             : 
     152           0 : void SkImageFilter::init(sk_sp<SkImageFilter>* inputs,
     153             :                          int inputCount,
     154             :                          const CropRect* cropRect) {
     155           0 :     fCropRect = cropRect ? *cropRect : CropRect(SkRect(), 0x0);
     156             : 
     157           0 :     fInputs.reset(inputCount);
     158             : 
     159           0 :     for (int i = 0; i < inputCount; ++i) {
     160           0 :         if (!inputs[i] || inputs[i]->usesSrcInput()) {
     161           0 :             fUsesSrcInput = true;
     162             :         }
     163           0 :         fInputs[i] = inputs[i];
     164             :     }
     165           0 : }
     166             : 
     167           0 : SkImageFilter::SkImageFilter(sk_sp<SkImageFilter>* inputs,
     168             :                              int inputCount,
     169           0 :                              const CropRect* cropRect)
     170             :     : fUsesSrcInput(false)
     171           0 :     , fUniqueID(next_image_filter_unique_id()) {
     172           0 :     this->init(inputs, inputCount, cropRect);
     173           0 : }
     174             : 
     175           0 : SkImageFilter::~SkImageFilter() {
     176           0 :     SkImageFilterCache::Get()->purgeByKeys(fCacheKeys.begin(), fCacheKeys.count());
     177           0 : }
     178             : 
     179           0 : SkImageFilter::SkImageFilter(int inputCount, SkReadBuffer& buffer)
     180             :     : fUsesSrcInput(false)
     181           0 :     , fCropRect(SkRect(), 0x0)
     182           0 :     , fUniqueID(next_image_filter_unique_id()) {
     183           0 :     Common common;
     184           0 :     if (common.unflatten(buffer, inputCount)) {
     185           0 :         this->init(common.inputs(), common.inputCount(), &common.cropRect());
     186             :     }
     187           0 : }
     188             : 
     189           0 : void SkImageFilter::flatten(SkWriteBuffer& buffer) const {
     190           0 :     buffer.writeInt(fInputs.count());
     191           0 :     for (int i = 0; i < fInputs.count(); i++) {
     192           0 :         SkImageFilter* input = this->getInput(i);
     193           0 :         buffer.writeBool(input != nullptr);
     194           0 :         if (input != nullptr) {
     195           0 :             buffer.writeFlattenable(input);
     196             :         }
     197             :     }
     198           0 :     buffer.writeRect(fCropRect.rect());
     199           0 :     buffer.writeUInt(fCropRect.flags());
     200           0 : }
     201             : 
     202           0 : sk_sp<SkSpecialImage> SkImageFilter::filterImage(SkSpecialImage* src, const Context& context,
     203             :                                                  SkIPoint* offset) const {
     204           0 :     SkASSERT(src && offset);
     205             : 
     206           0 :     uint32_t srcGenID = fUsesSrcInput ? src->uniqueID() : 0;
     207           0 :     const SkIRect srcSubset = fUsesSrcInput ? src->subset() : SkIRect::MakeWH(0, 0);
     208           0 :     SkImageFilterCacheKey key(fUniqueID, context.ctm(), context.clipBounds(), srcGenID, srcSubset);
     209           0 :     if (context.cache()) {
     210           0 :         sk_sp<SkSpecialImage> result = context.cache()->get(key, offset);
     211           0 :         if (result) {
     212           0 :             return result;
     213             :         }
     214             :     }
     215             : 
     216           0 :     sk_sp<SkSpecialImage> result(this->onFilterImage(src, context, offset));
     217             : 
     218             : #if SK_SUPPORT_GPU
     219           0 :     if (src->isTextureBacked() && result && !result->isTextureBacked()) {
     220             :         // Keep the result on the GPU - this is still required for some
     221             :         // image filters that don't support GPU in all cases
     222           0 :         GrContext* context = src->getContext();
     223           0 :         result = result->makeTextureImage(context);
     224             :     }
     225             : #endif
     226             : 
     227           0 :     if (result && context.cache()) {
     228           0 :         context.cache()->set(key, result.get(), *offset);
     229           0 :         SkAutoMutexAcquire mutex(fMutex);
     230           0 :         fCacheKeys.push_back(key);
     231             :     }
     232             : 
     233           0 :     return result;
     234             : }
     235             : 
     236           0 : SkIRect SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
     237             :                                  MapDirection direction) const {
     238           0 :     if (kReverse_MapDirection == direction) {
     239           0 :         SkIRect bounds = this->onFilterNodeBounds(src, ctm, direction);
     240           0 :         return this->onFilterBounds(bounds, ctm, direction);
     241             :     } else {
     242           0 :         SkIRect bounds = this->onFilterBounds(src, ctm, direction);
     243           0 :         bounds = this->onFilterNodeBounds(bounds, ctm, direction);
     244             :         SkIRect dst;
     245           0 :         this->getCropRect().applyTo(bounds, ctm, this->affectsTransparentBlack(), &dst);
     246           0 :         return dst;
     247             :     }
     248             : }
     249             : 
     250           0 : SkRect SkImageFilter::computeFastBounds(const SkRect& src) const {
     251           0 :     if (0 == this->countInputs()) {
     252           0 :         return src;
     253             :     }
     254           0 :     SkRect combinedBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
     255           0 :     for (int i = 1; i < this->countInputs(); i++) {
     256           0 :         SkImageFilter* input = this->getInput(i);
     257           0 :         if (input) {
     258           0 :             combinedBounds.join(input->computeFastBounds(src));
     259             :         } else {
     260           0 :             combinedBounds.join(src);
     261             :         }
     262             :     }
     263           0 :     return combinedBounds;
     264             : }
     265             : 
     266           0 : bool SkImageFilter::canComputeFastBounds() const {
     267           0 :     if (this->affectsTransparentBlack()) {
     268           0 :         return false;
     269             :     }
     270           0 :     for (int i = 0; i < this->countInputs(); i++) {
     271           0 :         SkImageFilter* input = this->getInput(i);
     272           0 :         if (input && !input->canComputeFastBounds()) {
     273           0 :             return false;
     274             :         }
     275             :     }
     276           0 :     return true;
     277             : }
     278             : 
     279             : #if SK_SUPPORT_GPU
     280           0 : sk_sp<SkSpecialImage> SkImageFilter::DrawWithFP(GrContext* context,
     281             :                                                 sk_sp<GrFragmentProcessor> fp,
     282             :                                                 const SkIRect& bounds,
     283             :                                                 const OutputProperties& outputProperties) {
     284           0 :     GrPaint paint;
     285           0 :     paint.addColorFragmentProcessor(std::move(fp));
     286           0 :     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
     287             : 
     288           0 :     sk_sp<SkColorSpace> colorSpace = sk_ref_sp(outputProperties.colorSpace());
     289           0 :     GrPixelConfig config = GrRenderableConfigForColorSpace(colorSpace.get());
     290             :     sk_sp<GrRenderTargetContext> renderTargetContext(context->makeDeferredRenderTargetContext(
     291           0 :         SkBackingFit::kApprox, bounds.width(), bounds.height(), config, std::move(colorSpace)));
     292           0 :     if (!renderTargetContext) {
     293           0 :         return nullptr;
     294             :     }
     295           0 :     paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
     296             : 
     297           0 :     SkIRect dstIRect = SkIRect::MakeWH(bounds.width(), bounds.height());
     298           0 :     SkRect srcRect = SkRect::Make(bounds);
     299           0 :     SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
     300           0 :     GrFixedClip clip(dstIRect);
     301           0 :     renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
     302           0 :                                         srcRect);
     303             : 
     304             :     return SkSpecialImage::MakeDeferredFromGpu(context, dstIRect,
     305             :                                                kNeedNewImageUniqueID_SpecialImage,
     306           0 :                                                renderTargetContext->asTextureProxyRef(),
     307           0 :                                                renderTargetContext->refColorSpace());
     308             : }
     309             : #endif
     310             : 
     311           0 : bool SkImageFilter::asAColorFilter(SkColorFilter** filterPtr) const {
     312           0 :     SkASSERT(nullptr != filterPtr);
     313           0 :     if (!this->isColorFilterNode(filterPtr)) {
     314           0 :         return false;
     315             :     }
     316           0 :     if (nullptr != this->getInput(0) || (*filterPtr)->affectsTransparentBlack()) {
     317           0 :         (*filterPtr)->unref();
     318           0 :         return false;
     319             :     }
     320           0 :     return true;
     321             : }
     322             : 
     323           0 : bool SkImageFilter::canHandleComplexCTM() const {
     324           0 :     if (!this->onCanHandleComplexCTM()) {
     325           0 :         return false;
     326             :     }
     327           0 :     const int count = this->countInputs();
     328           0 :     for (int i = 0; i < count; ++i) {
     329           0 :         SkImageFilter* input = this->getInput(i);
     330           0 :         if (input && !input->canHandleComplexCTM()) {
     331           0 :             return false;
     332             :         }
     333             :     }
     334           0 :     return true;
     335             : }
     336             : 
     337           0 : bool SkImageFilter::applyCropRect(const Context& ctx, const SkIRect& srcBounds,
     338             :                                   SkIRect* dstBounds) const {
     339           0 :     SkIRect temp = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection);
     340           0 :     fCropRect.applyTo(temp, ctx.ctm(), this->affectsTransparentBlack(), dstBounds);
     341             :     // Intersect against the clip bounds, in case the crop rect has
     342             :     // grown the bounds beyond the original clip. This can happen for
     343             :     // example in tiling, where the clip is much smaller than the filtered
     344             :     // primitive. If we didn't do this, we would be processing the filter
     345             :     // at the full crop rect size in every tile.
     346           0 :     return dstBounds->intersect(ctx.clipBounds());
     347             : }
     348             : 
     349             : #if SK_SUPPORT_GPU
     350           0 : sk_sp<SkSpecialImage> SkImageFilter::ImageToColorSpace(SkSpecialImage* src,
     351             :                                                        const OutputProperties& outProps) {
     352             :     // There are several conditions that determine if we actually need to convert the source to the
     353             :     // destination's color space. Rather than duplicate that logic here, just try to make an xform
     354             :     // object. If that produces something, then both are tagged, and the source is in a different
     355             :     // gamut than the dest. There is some overhead to making the xform, but those are cached, and
     356             :     // if we get one back, that means we're about to use it during the conversion anyway.
     357           0 :     sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(src->getColorSpace(),
     358           0 :                                                                        outProps.colorSpace());
     359             : 
     360           0 :     if (!colorSpaceXform) {
     361             :         // No xform needed, just return the original image
     362           0 :         return sk_ref_sp(src);
     363             :     }
     364             : 
     365             :     sk_sp<SkSpecialSurface> surf(src->makeSurface(outProps,
     366           0 :                                                   SkISize::Make(src->width(), src->height())));
     367           0 :     if (!surf) {
     368           0 :         return sk_ref_sp(src);
     369             :     }
     370             : 
     371           0 :     SkCanvas* canvas = surf->getCanvas();
     372           0 :     SkASSERT(canvas);
     373           0 :     SkPaint p;
     374           0 :     p.setBlendMode(SkBlendMode::kSrc);
     375           0 :     src->draw(canvas, 0, 0, &p);
     376           0 :     return surf->makeImageSnapshot();
     377             : }
     378             : #endif
     379             : 
     380             : // Return a larger (newWidth x newHeight) copy of 'src' with black padding
     381             : // around it.
     382           0 : static sk_sp<SkSpecialImage> pad_image(SkSpecialImage* src,
     383             :                                        const SkImageFilter::OutputProperties& outProps,
     384             :                                        int newWidth, int newHeight, int offX, int offY) {
     385             :     // We would like to operate in the source's color space (so that we return an "identical"
     386             :     // image, other than the padding. To achieve that, we'd create new output properties:
     387             :     //
     388             :     // SkImageFilter::OutputProperties outProps(src->getColorSpace());
     389             :     //
     390             :     // That fails in at least two ways. For formats that are texturable but not renderable (like
     391             :     // F16 on some ES implementations), we can't create a surface to do the work. For sRGB, images
     392             :     // may be tagged with an sRGB color space (which leads to an sRGB config in makeSurface). But
     393             :     // the actual config of that sRGB image on a device with no sRGB support is non-sRGB.
     394             :     //
     395             :     // Rather than try to special case these situations, we execute the image padding in the
     396             :     // destination color space. This should not affect the output of the DAG in (almost) any case,
     397             :     // because the result of this call is going to be used as an input, where it would have been
     398             :     // switched to the destination space anyway. The one exception would be a filter that expected
     399             :     // to consume unclamped F16 data, but the padded version of the image is pre-clamped to 8888.
     400             :     // We can revisit this logic if that ever becomes an actual problem.
     401           0 :     sk_sp<SkSpecialSurface> surf(src->makeSurface(outProps, SkISize::Make(newWidth, newHeight)));
     402           0 :     if (!surf) {
     403           0 :         return nullptr;
     404             :     }
     405             : 
     406           0 :     SkCanvas* canvas = surf->getCanvas();
     407           0 :     SkASSERT(canvas);
     408             : 
     409           0 :     canvas->clear(0x0);
     410             : 
     411           0 :     src->draw(canvas, offX, offY, nullptr);
     412             : 
     413           0 :     return surf->makeImageSnapshot();
     414             : }
     415             : 
     416           0 : sk_sp<SkSpecialImage> SkImageFilter::applyCropRect(const Context& ctx,
     417             :                                                    SkSpecialImage* src,
     418             :                                                    SkIPoint* srcOffset,
     419             :                                                    SkIRect* bounds) const {
     420             :     const SkIRect srcBounds = SkIRect::MakeXYWH(srcOffset->x(), srcOffset->y(),
     421           0 :                                                 src->width(), src->height());
     422             : 
     423           0 :     SkIRect dstBounds = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection);
     424           0 :     fCropRect.applyTo(dstBounds, ctx.ctm(), this->affectsTransparentBlack(), bounds);
     425           0 :     if (!bounds->intersect(ctx.clipBounds())) {
     426           0 :         return nullptr;
     427             :     }
     428             : 
     429           0 :     if (srcBounds.contains(*bounds)) {
     430           0 :         return sk_sp<SkSpecialImage>(SkRef(src));
     431             :     } else {
     432             :         sk_sp<SkSpecialImage> img(pad_image(src, ctx.outputProperties(),
     433             :                                             bounds->width(), bounds->height(),
     434           0 :                                             srcOffset->x() - bounds->x(),
     435           0 :                                             srcOffset->y() - bounds->y()));
     436           0 :         *srcOffset = SkIPoint::Make(bounds->x(), bounds->y());
     437           0 :         return img;
     438             :     }
     439             : }
     440             : 
     441           0 : SkIRect SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
     442             :                                       MapDirection direction) const {
     443           0 :     if (this->countInputs() < 1) {
     444           0 :         return src;
     445             :     }
     446             : 
     447             :     SkIRect totalBounds;
     448           0 :     for (int i = 0; i < this->countInputs(); ++i) {
     449           0 :         SkImageFilter* filter = this->getInput(i);
     450           0 :         SkIRect rect = filter ? filter->filterBounds(src, ctm, direction) : src;
     451           0 :         if (0 == i) {
     452           0 :             totalBounds = rect;
     453             :         } else {
     454           0 :             totalBounds.join(rect);
     455             :         }
     456             :     }
     457             : 
     458           0 :     return totalBounds;
     459             : }
     460             : 
     461           0 : SkIRect SkImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const {
     462           0 :     return src;
     463             : }
     464             : 
     465             : 
     466           0 : SkImageFilter::Context SkImageFilter::mapContext(const Context& ctx) const {
     467             :     SkIRect clipBounds = this->onFilterNodeBounds(ctx.clipBounds(), ctx.ctm(),
     468           0 :                                                   MapDirection::kReverse_MapDirection);
     469           0 :     return Context(ctx.ctm(), clipBounds, ctx.cache(), ctx.outputProperties());
     470             : }
     471             : 
     472           0 : sk_sp<SkImageFilter> SkImageFilter::MakeMatrixFilter(const SkMatrix& matrix,
     473             :                                                      SkFilterQuality filterQuality,
     474             :                                                      sk_sp<SkImageFilter> input) {
     475           0 :     return SkMatrixImageFilter::Make(matrix, filterQuality, std::move(input));
     476             : }
     477             : 
     478           0 : sk_sp<SkImageFilter> SkImageFilter::makeWithLocalMatrix(const SkMatrix& matrix) const {
     479             :     // SkLocalMatrixImageFilter takes SkImage* in its factory, but logically that parameter
     480             :     // is *always* treated as a const ptr. Hence the const-cast here.
     481             :     //
     482           0 :     SkImageFilter* nonConstThis = const_cast<SkImageFilter*>(this);
     483           0 :     return SkLocalMatrixImageFilter::Make(matrix, sk_ref_sp<SkImageFilter>(nonConstThis));
     484             : }
     485             : 
     486           0 : sk_sp<SkSpecialImage> SkImageFilter::filterInput(int index,
     487             :                                                  SkSpecialImage* src,
     488             :                                                  const Context& ctx,
     489             :                                                  SkIPoint* offset) const {
     490           0 :     SkImageFilter* input = this->getInput(index);
     491           0 :     if (!input) {
     492           0 :         return sk_sp<SkSpecialImage>(SkRef(src));
     493             :     }
     494             : 
     495           0 :     sk_sp<SkSpecialImage> result(input->filterImage(src, this->mapContext(ctx), offset));
     496             : 
     497           0 :     SkASSERT(!result || src->isTextureBacked() == result->isTextureBacked());
     498             : 
     499           0 :     return result;
     500             : }
     501             : 
     502           0 : void SkImageFilter::PurgeCache() {
     503           0 :     SkImageFilterCache::Get()->purge();
     504           0 : }

Generated by: LCOV version 1.13