1 /*
2 * Copyright 2015 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 #include "include/core/SkBitmap.h"
10 #include "include/core/SkBlendMode.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkFont.h"
14 #include "include/core/SkImage.h"
15 #include "include/core/SkImageFilter.h"
16 #include "include/core/SkPaint.h"
17 #include "include/core/SkPoint.h"
18 #include "include/core/SkRect.h"
19 #include "include/core/SkRefCnt.h"
20 #include "include/core/SkScalar.h"
21 #include "include/core/SkShader.h"
22 #include "include/core/SkSize.h"
23 #include "include/core/SkString.h"
24 #include "include/core/SkSurface.h"
25 #include "include/core/SkTileMode.h"
26 #include "include/core/SkTypes.h"
27 #include "include/effects/SkGradientShader.h"
28 #include "include/effects/SkImageFilters.h"
29 #include "tools/DecodeUtils.h"
30 #include "tools/Resources.h"
31 #include "tools/ToolUtils.h"
32 #include "tools/fonts/FontToolUtils.h"
33 #include "tools/timer/TimeUtils.h"
34
35 #include <utility>
36
37 namespace skiagm {
38
39 // This GM draws image filters with a CTM containing shearing / rotation.
40 // It checks that the scale portion of the CTM is correctly extracted
41 // and applied to the image inputs separately from the non-scale portion.
42
make_gradient_circle(int width,int height)43 static sk_sp<SkImage> make_gradient_circle(int width, int height) {
44 SkScalar x = SkIntToScalar(width / 2);
45 SkScalar y = SkIntToScalar(height / 2);
46 SkScalar radius = std::min(x, y) * 0.8f;
47
48 auto surface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(width, height)));
49 SkCanvas* canvas = surface->getCanvas();
50
51 canvas->clear(0x00000000);
52 SkColor colors[2];
53 colors[0] = SK_ColorWHITE;
54 colors[1] = SK_ColorBLACK;
55 SkPaint paint;
56 paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(x, y), radius, colors, nullptr, 2,
57 SkTileMode::kClamp));
58 canvas->drawCircle(x, y, radius, paint);
59
60 return surface->makeImageSnapshot();
61 }
62
63 class ImageFiltersTransformedGM : public GM {
64 public:
ImageFiltersTransformedGM()65 ImageFiltersTransformedGM() {
66 this->setBGColor(SK_ColorBLACK);
67 }
68
69 protected:
getName() const70 SkString getName() const override { return SkString("imagefilterstransformed"); }
71
getISize()72 SkISize getISize() override { return SkISize::Make(420, 240); }
73
onOnceBeforeDraw()74 void onOnceBeforeDraw() override {
75 fCheckerboard =
76 ToolUtils::create_checkerboard_image(64, 64, 0xFFA0A0A0, 0xFF404040, 8);
77 fGradientCircle = make_gradient_circle(64, 64);
78 }
79
onDraw(SkCanvas * canvas)80 void onDraw(SkCanvas* canvas) override {
81 sk_sp<SkImageFilter> gradient(SkImageFilters::Image(fGradientCircle,
82 SkFilterMode::kLinear));
83 sk_sp<SkImageFilter> checkerboard(SkImageFilters::Image(fCheckerboard,
84 SkFilterMode::kLinear));
85 sk_sp<SkImageFilter> filters[] = {
86 SkImageFilters::Blur(12, 0, nullptr),
87 SkImageFilters::DropShadow(0, 15, 8, 0, SK_ColorGREEN, nullptr),
88 SkImageFilters::DisplacementMap(SkColorChannel::kR, SkColorChannel::kR, 12,
89 std::move(gradient), checkerboard),
90 SkImageFilters::Dilate(2, 2, checkerboard),
91 SkImageFilters::Erode(2, 2, checkerboard),
92 };
93
94 const SkScalar margin = SkIntToScalar(20);
95 const SkScalar size = SkIntToScalar(60);
96
97 for (size_t j = 0; j < 3; j++) {
98 canvas->save();
99 canvas->translate(margin, 0);
100 for (size_t i = 0; i < std::size(filters); ++i) {
101 SkPaint paint;
102 paint.setColor(SK_ColorWHITE);
103 paint.setImageFilter(filters[i]);
104 paint.setAntiAlias(true);
105 canvas->save();
106 canvas->translate(size * SK_ScalarHalf, size * SK_ScalarHalf);
107 canvas->scale(SkDoubleToScalar(0.8), SkDoubleToScalar(0.8));
108 if (j == 1) {
109 canvas->rotate(SkIntToScalar(45));
110 } else if (j == 2) {
111 canvas->skew(SkDoubleToScalar(0.5), SkDoubleToScalar(0.2));
112 }
113 canvas->translate(-size * SK_ScalarHalf, -size * SK_ScalarHalf);
114 canvas->drawOval(SkRect::MakeXYWH(0, size * SkDoubleToScalar(0.1),
115 size, size * SkDoubleToScalar(0.6)), paint);
116 canvas->restore();
117 canvas->translate(size + margin, 0);
118 }
119 canvas->restore();
120 canvas->translate(0, size + margin);
121 }
122 }
123
124 private:
125 sk_sp<SkImage> fCheckerboard;
126 sk_sp<SkImage> fGradientCircle;
127 using INHERITED = GM;
128 };
129 DEF_GM( return new ImageFiltersTransformedGM; )
130 } // namespace skiagm
131
132 //////////////////////////////////////////////////////////////////////////////
133
134 DEF_SIMPLE_GM(rotate_imagefilter, canvas, 500, 500) {
135 SkPaint paint;
136
137 const SkRect r = SkRect::MakeXYWH(50, 50, 100, 100);
138
139 sk_sp<SkImageFilter> filters[] = {
140 nullptr,
141 SkImageFilters::Blur(6, 0, nullptr),
142 SkImageFilters::Blend(SkBlendMode::kSrcOver, nullptr),
143 };
144
145 for (auto& filter : filters) {
146 paint.setAntiAlias(false);
147 paint.setImageFilter(filter);
148
149 canvas->save();
150
151 canvas->drawRect(r, paint);
152
153 canvas->translate(150, 0);
154 canvas->save();
155 canvas->rotate(30, 100, 100);
156 canvas->drawRect(r, paint);
157 canvas->restore();
158
159 paint.setAntiAlias(true);
160 canvas->translate(150, 0);
161 canvas->save();
162 canvas->rotate(30, 100, 100);
163 canvas->drawRect(r, paint);
164 canvas->restore();
165
166 canvas->restore();
167 canvas->translate(0, 150);
168 }
169 }
170
171 class ImageFilterMatrixWLocalMatrix : public skiagm::GM {
172 public:
173
174 // Start at 132 degrees, since that resulted in a skipped draw before the fix to
175 // SkLocalMatrixImageFilter's computeFastBounds() function.
ImageFilterMatrixWLocalMatrix()176 ImageFilterMatrixWLocalMatrix() : fDegrees(132.f) {}
177
178 protected:
getName() const179 SkString getName() const override { return SkString("imagefilter_matrix_localmatrix"); }
180
getISize()181 SkISize getISize() override { return SkISize::Make(512, 512); }
182
onAnimate(double nanos)183 bool onAnimate(double nanos) override {
184 // Animate the rotation angle to ensure the local matrix bounds modifications work
185 // for a variety of transformations.
186 fDegrees = TimeUtils::Scaled(1e-9f * nanos, 360.f);
187 return true;
188 }
189
onOnceBeforeDraw()190 void onOnceBeforeDraw() override {
191 fImage = ToolUtils::GetResourceAsImage("images/mandrill_256.png");
192 }
193
onDraw(SkCanvas * canvas)194 void onDraw(SkCanvas* canvas) override {
195 SkMatrix localMatrix;
196 localMatrix.preTranslate(128, 128);
197 localMatrix.preScale(2.0f, 2.0f);
198
199 // This matrix applies a rotate around the center of the image (prior to the simulated
200 // hi-dpi 2x device scale).
201 SkMatrix filterMatrix;
202 filterMatrix.setRotate(fDegrees, 64, 64);
203
204 sk_sp<SkImageFilter> filter =
205 SkImageFilters::MatrixTransform(filterMatrix,
206 SkSamplingOptions(SkFilterMode::kLinear), nullptr)
207 ->makeWithLocalMatrix(localMatrix);
208
209 SkPaint p;
210 p.setImageFilter(filter);
211 canvas->drawImage(fImage.get(), 128, 128, SkSamplingOptions(), &p);
212 }
213
214 private:
215 SkScalar fDegrees;
216 sk_sp<SkImage> fImage;
217 };
218
219 DEF_GM(return new ImageFilterMatrixWLocalMatrix();)
220
221 class ImageFilterComposedTransform : public skiagm::GM {
222 public:
223
224 // Start at 70 degrees since that highlighted the issue in skbug.com/10888
ImageFilterComposedTransform()225 ImageFilterComposedTransform() : fDegrees(70.f) {}
226
227 protected:
getName() const228 SkString getName() const override { return SkString("imagefilter_composed_transform"); }
229
getISize()230 SkISize getISize() override { return SkISize::Make(512, 512); }
231
onAnimate(double nanos)232 bool onAnimate(double nanos) override {
233 // Animate the rotation angle to test a variety of transformations
234 fDegrees = TimeUtils::Scaled(1e-9f * nanos, 360.f);
235 return true;
236 }
237
onOnceBeforeDraw()238 void onOnceBeforeDraw() override {
239 fImage = ToolUtils::GetResourceAsImage("images/mandrill_256.png");
240 }
241
onDraw(SkCanvas * canvas)242 void onDraw(SkCanvas* canvas) override {
243 SkMatrix matrix = SkMatrix::RotateDeg(fDegrees);
244 // All four quadrants should render the same
245 this->drawFilter(canvas, 0.f, 0.f, this->makeDirectFilter(matrix));
246 this->drawFilter(canvas, 256.f, 0.f, this->makeEarlyComposeFilter(matrix));
247 this->drawFilter(canvas, 0.f, 256.f, this->makeLateComposeFilter(matrix));
248 this->drawFilter(canvas, 256.f, 256.f, this->makeFullComposeFilter(matrix));
249 }
250
251 private:
252 SkScalar fDegrees;
253 sk_sp<SkImage> fImage;
254
drawFilter(SkCanvas * canvas,SkScalar tx,SkScalar ty,sk_sp<SkImageFilter> filter) const255 void drawFilter(SkCanvas* canvas, SkScalar tx, SkScalar ty, sk_sp<SkImageFilter> filter) const {
256 SkPaint p;
257 p.setImageFilter(std::move(filter));
258
259 canvas->save();
260 canvas->translate(tx, ty);
261 canvas->clipRect(SkRect::MakeWH(256, 256));
262 canvas->scale(0.5f, 0.5f);
263 canvas->translate(128, 128);
264 canvas->drawImage(fImage, 0, 0, SkSamplingOptions(SkFilterMode::kLinear), &p);
265 canvas->restore();
266 }
267
268 // offset(matrix(offset))
makeDirectFilter(const SkMatrix & matrix) const269 sk_sp<SkImageFilter> makeDirectFilter(const SkMatrix& matrix) const {
270 SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
271 sk_sp<SkImageFilter> filter = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
272 filter = SkImageFilters::MatrixTransform(matrix, SkSamplingOptions(SkFilterMode::kLinear),
273 std::move(filter));
274 filter = SkImageFilters::Offset(v.fX, v.fY, std::move(filter));
275 return filter;
276 }
277
278 // offset(compose(matrix, offset))
makeEarlyComposeFilter(const SkMatrix & matrix) const279 sk_sp<SkImageFilter> makeEarlyComposeFilter(const SkMatrix& matrix) const {
280 SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
281 sk_sp<SkImageFilter> offset = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
282 sk_sp<SkImageFilter> filter = SkImageFilters::MatrixTransform(
283 matrix, SkSamplingOptions(SkFilterMode::kLinear), nullptr);
284 filter = SkImageFilters::Compose(std::move(filter), std::move(offset));
285 filter = SkImageFilters::Offset(v.fX, v.fY, std::move(filter));
286 return filter;
287 }
288
289 // compose(offset, matrix(offset))
makeLateComposeFilter(const SkMatrix & matrix) const290 sk_sp<SkImageFilter> makeLateComposeFilter(const SkMatrix& matrix) const {
291 SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
292 sk_sp<SkImageFilter> filter = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
293 filter = SkImageFilters::MatrixTransform(matrix, SkSamplingOptions(SkFilterMode::kLinear),
294 std::move(filter));
295 sk_sp<SkImageFilter> offset = SkImageFilters::Offset(v.fX, v.fY, nullptr);
296 filter = SkImageFilters::Compose(std::move(offset), std::move(filter));
297 return filter;
298 }
299
300 // compose(offset, compose(matrix, offset))
makeFullComposeFilter(const SkMatrix & matrix) const301 sk_sp<SkImageFilter> makeFullComposeFilter(const SkMatrix& matrix) const {
302 SkPoint v = {fImage->width() / 2.f, fImage->height() / 2.f};
303 sk_sp<SkImageFilter> offset = SkImageFilters::Offset(-v.fX, -v.fY, nullptr);
304 sk_sp<SkImageFilter> filter = SkImageFilters::MatrixTransform(
305 matrix, SkSamplingOptions(SkFilterMode::kLinear), nullptr);
306 filter = SkImageFilters::Compose(std::move(filter), std::move(offset));
307 offset = SkImageFilters::Offset(v.fX, v.fY, nullptr);
308 filter = SkImageFilters::Compose(std::move(offset), std::move(filter));
309 return filter;
310 }
311 };
312
313 DEF_GM(return new ImageFilterComposedTransform();)
314
315 // Tests SkImageFilters::Image under tricky matrices (mirrors and perspective)
316 DEF_SIMPLE_GM(imagefilter_transformed_image, canvas, 256, 256) {
317 sk_sp<SkImage> image = ToolUtils::GetResourceAsImage("images/color_wheel.png");
318 sk_sp<SkImageFilter> imageFilter = SkImageFilters::Image(image, SkFilterMode::kLinear);
319
320 const SkRect imageRect = SkRect::MakeIWH(image->width(), image->height());
321
322 SkM44 m1 = SkM44::Translate(0.9f * image->width(), 0.1f * image->height()) *
323 SkM44::Scale(-.8f, .8f);
324
325 SkM44 m2 = SkM44::RectToRect({-1.f, -1.f, 1.f, 1.f}, imageRect) *
326 SkM44::Perspective(0.01f, 100.f, SK_ScalarPI / 3.f) *
327 SkM44::Translate(0.f, 0.f, -2.f) *
328 SkM44::Rotate({0.f, 1.f, 0.f}, SK_ScalarPI / 6.f) *
329 SkM44::RectToRect(imageRect, {-1.f, -1.f, 1.f, 1.f});
330
331 SkFont font = ToolUtils::DefaultPortableFont();
332 canvas->drawString("Columns should match", 5.f, 15.f, font, SkPaint());
333 canvas->translate(0.f, 10.f);
334
335 SkSamplingOptions sampling(SkFilterMode::kLinear);
336 for (auto m : {m1, m2}) {
337 canvas->save();
338 for (bool canvasTransform : {false, true}) {
339 canvas->save();
340 canvas->clipRect(imageRect);
341
342 sk_sp<SkImageFilter> finalFilter;
343 if (canvasTransform) {
344 canvas->concat(m);
345 finalFilter = imageFilter;
346 } else {
347 finalFilter = SkImageFilters::MatrixTransform(m.asM33(), sampling, imageFilter);
348 }
349
350 SkPaint paint;
351 paint.setImageFilter(std::move(finalFilter));
352 canvas->drawPaint(paint);
353
354 canvas->restore();
355 canvas->translate(image->width(), 0.f);
356 }
357 canvas->restore();
358
359 canvas->translate(0.f, image->height());
360 }
361 }
362