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