blob: f09bf2ad6c5a5dc8b24c5f454b90acadf775cffc [file] [log] [blame]
reed@google.com37f3ae02011-11-28 16:06:04 +00001/*
junov@chromium.org1cc8f6f2012-02-22 21:00:42 +00002 * Copyright 2012 Google Inc.
reed@google.com37f3ae02011-11-28 16:06:04 +00003 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
junov@chromium.org1cc8f6f2012-02-22 21:00:42 +00007
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkBitmap.h"
9#include "include/core/SkBlendMode.h"
10#include "include/core/SkCanvas.h"
11#include "include/core/SkClipOp.h"
12#include "include/core/SkColor.h"
Ben Wagnercbf6d302019-05-06 15:13:30 -040013#include "include/core/SkDocument.h"
14#include "include/core/SkFlattenable.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050015#include "include/core/SkImageFilter.h"
16#include "include/core/SkImageInfo.h"
17#include "include/core/SkMatrix.h"
18#include "include/core/SkPaint.h"
19#include "include/core/SkPath.h"
20#include "include/core/SkPictureRecorder.h"
21#include "include/core/SkPixmap.h"
22#include "include/core/SkPoint.h"
23#include "include/core/SkRect.h"
24#include "include/core/SkRefCnt.h"
25#include "include/core/SkRegion.h"
26#include "include/core/SkScalar.h"
27#include "include/core/SkShader.h"
28#include "include/core/SkSize.h"
29#include "include/core/SkStream.h"
30#include "include/core/SkString.h"
31#include "include/core/SkSurface.h"
32#include "include/core/SkTypes.h"
33#include "include/core/SkVertices.h"
34#include "include/docs/SkPDFDocument.h"
Michael Ludwig55edb502019-08-05 10:41:10 -040035#include "include/effects/SkImageFilters.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050036#include "include/private/SkMalloc.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050037#include "include/private/SkTemplates.h"
38#include "include/utils/SkNWayCanvas.h"
39#include "include/utils/SkPaintFilterCanvas.h"
Mike Klein3c685bf2020-09-15 13:10:52 -050040#include "src/core/SkBigPicture.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050041#include "src/core/SkClipOpPriv.h"
Michael Ludwig8ee6cf32019-08-02 09:57:04 -040042#include "src/core/SkImageFilter_Base.h"
Mike Klein3c685bf2020-09-15 13:10:52 -050043#include "src/core/SkRecord.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050044#include "src/core/SkSpecialImage.h"
45#include "src/utils/SkCanvasStack.h"
46#include "tests/Test.h"
reed@google.com37f3ae02011-11-28 16:06:04 +000047
Ben Wagnerb607a8f2018-03-12 13:46:21 -040048#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Mike Kleinc0bd9f92019-04-23 12:05:21 -050049#include "include/core/SkColorSpace.h"
50#include "include/private/SkColorData.h"
Ben Wagnerb607a8f2018-03-12 13:46:21 -040051#endif
52
53#include <memory>
54#include <utility>
55
Ben Wagnerb607a8f2018-03-12 13:46:21 -040056class SkReadBuffer;
Ben Wagnerb607a8f2018-03-12 13:46:21 -040057
Mike Klein3c685bf2020-09-15 13:10:52 -050058struct ClipRectVisitor {
59 skiatest::Reporter* r;
60
61 template <typename T>
62 SkRect operator()(const T&) {
63 REPORTER_ASSERT(r, false, "unexpected record");
64 return {1,1,0,0};
65 }
66
67 SkRect operator()(const SkRecords::ClipRect& op) {
68 return op.rect;
69 }
70};
71
72DEF_TEST(canvas_unsorted_clip, r) {
73 // Test that sorted and unsorted clip rects are forwarded
74 // to picture subclasses and/or devices sorted.
75 //
76 // We can't just test this with an SkCanvas on stack and
77 // SkCanvas::getLocalClipBounds(), as that only tests the raster device,
78 // which sorts these rects itself.
79 for (SkRect clip : {SkRect{0,0,5,5}, SkRect{5,5,0,0}}) {
80 SkPictureRecorder rec;
81 rec.beginRecording({0,0,10,10})
82 ->clipRect(clip);
83 sk_sp<SkPicture> pic = rec.finishRecordingAsPicture();
84
85 auto bp = (const SkBigPicture*)pic.get();
86 const SkRecord* record = bp->record();
87
88 REPORTER_ASSERT(r, record->count() == 1);
89 REPORTER_ASSERT(r, record->visit(0, ClipRectVisitor{r})
90 .isSorted());
91 }
92}
93
Mike Reed918e1442017-01-23 11:39:45 -050094DEF_TEST(canvas_clipbounds, reporter) {
95 SkCanvas canvas(10, 10);
Mike Reed6f4a9b22017-01-24 09:13:40 -050096 SkIRect irect, irect2;
97 SkRect rect, rect2;
Mike Reed918e1442017-01-23 11:39:45 -050098
99 irect = canvas.getDeviceClipBounds();
100 REPORTER_ASSERT(reporter, irect == SkIRect::MakeWH(10, 10));
Mike Reed6f4a9b22017-01-24 09:13:40 -0500101 REPORTER_ASSERT(reporter, canvas.getDeviceClipBounds(&irect2));
102 REPORTER_ASSERT(reporter, irect == irect2);
103
Mike Reed918e1442017-01-23 11:39:45 -0500104 // local bounds are always too big today -- can we trim them?
105 rect = canvas.getLocalClipBounds();
106 REPORTER_ASSERT(reporter, rect.contains(SkRect::MakeWH(10, 10)));
Mike Reed6f4a9b22017-01-24 09:13:40 -0500107 REPORTER_ASSERT(reporter, canvas.getLocalClipBounds(&rect2));
108 REPORTER_ASSERT(reporter, rect == rect2);
Mike Reed918e1442017-01-23 11:39:45 -0500109
110 canvas.clipRect(SkRect::MakeEmpty());
111
112 irect = canvas.getDeviceClipBounds();
113 REPORTER_ASSERT(reporter, irect == SkIRect::MakeEmpty());
Mike Reed6f4a9b22017-01-24 09:13:40 -0500114 REPORTER_ASSERT(reporter, !canvas.getDeviceClipBounds(&irect2));
115 REPORTER_ASSERT(reporter, irect == irect2);
116
Mike Reed918e1442017-01-23 11:39:45 -0500117 rect = canvas.getLocalClipBounds();
118 REPORTER_ASSERT(reporter, rect == SkRect::MakeEmpty());
Mike Reed6f4a9b22017-01-24 09:13:40 -0500119 REPORTER_ASSERT(reporter, !canvas.getLocalClipBounds(&rect2));
120 REPORTER_ASSERT(reporter, rect == rect2);
Mike Reed566e53c2017-03-10 10:49:45 -0500121
122 // Test for wacky sizes that we (historically) have guarded against
123 {
124 SkCanvas c(-10, -20);
125 REPORTER_ASSERT(reporter, c.getBaseLayerSize() == SkISize::MakeEmpty());
Mike Reeddc9f0db2017-03-10 13:54:16 -0500126
127 SkPictureRecorder().beginRecording({ 5, 5, 4, 4 });
Mike Reed566e53c2017-03-10 10:49:45 -0500128 }
Mike Reed918e1442017-01-23 11:39:45 -0500129}
130
Robert Phillips09dfc472017-09-13 15:25:47 -0400131// Will call proc with multiple styles of canvas (recording, raster, pdf)
Mike Reed02be3c12017-03-23 12:34:15 -0400132template <typename F> static void multi_canvas_driver(int w, int h, F proc) {
133 proc(SkPictureRecorder().beginRecording(SkRect::MakeIWH(w, h)));
134
135 SkNullWStream stream;
Hal Canary23564b92018-09-07 14:33:14 -0400136 if (auto doc = SkPDF::MakeDocument(&stream)) {
Hal Canary4125b612018-03-20 14:17:00 -0400137 proc(doc->beginPage(SkIntToScalar(w), SkIntToScalar(h)));
138 }
Mike Reed02be3c12017-03-23 12:34:15 -0400139
140 proc(SkSurface::MakeRasterN32Premul(w, h, nullptr)->getCanvas());
141}
142
Mike Reed2a65cc02017-03-22 10:01:53 -0400143const SkIRect gBaseRestrictedR = { 0, 0, 10, 10 };
144
145static void test_restriction(skiatest::Reporter* reporter, SkCanvas* canvas) {
146 REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == gBaseRestrictedR);
147
148 const SkIRect restrictionR = { 2, 2, 8, 8 };
149 canvas->androidFramework_setDeviceClipRestriction(restrictionR);
150 REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == restrictionR);
151
152 const SkIRect clipR = { 4, 4, 6, 6 };
153 canvas->clipRect(SkRect::Make(clipR), SkClipOp::kIntersect);
154 REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == clipR);
155
Mike Reed14113bc2017-05-10 14:13:20 -0400156#ifdef SK_SUPPORT_DEPRECATED_CLIPOPS
Mike Reed2a65cc02017-03-22 10:01:53 -0400157 // now test that expanding clipops can't exceed the restriction
158 const SkClipOp expanders[] = {
159 SkClipOp::kUnion_deprecated,
160 SkClipOp::kXOR_deprecated,
161 SkClipOp::kReverseDifference_deprecated,
162 SkClipOp::kReplace_deprecated,
163 };
164
165 const SkRect expandR = { 0, 0, 5, 9 };
166 SkASSERT(!SkRect::Make(restrictionR).contains(expandR));
167
168 for (SkClipOp op : expanders) {
169 canvas->save();
170 canvas->clipRect(expandR, op);
171 REPORTER_ASSERT(reporter, gBaseRestrictedR.contains(canvas->getDeviceClipBounds()));
172 canvas->restore();
173 }
Mike Reed14113bc2017-05-10 14:13:20 -0400174#endif
Mike Reed2a65cc02017-03-22 10:01:53 -0400175}
176
177/**
178 * Clip restriction logic exists in the canvas itself, and in various kinds of devices.
179 *
180 * This test explicitly tries to exercise that variety:
181 * - picture : empty device but exercises canvas itself
182 * - pdf : uses SkClipStack in its device (as does SVG and GPU)
183 * - raster : uses SkRasterClip in its device
184 */
185DEF_TEST(canvas_clip_restriction, reporter) {
Mike Reed02be3c12017-03-23 12:34:15 -0400186 multi_canvas_driver(gBaseRestrictedR.width(), gBaseRestrictedR.height(),
187 [reporter](SkCanvas* canvas) { test_restriction(reporter, canvas); });
188}
Mike Reed2a65cc02017-03-22 10:01:53 -0400189
Mike Reed02be3c12017-03-23 12:34:15 -0400190DEF_TEST(canvas_empty_clip, reporter) {
191 multi_canvas_driver(50, 50, [reporter](SkCanvas* canvas) {
192 canvas->save();
193 canvas->clipRect({0, 0, 20, 40 });
194 REPORTER_ASSERT(reporter, !canvas->isClipEmpty());
195 canvas->clipRect({30, 0, 50, 40 });
196 REPORTER_ASSERT(reporter, canvas->isClipEmpty());
197 });
Mike Reed2a65cc02017-03-22 10:01:53 -0400198}
199
Ben Wagnercbf6d302019-05-06 15:13:30 -0400200DEF_TEST(CanvasNewRasterTest, reporter) {
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000201 SkImageInfo info = SkImageInfo::MakeN32Premul(10, 10);
reed3054be12014-12-10 07:24:28 -0800202 const size_t minRowBytes = info.minRowBytes();
Mike Reedf0ffb892017-10-03 14:47:21 -0400203 const size_t size = info.computeByteSize(minRowBytes);
scroggo565901d2015-12-10 10:44:13 -0800204 SkAutoTMalloc<SkPMColor> storage(size);
205 SkPMColor* baseAddr = storage.get();
reed3054be12014-12-10 07:24:28 -0800206 sk_bzero(baseAddr, size);
207
Mike Reed5df49342016-11-12 08:06:55 -0600208 std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes);
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000209 REPORTER_ASSERT(reporter, canvas);
210
reed6ceeebd2016-03-09 14:26:26 -0800211 SkPixmap pmap;
212 const SkPMColor* addr = canvas->peekPixels(&pmap) ? pmap.addr32() : nullptr;
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000213 REPORTER_ASSERT(reporter, addr);
reed6ceeebd2016-03-09 14:26:26 -0800214 REPORTER_ASSERT(reporter, info == pmap.info());
215 REPORTER_ASSERT(reporter, minRowBytes == pmap.rowBytes());
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000216 for (int y = 0; y < info.height(); ++y) {
217 for (int x = 0; x < info.width(); ++x) {
218 REPORTER_ASSERT(reporter, 0 == addr[x]);
219 }
reed6ceeebd2016-03-09 14:26:26 -0800220 addr = (const SkPMColor*)((const char*)addr + pmap.rowBytes());
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000221 }
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000222
Leon Scroggins III3eedc972020-01-22 10:44:00 -0500223 // unaligned rowBytes
224 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr,
225 minRowBytes + 1));
226
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000227 // now try a deliberately bad info
reede5ea5002014-09-03 11:54:58 -0700228 info = info.makeWH(-1, info.height());
Mike Reed5df49342016-11-12 08:06:55 -0600229 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes));
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000230
231 // too big
reede5ea5002014-09-03 11:54:58 -0700232 info = info.makeWH(1 << 30, 1 << 30);
Mike Reed5df49342016-11-12 08:06:55 -0600233 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes));
skia.committer@gmail.com0e530752014-02-28 03:02:05 +0000234
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000235 // not a valid pixel type
reede5ea5002014-09-03 11:54:58 -0700236 info = SkImageInfo::Make(10, 10, kUnknown_SkColorType, info.alphaType());
Mike Reed5df49342016-11-12 08:06:55 -0600237 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes));
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000238
Brian Osman431ac562018-10-10 13:20:38 -0400239 // We should not succeed with a zero-sized valid info
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000240 info = SkImageInfo::MakeN32Premul(0, 0);
Mike Reed5df49342016-11-12 08:06:55 -0600241 canvas = SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes);
Brian Osman431ac562018-10-10 13:20:38 -0400242 REPORTER_ASSERT(reporter, nullptr == canvas);
commit-bot@chromium.org3107b6a2014-02-27 20:32:51 +0000243}
244
Hal Canary5f2807b2019-05-06 16:51:06 -0400245static SkPath make_path_from_rect(SkRect r) {
246 SkPath path;
247 path.addRect(r);
248 return path;
249}
reed@google.com37f3ae02011-11-28 16:06:04 +0000250
Hal Canary5f2807b2019-05-06 16:51:06 -0400251static SkRegion make_region_from_irect(SkIRect r) {
252 SkRegion region;
253 region.setRect(r);
254 return region;
255}
Ben Wagnercbf6d302019-05-06 15:13:30 -0400256
Hal Canary5f2807b2019-05-06 16:51:06 -0400257static SkBitmap make_n32_bitmap(int w, int h, SkColor c = SK_ColorWHITE) {
258 SkBitmap bm;
259 bm.allocN32Pixels(w, h);
260 bm.eraseColor(c);
261 return bm;
262}
Ben Wagnercbf6d302019-05-06 15:13:30 -0400263
Hal Canary5f2807b2019-05-06 16:51:06 -0400264// Constants used by test steps
265static constexpr SkRect kRect = {0, 0, 2, 1};
266static constexpr SkColor kColor = 0x01020304;
267static constexpr int kWidth = 2;
268static constexpr int kHeight = 2;
Ben Wagnercbf6d302019-05-06 15:13:30 -0400269
Hal Canary5f2807b2019-05-06 16:51:06 -0400270using CanvasTest = void (*)(SkCanvas*, skiatest::Reporter*);
Ben Wagnercbf6d302019-05-06 15:13:30 -0400271
Hal Canary5f2807b2019-05-06 16:51:06 -0400272static CanvasTest kCanvasTests[] = {
273 [](SkCanvas* c, skiatest::Reporter* r) {
274 c->translate(SkIntToScalar(1), SkIntToScalar(2));
275 },
276 [](SkCanvas* c, skiatest::Reporter* r) {
277 c->scale(SkIntToScalar(1), SkIntToScalar(2));
278 },
279 [](SkCanvas* c, skiatest::Reporter* r) {
280 c->rotate(SkIntToScalar(1));
281 },
282 [](SkCanvas* c, skiatest::Reporter* r) {
283 c->skew(SkIntToScalar(1), SkIntToScalar(2));
284 },
285 [](SkCanvas* c, skiatest::Reporter* r) {
Mike Reed1f607332020-05-21 12:11:27 -0400286 c->concat(SkMatrix::Scale(2, 3));
Hal Canary5f2807b2019-05-06 16:51:06 -0400287 },
288 [](SkCanvas* c, skiatest::Reporter* r) {
Mike Reed1f607332020-05-21 12:11:27 -0400289 c->setMatrix(SkMatrix::Scale(2, 3));
Hal Canary5f2807b2019-05-06 16:51:06 -0400290 },
291 [](SkCanvas* c, skiatest::Reporter* r) {
292 c->clipRect(kRect);
293 },
294 [](SkCanvas* c, skiatest::Reporter* r) {
295 c->clipPath(make_path_from_rect(SkRect{0, 0, 2, 1}));
296 },
297 [](SkCanvas* c, skiatest::Reporter* r) {
Michael Ludwige4b79692020-09-16 13:55:05 -0400298 c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1}));
Hal Canary5f2807b2019-05-06 16:51:06 -0400299 },
300 [](SkCanvas* c, skiatest::Reporter* r) {
301 c->clear(kColor);
302 },
303 [](SkCanvas* c, skiatest::Reporter* r) {
304 int saveCount = c->getSaveCount();
305 c->save();
306 c->translate(SkIntToScalar(1), SkIntToScalar(2));
307 c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1}));
308 c->restore();
309 REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
310 REPORTER_ASSERT(r, c->getTotalMatrix().isIdentity());
311 //REPORTER_ASSERT(reporter, c->getTotalClip() != kTestRegion);
312 },
313 [](SkCanvas* c, skiatest::Reporter* r) {
314 int saveCount = c->getSaveCount();
315 c->saveLayer(nullptr, nullptr);
316 c->restore();
317 REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
318 },
319 [](SkCanvas* c, skiatest::Reporter* r) {
320 int saveCount = c->getSaveCount();
321 c->saveLayer(&kRect, nullptr);
322 c->restore();
323 REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
324 },
325 [](SkCanvas* c, skiatest::Reporter* r) {
326 int saveCount = c->getSaveCount();
327 SkPaint p;
328 c->saveLayer(nullptr, &p);
329 c->restore();
330 REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
331 },
332 [](SkCanvas* c, skiatest::Reporter* r) {
333 // This test exercises a functionality in SkPicture that leads to the
334 // recording of restore offset placeholders. This test will trigger an
335 // assertion at playback time if the placeholders are not properly
336 // filled when the recording ends.
337 c->clipRect(kRect);
338 c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1}));
339 },
340 [](SkCanvas* c, skiatest::Reporter* r) {
341 // exercise fix for http://code.google.com/p/skia/issues/detail?id=560
342 // ('SkPathStroker::lineTo() fails for line with length SK_ScalarNearlyZero')
343 SkPaint paint;
344 paint.setStrokeWidth(SkIntToScalar(1));
345 paint.setStyle(SkPaint::kStroke_Style);
346 SkPath path;
347 path.moveTo(SkPoint{ 0, 0 });
348 path.lineTo(SkPoint{ 0, SK_ScalarNearlyZero });
349 path.lineTo(SkPoint{ SkIntToScalar(1), 0 });
350 path.lineTo(SkPoint{ SkIntToScalar(1), SK_ScalarNearlyZero/2 });
351 // test nearly zero length path
352 c->drawPath(path, paint);
353 },
354 [](SkCanvas* c, skiatest::Reporter* r) {
355 SkPictureRecorder recorder;
Mike Reed457c6dd2020-08-21 13:42:01 -0400356 SkCanvas* testCanvas = recorder.beginRecording(SkIntToScalar(kWidth),
357 SkIntToScalar(kHeight));
Hal Canary5f2807b2019-05-06 16:51:06 -0400358 testCanvas->scale(SkIntToScalar(2), SkIntToScalar(1));
359 testCanvas->clipRect(kRect);
360 testCanvas->drawRect(kRect, SkPaint());
361 c->drawPicture(recorder.finishRecordingAsPicture());
362 },
363 [](SkCanvas* c, skiatest::Reporter* r) {
364 int baseSaveCount = c->getSaveCount();
365 int n = c->save();
366 REPORTER_ASSERT(r, baseSaveCount == n);
367 REPORTER_ASSERT(r, baseSaveCount + 1 == c->getSaveCount());
368 c->save();
369 c->save();
370 REPORTER_ASSERT(r, baseSaveCount + 3 == c->getSaveCount());
371 c->restoreToCount(baseSaveCount + 1);
372 REPORTER_ASSERT(r, baseSaveCount + 1 == c->getSaveCount());
Ben Wagnercbf6d302019-05-06 15:13:30 -0400373
Hal Canary5f2807b2019-05-06 16:51:06 -0400374 // should this pin to 1, or be a no-op, or crash?
375 c->restoreToCount(0);
376 REPORTER_ASSERT(r, 1 == c->getSaveCount());
377 },
378 [](SkCanvas* c, skiatest::Reporter* r) {
379 // This test step challenges the TestDeferredCanvasStateConsistency
380 // test cases because the opaque paint can trigger an optimization
381 // that discards previously recorded commands. The challenge is to maintain
382 // correct clip and matrix stack state.
383 c->resetMatrix();
384 c->rotate(SkIntToScalar(30));
385 c->save();
386 c->translate(SkIntToScalar(2), SkIntToScalar(1));
387 c->save();
388 c->scale(SkIntToScalar(3), SkIntToScalar(3));
389 SkPaint paint;
390 paint.setColor(0xFFFFFFFF);
391 c->drawPaint(paint);
392 c->restore();
393 c->restore();
394 },
395 [](SkCanvas* c, skiatest::Reporter* r) {
396 // This test step challenges the TestDeferredCanvasStateConsistency
397 // test case because the canvas flush on a deferred canvas will
398 // reset the recording session. The challenge is to maintain correct
399 // clip and matrix stack state on the playback canvas.
400 c->resetMatrix();
401 c->rotate(SkIntToScalar(30));
402 c->save();
403 c->translate(SkIntToScalar(2), SkIntToScalar(1));
404 c->save();
405 c->scale(SkIntToScalar(3), SkIntToScalar(3));
406 c->drawRect(kRect, SkPaint());
407 c->flush();
408 c->restore();
409 c->restore();
410 },
411 [](SkCanvas* c, skiatest::Reporter* r) {
412 SkPoint pts[4];
413 pts[0].set(0, 0);
414 pts[1].set(SkIntToScalar(kWidth), 0);
415 pts[2].set(SkIntToScalar(kWidth), SkIntToScalar(kHeight));
416 pts[3].set(0, SkIntToScalar(kHeight));
417 SkPaint paint;
418 SkBitmap bitmap(make_n32_bitmap(kWidth, kHeight, 0x05060708));
Mike Reed82abece2020-12-12 09:51:11 -0500419 paint.setShader(bitmap.makeShader(SkSamplingOptions()));
Hal Canary5f2807b2019-05-06 16:51:06 -0400420 c->drawVertices(
421 SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode, 4, pts, pts, nullptr),
422 SkBlendMode::kModulate, paint);
423 }
424};
Ben Wagnercbf6d302019-05-06 15:13:30 -0400425
Hal Canary5f2807b2019-05-06 16:51:06 -0400426DEF_TEST(Canvas_bitmap, reporter) {
427 for (const CanvasTest& test : kCanvasTests) {
428 SkBitmap referenceStore = make_n32_bitmap(kWidth, kHeight);
Ben Wagnercbf6d302019-05-06 15:13:30 -0400429 SkCanvas referenceCanvas(referenceStore);
Hal Canary5f2807b2019-05-06 16:51:06 -0400430 test(&referenceCanvas, reporter);
431 }
432}
Ben Wagnercbf6d302019-05-06 15:13:30 -0400433
Hal Canary5f2807b2019-05-06 16:51:06 -0400434DEF_TEST(Canvas_pdf, reporter) {
435 for (const CanvasTest& test : kCanvasTests) {
436 SkNullWStream outStream;
437 if (auto doc = SkPDF::MakeDocument(&outStream)) {
438 SkCanvas* canvas = doc->beginPage(SkIntToScalar(kWidth),
439 SkIntToScalar(kHeight));
440 REPORTER_ASSERT(reporter, canvas);
441 test(canvas, reporter);
edisonn@google.com77909122012-10-18 15:58:23 +0000442 }
junov@chromium.org1cc8f6f2012-02-22 21:00:42 +0000443 }
reed@google.com37f3ae02011-11-28 16:06:04 +0000444}
reedf0090cb2014-11-26 08:55:51 -0800445
446DEF_TEST(Canvas_SaveState, reporter) {
447 SkCanvas canvas(10, 10);
448 REPORTER_ASSERT(reporter, 1 == canvas.getSaveCount());
449
450 int n = canvas.save();
451 REPORTER_ASSERT(reporter, 1 == n);
452 REPORTER_ASSERT(reporter, 2 == canvas.getSaveCount());
453
halcanary96fcdcc2015-08-27 07:41:13 -0700454 n = canvas.saveLayer(nullptr, nullptr);
reedf0090cb2014-11-26 08:55:51 -0800455 REPORTER_ASSERT(reporter, 2 == n);
456 REPORTER_ASSERT(reporter, 3 == canvas.getSaveCount());
halcanary9d524f22016-03-29 09:03:52 -0700457
reedf0090cb2014-11-26 08:55:51 -0800458 canvas.restore();
459 REPORTER_ASSERT(reporter, 2 == canvas.getSaveCount());
460 canvas.restore();
461 REPORTER_ASSERT(reporter, 1 == canvas.getSaveCount());
462}
reedc1b11f12015-03-13 08:48:26 -0700463
464DEF_TEST(Canvas_ClipEmptyPath, reporter) {
465 SkCanvas canvas(10, 10);
466 canvas.save();
467 SkPath path;
468 canvas.clipPath(path);
469 canvas.restore();
470 canvas.save();
471 path.moveTo(5, 5);
472 canvas.clipPath(path);
473 canvas.restore();
474 canvas.save();
475 path.moveTo(7, 7);
476 canvas.clipPath(path); // should not assert here
477 canvas.restore();
478}
fmalitaf433bb22015-08-17 08:05:13 -0700479
480namespace {
481
482class MockFilterCanvas : public SkPaintFilterCanvas {
483public:
484 MockFilterCanvas(SkCanvas* canvas) : INHERITED(canvas) { }
485
486protected:
Ben Wagnerf55fa0d2018-08-27 18:11:57 -0400487 bool onFilter(SkPaint&) const override { return true; }
fmalitaf433bb22015-08-17 08:05:13 -0700488
489private:
John Stiles7571f9e2020-09-02 22:42:33 -0400490 using INHERITED = SkPaintFilterCanvas;
fmalitaf433bb22015-08-17 08:05:13 -0700491};
492
493} // anonymous namespace
494
495// SkPaintFilterCanvas should inherit the initial target canvas state.
496DEF_TEST(PaintFilterCanvas_ConsistentState, reporter) {
497 SkCanvas canvas(100, 100);
498 canvas.clipRect(SkRect::MakeXYWH(12.7f, 12.7f, 75, 75));
499 canvas.scale(0.5f, 0.75f);
500
fmalitaf433bb22015-08-17 08:05:13 -0700501 MockFilterCanvas filterCanvas(&canvas);
502 REPORTER_ASSERT(reporter, canvas.getTotalMatrix() == filterCanvas.getTotalMatrix());
Mike Reed918e1442017-01-23 11:39:45 -0500503 REPORTER_ASSERT(reporter, canvas.getLocalClipBounds() == filterCanvas.getLocalClipBounds());
fmalitaf433bb22015-08-17 08:05:13 -0700504
505 filterCanvas.clipRect(SkRect::MakeXYWH(30.5f, 30.7f, 100, 100));
506 filterCanvas.scale(0.75f, 0.5f);
507 REPORTER_ASSERT(reporter, canvas.getTotalMatrix() == filterCanvas.getTotalMatrix());
Mike Reed918e1442017-01-23 11:39:45 -0500508 REPORTER_ASSERT(reporter, filterCanvas.getLocalClipBounds().contains(canvas.getLocalClipBounds()));
fmalitaf433bb22015-08-17 08:05:13 -0700509}
reedbabc3de2016-07-08 08:43:27 -0700510
511///////////////////////////////////////////////////////////////////////////////////////////////////
512
Ben Wagnercbf6d302019-05-06 15:13:30 -0400513namespace {
514
Mike Reed584ca892016-11-15 11:52:55 -0500515// Subclass that takes a bool*, which it updates in its construct (true) and destructor (false)
516// to allow the caller to know how long the object is alive.
517class LifeLineCanvas : public SkCanvas {
518 bool* fLifeLine;
519public:
520 LifeLineCanvas(int w, int h, bool* lifeline) : SkCanvas(w, h), fLifeLine(lifeline) {
521 *fLifeLine = true;
522 }
Brian Salomond0072812020-07-21 17:03:56 -0400523 ~LifeLineCanvas() override {
Mike Reed584ca892016-11-15 11:52:55 -0500524 *fLifeLine = false;
525 }
526};
527
John Stilesa6841be2020-08-06 14:11:56 -0400528} // namespace
Ben Wagnercbf6d302019-05-06 15:13:30 -0400529
Mike Reed584ca892016-11-15 11:52:55 -0500530// Check that NWayCanvas does NOT try to manage the lifetime of its sub-canvases
531DEF_TEST(NWayCanvas, r) {
532 const int w = 10;
533 const int h = 10;
534 bool life[2];
535 {
536 LifeLineCanvas c0(w, h, &life[0]);
537 REPORTER_ASSERT(r, life[0]);
538 }
539 REPORTER_ASSERT(r, !life[0]);
540
541
542 std::unique_ptr<SkCanvas> c0 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[0]));
543 std::unique_ptr<SkCanvas> c1 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[1]));
544 REPORTER_ASSERT(r, life[0]);
545 REPORTER_ASSERT(r, life[1]);
546
547 {
548 SkNWayCanvas nway(w, h);
549 nway.addCanvas(c0.get());
550 nway.addCanvas(c1.get());
551 REPORTER_ASSERT(r, life[0]);
552 REPORTER_ASSERT(r, life[1]);
553 }
554 // Now assert that the death of the nway has NOT also killed the sub-canvases
555 REPORTER_ASSERT(r, life[0]);
556 REPORTER_ASSERT(r, life[1]);
557}
558
559// Check that CanvasStack DOES manage the lifetime of its sub-canvases
560DEF_TEST(CanvasStack, r) {
561 const int w = 10;
562 const int h = 10;
563 bool life[2];
564 std::unique_ptr<SkCanvas> c0 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[0]));
565 std::unique_ptr<SkCanvas> c1 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[1]));
566 REPORTER_ASSERT(r, life[0]);
567 REPORTER_ASSERT(r, life[1]);
568
569 {
570 SkCanvasStack stack(w, h);
571 stack.pushCanvas(std::move(c0), {0,0});
572 stack.pushCanvas(std::move(c1), {0,0});
573 REPORTER_ASSERT(r, life[0]);
574 REPORTER_ASSERT(r, life[1]);
575 }
576 // Now assert that the death of the canvasstack has also killed the sub-canvases
577 REPORTER_ASSERT(r, !life[0]);
578 REPORTER_ASSERT(r, !life[1]);
579}
Mike Reedcd667b62017-03-02 15:21:11 -0500580
Mike Reed3b4c22d2017-03-02 20:07:46 -0500581static void test_cliptype(SkCanvas* canvas, skiatest::Reporter* r) {
Mike Reedcd667b62017-03-02 15:21:11 -0500582 REPORTER_ASSERT(r, !canvas->isClipEmpty());
583 REPORTER_ASSERT(r, canvas->isClipRect());
584
585 canvas->save();
586 canvas->clipRect({0, 0, 0, 0});
587 REPORTER_ASSERT(r, canvas->isClipEmpty());
588 REPORTER_ASSERT(r, !canvas->isClipRect());
589 canvas->restore();
590
591 canvas->save();
592 canvas->clipRect({2, 2, 6, 6});
593 REPORTER_ASSERT(r, !canvas->isClipEmpty());
594 REPORTER_ASSERT(r, canvas->isClipRect());
595 canvas->restore();
596
597 canvas->save();
598 canvas->clipRect({2, 2, 6, 6}, SkClipOp::kDifference); // punch a hole in the clip
599 REPORTER_ASSERT(r, !canvas->isClipEmpty());
600 REPORTER_ASSERT(r, !canvas->isClipRect());
601 canvas->restore();
602
603 REPORTER_ASSERT(r, !canvas->isClipEmpty());
604 REPORTER_ASSERT(r, canvas->isClipRect());
605}
Mike Reed3b4c22d2017-03-02 20:07:46 -0500606
607DEF_TEST(CanvasClipType, r) {
608 // test rasterclip backend
609 test_cliptype(SkSurface::MakeRasterN32Premul(10, 10)->getCanvas(), r);
610
611 // test clipstack backend
612 SkDynamicMemoryWStream stream;
Hal Canary23564b92018-09-07 14:33:14 -0400613 if (auto doc = SkPDF::MakeDocument(&stream)) {
Hal Canary4125b612018-03-20 14:17:00 -0400614 test_cliptype(doc->beginPage(100, 100), r);
615 }
Mike Reed3b4c22d2017-03-02 20:07:46 -0500616}
Matt Sarett31f99ce2017-04-11 08:46:01 -0400617
618#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
619DEF_TEST(Canvas_LegacyColorBehavior, r) {
Brian Osman82ebe042019-01-04 17:03:00 -0500620 sk_sp<SkColorSpace> cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
621 SkNamedGamut::kAdobeRGB);
Matt Sarett31f99ce2017-04-11 08:46:01 -0400622
623 // Make a Adobe RGB bitmap.
624 SkBitmap bitmap;
625 bitmap.allocPixels(SkImageInfo::MakeN32(1, 1, kOpaque_SkAlphaType, cs));
626 bitmap.eraseColor(0xFF000000);
627
628 // Wrap it in a legacy canvas. Test that the canvas behaves like a legacy canvas.
629 SkCanvas canvas(bitmap, SkCanvas::ColorBehavior::kLegacy);
630 REPORTER_ASSERT(r, !canvas.imageInfo().colorSpace());
631 SkPaint p;
632 p.setColor(SK_ColorRED);
633 canvas.drawIRect(SkIRect::MakeWH(1, 1), p);
634 REPORTER_ASSERT(r, SK_ColorRED == SkSwizzle_BGRA_to_PMColor(*bitmap.getAddr32(0, 0)));
635}
636#endif
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800637
638namespace {
639
Michael Ludwig8ee6cf32019-08-02 09:57:04 -0400640class ZeroBoundsImageFilter : public SkImageFilter_Base {
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800641public:
642 static sk_sp<SkImageFilter> Make() { return sk_sp<SkImageFilter>(new ZeroBoundsImageFilter); }
643
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800644protected:
Michael Ludwige30a4852019-08-14 14:35:42 -0400645 sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint*) const override {
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800646 return nullptr;
647 }
Robert Phillips12078432018-05-17 11:17:39 -0400648 SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&,
649 MapDirection, const SkIRect* inputRect) const override {
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800650 return SkIRect::MakeEmpty();
651 }
652
653private:
Mike Klein4fee3232018-10-18 17:27:16 -0400654 SK_FLATTENABLE_HOOKS(ZeroBoundsImageFilter)
655
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800656 ZeroBoundsImageFilter() : INHERITED(nullptr, 0, nullptr) {}
657
John Stiles7571f9e2020-09-02 22:42:33 -0400658 using INHERITED = SkImageFilter_Base;
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800659};
660
661sk_sp<SkFlattenable> ZeroBoundsImageFilter::CreateProc(SkReadBuffer& buffer) {
662 SkDEBUGFAIL("Should never get here");
663 return nullptr;
664}
665
Xianzhu Wang3b428cb2018-01-22 09:51:13 -0800666} // anonymous namespace
667
668DEF_TEST(Canvas_SaveLayerWithNullBoundsAndZeroBoundsImageFilter, r) {
669 SkCanvas canvas(10, 10);
670 SkPaint p;
671 p.setImageFilter(ZeroBoundsImageFilter::Make());
672 // This should not fail any assert.
673 canvas.saveLayer(nullptr, &p);
674 REPORTER_ASSERT(r, canvas.getDeviceClipBounds().isEmpty());
675 canvas.restore();
676}
Mike Reed490aa592018-04-13 15:34:16 -0400677
Mike Reed490aa592018-04-13 15:34:16 -0400678// Test that we don't crash/assert when building a canvas with degenerate coordintes
679// (esp. big ones, that might invoke tiling).
680DEF_TEST(Canvas_degenerate_dimension, reporter) {
681 // Need a paint that will sneak us past the quickReject in SkCanvas, so we can test the
682 // raster code further downstream.
683 SkPaint paint;
Michael Ludwig7d0f8532020-10-07 15:27:20 -0400684 paint.setImageFilter(SkImageFilters::Shader(SkShaders::Color(SK_ColorBLACK), nullptr));
Mike Reed490aa592018-04-13 15:34:16 -0400685 REPORTER_ASSERT(reporter, !paint.canComputeFastBounds());
686
687 const int big = 100 * 1024; // big enough to definitely trigger tiling
688 const SkISize sizes[] {SkISize{0, big}, {big, 0}, {0, 0}};
689 for (SkISize size : sizes) {
690 SkBitmap bm;
691 bm.setInfo(SkImageInfo::MakeN32Premul(size.width(), size.height()));
692 SkCanvas canvas(bm);
693 canvas.drawRect({0, 0, 100, 90*1024}, paint);
694 }
695}
696
Robert Phillips3d0e8502018-04-20 10:27:27 -0400697DEF_TEST(Canvas_ClippedOutImageFilter, reporter) {
698 SkCanvas canvas(100, 100);
699
700 SkPaint p;
701 p.setColor(SK_ColorGREEN);
Michael Ludwig55edb502019-08-05 10:41:10 -0400702 p.setImageFilter(SkImageFilters::Blur(3.0f, 3.0f, nullptr, nullptr));
Robert Phillips3d0e8502018-04-20 10:27:27 -0400703
704 SkRect blurredRect = SkRect::MakeXYWH(60, 10, 30, 30);
705
706 SkMatrix invM;
707 invM.setRotate(-45);
708 invM.mapRect(&blurredRect);
709
710 const SkRect clipRect = SkRect::MakeXYWH(0, 50, 50, 50);
711
712 canvas.clipRect(clipRect);
713
714 canvas.rotate(45);
715 const SkMatrix preCTM = canvas.getTotalMatrix();
716 canvas.drawRect(blurredRect, p);
717 const SkMatrix postCTM = canvas.getTotalMatrix();
718 REPORTER_ASSERT(reporter, preCTM == postCTM);
719}
720
Mike Reed7fe6ee32020-04-09 12:35:09 -0400721DEF_TEST(canvas_markctm, reporter) {
722 SkCanvas canvas(10, 10);
723
724 SkM44 m;
Brian Osman548de742020-04-24 12:02:25 -0400725 const char* id_a = "a";
726 const char* id_b = "b";
Mike Reed7fe6ee32020-04-09 12:35:09 -0400727
728 REPORTER_ASSERT(reporter, !canvas.findMarkedCTM(id_a, nullptr));
729 REPORTER_ASSERT(reporter, !canvas.findMarkedCTM(id_b, nullptr));
730
731 // remember the starting state
732 SkM44 b = canvas.getLocalToDevice();
733 canvas.markCTM(id_b);
734
735 // test add
736 canvas.concat(SkM44::Scale(2, 4, 6));
737 SkM44 a = canvas.getLocalToDevice();
738 canvas.markCTM(id_a);
739 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a);
740
741 // test replace
742 canvas.translate(1, 2);
743 SkM44 a1 = canvas.getLocalToDevice();
744 SkASSERT(a != a1);
745 canvas.markCTM(id_a);
746 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1);
747
748 // test nested
749 canvas.save();
750 // no change
751 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_b, &m) && m == b);
752 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1);
753 canvas.translate(2, 3);
754 SkM44 a2 = canvas.getLocalToDevice();
755 SkASSERT(a2 != a1);
756 canvas.markCTM(id_a);
757 // found the new one
758 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a2);
759 canvas.restore();
760 // found the previous one
761 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1);
762}