diff --git a/gm/imagefilters.cpp b/gm/imagefilters.cpp index 2d39b52ca5..cf2d22cbc5 100644 --- a/gm/imagefilters.cpp +++ b/gm/imagefilters.cpp @@ -14,6 +14,7 @@ #include "include/core/SkImage.h" #include "include/core/SkImageFilter.h" #include "include/core/SkImageInfo.h" +#include "include/core/SkMaskFilter.h" #include "include/core/SkMatrix.h" #include "include/core/SkPaint.h" #include "include/core/SkRRect.h" @@ -23,7 +24,10 @@ #include "include/core/SkSurface.h" #include "include/core/SkTypes.h" #include "include/effects/SkColorMatrix.h" +#include "include/effects/SkGradientShader.h" +#include "include/effects/SkHighContrastFilter.h" #include "include/effects/SkImageFilters.h" +#include "include/effects/SkShaderMaskFilter.h" #include "tools/Resources.h" #include "tools/ToolUtils.h" @@ -189,3 +193,89 @@ protected: }; DEF_GM(return new SaveLayerWithBackdropGM();) + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// Test that color filters and mask filters are applied before the image filter, even if it would +// normally be a sprite draw that could avoid an auto-saveLayer. +DEF_SIMPLE_GM(imagefilters_effect_order, canvas, 512, 512) { + sk_sp image(GetResourceAsImage("images/mandrill_256.png")); + if (canvas->getGrContext()) { + image = image->makeTextureImage(canvas->getGrContext()); + } + + SkISize kernelSize = SkISize::Make(3, 3); + SkIPoint kernelOffset = SkIPoint::Make(1, 1); + // A Laplacian edge detector, ie https://en.wikipedia.org/wiki/Kernel_(image_processing) + SkScalar kernel[9] = {-1.f, -1.f, -1.f, + -1.f, 8.f, -1.f, + -1.f, -1.f, -1.f}; + auto edgeDetector = SkImageFilters::MatrixConvolution( + kernelSize, kernel, 1.f, 0.f, kernelOffset, SkTileMode::kClamp, false, nullptr); + // This uses the high contrast filter because it resembles a pre-processing step you may perform + // prior to edge detection. The specifics of the high contrast algorithm don't matter for the GM + auto edgeAmplify = SkHighContrastFilter::Make( + {false, SkHighContrastConfig::InvertStyle::kNoInvert, 0.5f}); + + SkPaint testCFPaint; + testCFPaint.setColorFilter(edgeAmplify); + testCFPaint.setImageFilter(edgeDetector); + + // The expected result is color filter then image filter, so represent this explicitly in the + // image filter graph. + SkPaint expectedCFPaint; + expectedCFPaint.setImageFilter(SkImageFilters::Compose(edgeDetector, + SkImageFilters::ColorFilter(edgeAmplify, nullptr))); + + // Draw the image twice (expected on the left, test on the right that should match) + SkRect crop = SkRect::Make(image->bounds()); + canvas->save(); + canvas->clipRect(crop); + canvas->drawImage(image, 0, 0, &expectedCFPaint); // Filter applied by draw's SkPaint + canvas->restore(); + + canvas->save(); + canvas->translate(image->width(), 0); + canvas->clipRect(crop); + canvas->drawImage(image, 0, 0, &testCFPaint); + canvas->restore(); + + // Now test mask filters. These should be run before the image filter, and thus have the same + // effect as multiplying by an alpha mask. + + // This mask filter pokes a hole in the center of the image + static constexpr SkColor kAlphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT }; + static constexpr SkScalar kPos[] = { 0.4f, 0.9f }; + sk_sp alphaMaskShader = SkGradientShader::MakeRadial( + {128.f, 128.f}, 128.f, kAlphas, kPos, 2, SkTileMode::kClamp); + sk_sp maskFilter = SkShaderMaskFilter::Make(alphaMaskShader); + + // If edge detector sees the mask filter, it'll have alpha and then blend with the original + // image; otherwise the mask filter will apply late (incorrectly) and none of the original + // image will be visible. + sk_sp edgeBlend = SkImageFilters::Xfermode(SkBlendMode::kSrcOver, + SkImageFilters::Image(image), edgeDetector); + + SkPaint testMaskPaint; + testMaskPaint.setMaskFilter(maskFilter); + testMaskPaint.setImageFilter(edgeBlend); + + SkPaint alphaPaint; + alphaPaint.setShader(alphaMaskShader); + SkPaint expectedMaskPaint; + expectedMaskPaint.setImageFilter(SkImageFilters::Compose(edgeBlend, + SkImageFilters::Xfermode(SkBlendMode::kSrcIn, + SkImageFilters::Paint(alphaPaint)))); + + canvas->save(); + canvas->translate(0, image->height()); + canvas->clipRect(crop); + canvas->drawImage(image, 0, 0, &expectedMaskPaint); + canvas->restore(); + + canvas->save(); + canvas->translate(image->width(), image->height()); + canvas->clipRect(crop); + canvas->drawImage(image, 0, 0, &testMaskPaint); + canvas->restore(); +} diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp index e9b0af9a07..7d5a475adc 100644 --- a/src/core/SkCanvas.cpp +++ b/src/core/SkCanvas.cpp @@ -2467,6 +2467,11 @@ bool SkCanvas::canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const return false; } + // The other paint effects need to be applied before the image filter, but the sprite draw + // applies the filter explicitly first. + if (paint.getAlphaf() < 1.f || paint.getColorFilter() || paint.getMaskFilter()) { + return false; + } // Currently we can only use the filterSprite code if we are clipped to the bitmap's bounds. // Once we can filter and the filter will return a result larger than itself, we should be // able to remove this constraint.