• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc.
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 "gm/gm.h"
9 
10 #include "include/core/SkBlendMode.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkColorFilter.h"
14 #include "include/core/SkImage.h"
15 #include "include/core/SkImageFilter.h"
16 #include "include/core/SkImageInfo.h"
17 #include "include/core/SkPaint.h"
18 #include "include/core/SkPoint.h"
19 #include "include/core/SkPoint3.h"
20 #include "include/core/SkRect.h"
21 #include "include/core/SkRefCnt.h"
22 #include "include/core/SkRegion.h"
23 #include "include/core/SkScalar.h"
24 #include "include/core/SkSize.h"
25 #include "include/core/SkString.h"
26 #include "include/core/SkSurface.h"
27 #include "include/core/SkTypes.h"
28 
29 #include "include/effects/SkImageFilters.h"
30 
31 #include "include/gpu/GrContext.h"
32 
33 #include "tools/Resources.h"
34 #include "tools/ToolUtils.h"
35 
36 #include <utility>
37 
38 ///////////////////////////////////////////////////////////////////////////////
39 
show_bounds(SkCanvas * canvas,const SkIRect * clip,const SkIRect * inSubset,const SkIRect * outSubset)40 static void show_bounds(SkCanvas* canvas, const SkIRect* clip, const SkIRect* inSubset,
41                         const SkIRect* outSubset) {
42     const SkIRect* rects[] { clip, inSubset, outSubset };
43     SkColor colors[] { SK_ColorBLUE, SK_ColorYELLOW, SK_ColorRED };
44 
45     SkPaint paint;
46     paint.setStyle(SkPaint::kStroke_Style);
47 
48     for (size_t i = 0; i < SK_ARRAY_COUNT(rects); ++i) {
49         // Skip null bounds rects, since not all methods have subsets
50         if (rects[i]) {
51             paint.setColor(colors[i]);
52             canvas->drawRect(SkRect::Make(*(rects[i])), paint);
53         }
54     }
55 }
56 
57 // Factories for creating image filters, either with or without a cropRect
58 // (this could go away if there was a SkImageFilter::makeWithCropRect() function, but that seems
59 //  less generally useful).
60 typedef sk_sp<SkImageFilter> (*FilterFactory)(sk_sp<SkImage> auxImage, const SkIRect* cropRect);
61 
color_filter_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)62 static sk_sp<SkImageFilter> color_filter_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
63     // The color filter uses kSrcIn so that it respects the transparency introduced by clamping;
64     // using kSrc would just turn the entire out rect to green regardless.
65     auto cf = SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrcIn);
66     return SkImageFilters::ColorFilter(std::move(cf), nullptr, cropRect);
67 }
68 
blur_filter_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)69 static sk_sp<SkImageFilter> blur_filter_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
70     return SkImageFilters::Blur(2.0f, 2.0f, nullptr, cropRect);
71 }
72 
drop_shadow_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)73 static sk_sp<SkImageFilter> drop_shadow_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
74     return SkImageFilters::DropShadow(10.0f, 5.0f, 3.0f, 3.0f, SK_ColorBLUE, nullptr, cropRect);
75 }
76 
offset_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)77 static sk_sp<SkImageFilter> offset_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
78     return SkImageFilters::Offset(10.f, 5.f, nullptr, cropRect);
79 }
80 
dilate_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)81 static sk_sp<SkImageFilter> dilate_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
82     return SkImageFilters::Dilate(10.f, 5.f, nullptr, cropRect);
83 }
84 
erode_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)85 static sk_sp<SkImageFilter> erode_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
86     return SkImageFilters::Erode(10.f, 5.f, nullptr, cropRect);
87 }
88 
displacement_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)89 static sk_sp<SkImageFilter> displacement_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
90     sk_sp<SkImageFilter> displacement = SkImageFilters::Image(std::move(auxImage));
91     return SkImageFilters::DisplacementMap(SkColorChannel::kR, SkColorChannel::kG, 40.f,
92                                            std::move(displacement), nullptr, cropRect);
93 }
94 
arithmetic_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)95 static sk_sp<SkImageFilter> arithmetic_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
96     sk_sp<SkImageFilter> background = SkImageFilters::Image(std::move(auxImage));
97     return SkImageFilters::Arithmetic(0.0f, .6f, 1.f, 0.f, false, std::move(background),
98                                       nullptr, cropRect);
99 }
100 
xfermode_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)101 static sk_sp<SkImageFilter> xfermode_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
102     sk_sp<SkImageFilter> background = SkImageFilters::Image(std::move(auxImage));
103     return SkImageFilters::Xfermode(
104             SkBlendMode::kModulate, std::move(background), nullptr, cropRect);
105 }
106 
convolution_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)107 static sk_sp<SkImageFilter> convolution_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
108     SkISize kernelSize = SkISize::Make(3, 3);
109     SkIPoint kernelOffset = SkIPoint::Make(1, 1);
110     // A Laplacian edge detector, ee https://en.wikipedia.org/wiki/Kernel_(image_processing)
111     SkScalar kernel[9] = {-1.f, -1.f, -1.f,
112                           -1.f, 8.f, -1.f,
113                           -1.f, -1.f, -1.f};
114     return SkImageFilters::MatrixConvolution(kernelSize, kernel, 1.f, 0.f, kernelOffset,
115                                              SkTileMode::kClamp, false, nullptr, cropRect);
116 }
117 
matrix_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)118 static sk_sp<SkImageFilter> matrix_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
119     SkMatrix matrix = SkMatrix::I();
120     matrix.setRotate(45.f, 50.f, 50.f);
121 
122     // This doesn't support a cropRect
123     return SkImageFilters::MatrixTransform(matrix, kLow_SkFilterQuality, nullptr);
124 }
125 
alpha_threshold_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)126 static sk_sp<SkImageFilter> alpha_threshold_factory(sk_sp<SkImage> auxImage,
127                                                     const SkIRect* cropRect) {
128     // Centered cross with higher opacity
129     SkRegion region(SkIRect::MakeLTRB(30, 45, 70, 55));
130     region.op(SkIRect::MakeLTRB(45, 30, 55, 70), SkRegion::kUnion_Op);
131 
132     return SkImageFilters::AlphaThreshold(region, 1.f, .2f, nullptr, cropRect);
133 }
134 
lighting_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)135 static sk_sp<SkImageFilter> lighting_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
136     // Must convert the RGB values of the source to alpha, since that is what the lighting filters
137     // use to estimate their normals. This color matrix changes the color to white and the alpha
138     // to be equal to the approx. luminance of the original color.
139     static const float kMatrix[20] = {
140         0.f, 0.f, 0.f, 0.f, 1.f,
141         0.f, 0.f, 0.f, 0.f, 1.f,
142         0.f, 0.f, 0.f, 0.f, 1.f,
143         0.2126f, 0.7152f, 0.0722f, 0.f, 0.f
144     };
145     sk_sp<SkImageFilter> srcToAlpha = SkImageFilters::ColorFilter(
146             SkColorFilters::Matrix(kMatrix), nullptr);
147 
148     // Combine both specular and diffuse into a single DAG since they use separate internal filter
149     // implementations.
150     SkScalar sinAzimuth = SkScalarSin(SkDegreesToRadians(225.f)),
151              cosAzimuth = SkScalarCos(SkDegreesToRadians(225.f));
152 
153     SkPoint3 spotTarget = SkPoint3::Make(SkIntToScalar(40), SkIntToScalar(40), 0);
154     SkPoint3 diffLocation = SkPoint3::Make(spotTarget.fX + 50 * cosAzimuth,
155                                            spotTarget.fY + 50 * sinAzimuth,
156                                            SkIntToScalar(10));
157     SkPoint3 specLocation = SkPoint3::Make(spotTarget.fX - 50 * sinAzimuth,
158                                            spotTarget.fY + 50 * cosAzimuth,
159                                            SkIntToScalar(10));
160     sk_sp<SkImageFilter> diffuse = SkImageFilters::PointLitDiffuse(
161             diffLocation, SK_ColorWHITE, /* scale */ 1.f, /* kd */ 2.f, srcToAlpha, cropRect);
162     sk_sp<SkImageFilter> specular = SkImageFilters::PointLitSpecular(
163             specLocation, SK_ColorRED, /* scale */ 1.f, /* ks */ 1.f, /* shine */ 8.f,
164             srcToAlpha, cropRect);
165     return SkImageFilters::Merge(std::move(diffuse), std::move(specular), cropRect);
166 }
167 
tile_factory(sk_sp<SkImage> auxImage,const SkIRect * cropRect)168 static sk_sp<SkImageFilter> tile_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
169     // Tile the subset over a large region
170     return SkImageFilters::Tile(SkRect::MakeLTRB(25, 25, 75, 75), SkRect::MakeWH(100, 100),
171                                 nullptr);
172 }
173 
174 namespace {
175     enum class Strategy {
176         // Uses makeWithFilter, passing in subset and clip directly
177         kMakeWithFilter,
178         // Uses saveLayer after clipRect() to filter on the restore (i.e. reference image)
179         kSaveLayer
180     };
181 };
182 
183 // In this GM, we're going to feed the inner portion of a 100x100 mandrill (i.e., strip off a
184 // 25-wide border) through the makeWithFilter method. We'll then draw the appropriate subset of the
185 // result to the screen at the given offset. Some filters rely on a secondary image, which will be a
186 // 100x100 checkerboard. The original image is drawn in the background so that alignment is clear
187 // when drawing the result at its reported offset.
188 class ImageMakeWithFilterGM : public skiagm::GM {
189 public:
ImageMakeWithFilterGM(Strategy strategy,bool filterWithCropRect=false)190     ImageMakeWithFilterGM (Strategy strategy, bool filterWithCropRect = false)
191             : fStrategy(strategy)
192             , fFilterWithCropRect(filterWithCropRect)
193             , fMainImage(nullptr)
194             , fAuxImage(nullptr) {}
195 
196 protected:
onShortName()197     SkString onShortName() override {
198         SkString name = SkString("imagemakewithfilter");
199 
200         if (fFilterWithCropRect) {
201             name.append("_crop");
202         }
203         if (fStrategy == Strategy::kSaveLayer) {
204             name.append("_ref");
205         }
206         return name;
207     }
208 
onISize()209     SkISize onISize() override { return SkISize::Make(1980, 860); }
210 
onOnceBeforeDraw()211     void onOnceBeforeDraw() override {
212         SkImageInfo info = SkImageInfo::MakeN32(100, 100, kUnpremul_SkAlphaType);
213         auto surface = SkSurface::MakeRaster(info, nullptr);
214 
215         sk_sp<SkImage> colorImage = GetResourceAsImage("images/mandrill_128.png");
216         // Resize to 100x100
217         surface->getCanvas()->drawImageRect(
218                 colorImage, SkRect::MakeWH(colorImage->width(), colorImage->height()),
219                 SkRect::MakeWH(info.width(), info.height()), nullptr);
220         fMainImage = surface->makeImageSnapshot();
221 
222         ToolUtils::draw_checkerboard(surface->getCanvas());
223         fAuxImage = surface->makeImageSnapshot();
224     }
225 
onDraw(SkCanvas * canvas)226     void onDraw(SkCanvas* canvas) override {
227         FilterFactory filters[] = {
228             color_filter_factory,
229             blur_filter_factory,
230             drop_shadow_factory,
231             offset_factory,
232             dilate_factory,
233             erode_factory,
234             displacement_factory,
235             arithmetic_factory,
236             xfermode_factory,
237             convolution_factory,
238             matrix_factory,
239             alpha_threshold_factory,
240             lighting_factory,
241             tile_factory
242         };
243         const char* filterNames[] = {
244             "Color",
245             "Blur",
246             "Drop Shadow",
247             "Offset",
248             "Dilate",
249             "Erode",
250             "Displacement",
251             "Arithmetic",
252             "Xfer Mode",
253             "Convolution",
254             "Matrix Xform",
255             "Alpha Threshold",
256             "Lighting",
257             "Tile"
258         };
259         static_assert(SK_ARRAY_COUNT(filters) == SK_ARRAY_COUNT(filterNames), "filter name length");
260 
261         SkIRect clipBounds[] {
262             { -20, -20, 100, 100 },
263             {   0,   0,  75,  75 },
264             {  20,  20, 100, 100 },
265             { -20, -20,  50,  50 },
266             {  20,  20,  50,  50 },
267             {  30,  30,  75,  75 }
268         };
269 
270         // These need to be GPU-backed when on the GPU to ensure that the image filters use the GPU
271         // code paths (otherwise they may choose to do CPU filtering then upload)
272         sk_sp<SkImage> mainImage, auxImage;
273         if (canvas->getGrContext()) {
274             if (canvas->getGrContext()->abandoned()) {
275                 return;
276             }
277             mainImage = fMainImage->makeTextureImage(canvas->getGrContext());
278             auxImage = fAuxImage->makeTextureImage(canvas->getGrContext());
279         } else {
280             mainImage = fMainImage;
281             auxImage = fAuxImage;
282         }
283         SkASSERT(mainImage && (mainImage->isTextureBacked() || !canvas->getGrContext()));
284         SkASSERT(auxImage && (auxImage->isTextureBacked() || !canvas->getGrContext()));
285 
286         SkScalar MARGIN = SkIntToScalar(40);
287         SkScalar DX = mainImage->width() + MARGIN;
288         SkScalar DY = auxImage->height() + MARGIN;
289 
290         // Header hinting at what the filters do
291         SkPaint textPaint;
292         textPaint.setAntiAlias(true);
293         SkFont font(nullptr, 12);
294         for (size_t i = 0; i < SK_ARRAY_COUNT(filterNames); ++i) {
295             canvas->drawString(filterNames[i], DX * i + MARGIN, 15, font, textPaint);
296         }
297 
298         canvas->translate(MARGIN, MARGIN);
299 
300         for (auto clipBound : clipBounds) {
301             canvas->save();
302             for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
303                 SkIRect subset = SkIRect::MakeXYWH(25, 25, 50, 50);
304                 SkIRect outSubset;
305 
306                 // Draw the original image faintly so that it aids in checking alignment of the
307                 // filtered result.
308                 SkPaint alpha;
309                 alpha.setAlphaf(0.3f);
310                 canvas->drawImage(mainImage, 0, 0, &alpha);
311 
312                 this->drawImageWithFilter(canvas, mainImage, auxImage, filters[i], clipBound,
313                                           subset, &outSubset);
314 
315                 // Draw outlines to highlight what was subset, what was cropped, and what was output
316                 // (no output subset is displayed for kSaveLayer since that information isn't avail)
317                 SkIRect* outSubsetBounds = nullptr;
318                 if (fStrategy != Strategy::kSaveLayer) {
319                     outSubsetBounds = &outSubset;
320                 }
321                 show_bounds(canvas, &clipBound, &subset, outSubsetBounds);
322 
323                 canvas->translate(DX, 0);
324             }
325             canvas->restore();
326             canvas->translate(0, DY);
327         }
328     }
329 
330 private:
331     Strategy fStrategy;
332     bool fFilterWithCropRect;
333     sk_sp<SkImage> fMainImage;
334     sk_sp<SkImage> fAuxImage;
335 
drawImageWithFilter(SkCanvas * canvas,sk_sp<SkImage> mainImage,sk_sp<SkImage> auxImage,FilterFactory filterFactory,const SkIRect & clip,const SkIRect & subset,SkIRect * dstRect)336     void drawImageWithFilter(SkCanvas* canvas, sk_sp<SkImage> mainImage, sk_sp<SkImage> auxImage,
337                              FilterFactory filterFactory, const SkIRect& clip,
338                              const SkIRect& subset, SkIRect* dstRect) {
339         // When creating the filter with a crop rect equal to the clip, we should expect to see no
340         // difference from a filter without a crop rect. However, if the CTM isn't managed properly
341         // by makeWithFilter, then the final result will be the incorrect intersection of the clip
342         // and the transformed crop rect.
343         sk_sp<SkImageFilter> filter = filterFactory(auxImage,
344                                                     fFilterWithCropRect ? &clip : nullptr);
345 
346         if (fStrategy == Strategy::kSaveLayer) {
347             SkAutoCanvasRestore acr(canvas, true);
348 
349             // Clip before the saveLayer with the filter
350             canvas->clipRect(SkRect::Make(clip));
351 
352             // Put the image filter on the layer
353             SkPaint paint;
354             paint.setImageFilter(filter);
355             canvas->saveLayer(nullptr, &paint);
356 
357             // Draw the original subset of the image
358             canvas->drawImageRect(mainImage, subset, SkRect::Make(subset), nullptr);
359 
360             *dstRect = subset;
361         } else {
362             sk_sp<SkImage> result;
363             SkIRect outSubset;
364             SkIPoint offset;
365 
366             result = mainImage->makeWithFilter(filter.get(), subset, clip, &outSubset, &offset);
367 
368             SkASSERT(result);
369             SkASSERT(mainImage->isTextureBacked() == result->isTextureBacked());
370 
371             *dstRect = SkIRect::MakeXYWH(offset.x(), offset.y(),
372                                          outSubset.width(), outSubset.height());
373             canvas->drawImageRect(result, outSubset, SkRect::Make(*dstRect), nullptr);
374         }
375     }
376 
377     typedef GM INHERITED;
378 };
379 // The different strategies should all look the same, with the exception of filters that affect
380 // transparent black (i.e. the lighting filter). In the save layer case, the filter affects the
381 // transparent pixels outside of the drawn subset, whereas the makeWithFilter is restricted. This
382 // works as intended.
383 DEF_GM( return new ImageMakeWithFilterGM(Strategy::kMakeWithFilter); )
384 DEF_GM( return new ImageMakeWithFilterGM(Strategy::kSaveLayer); )
385 // Test with crop rects on the image filters; should look identical to above if working correctly
386 DEF_GM( return new ImageMakeWithFilterGM(Strategy::kMakeWithFilter, true); )
387 DEF_GM( return new ImageMakeWithFilterGM(Strategy::kSaveLayer, true); )
388