xref: /third_party/skia/tests/CanvasTest.cpp (revision cb93a386)
1/*
2 * Copyright 2012 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 "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"
13#include "include/core/SkDocument.h"
14#include "include/core/SkFlattenable.h"
15#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"
35#include "include/effects/SkImageFilters.h"
36#include "include/private/SkMalloc.h"
37#include "include/private/SkTemplates.h"
38#include "include/utils/SkNWayCanvas.h"
39#include "include/utils/SkPaintFilterCanvas.h"
40#include "src/core/SkBigPicture.h"
41#include "src/core/SkImageFilter_Base.h"
42#include "src/core/SkRecord.h"
43#include "src/core/SkSpecialImage.h"
44#include "src/utils/SkCanvasStack.h"
45#include "tests/Test.h"
46
47#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
48#include "include/core/SkColorSpace.h"
49#include "include/private/SkColorData.h"
50#endif
51
52#include <memory>
53#include <utility>
54
55class SkReadBuffer;
56
57struct ClipRectVisitor {
58    skiatest::Reporter* r;
59
60    template <typename T>
61    SkRect operator()(const T&) {
62        REPORTER_ASSERT(r, false, "unexpected record");
63        return {1,1,0,0};
64    }
65
66    SkRect operator()(const SkRecords::ClipRect& op) {
67        return op.rect;
68    }
69};
70
71DEF_TEST(canvas_unsorted_clip, r) {
72    // Test that sorted and unsorted clip rects are forwarded
73    // to picture subclasses and/or devices sorted.
74    //
75    // We can't just test this with an SkCanvas on stack and
76    // SkCanvas::getLocalClipBounds(), as that only tests the raster device,
77    // which sorts these rects itself.
78    for (SkRect clip : {SkRect{0,0,5,5}, SkRect{5,5,0,0}}) {
79        SkPictureRecorder rec;
80        rec.beginRecording({0,0,10,10})
81            ->clipRect(clip);
82        sk_sp<SkPicture> pic = rec.finishRecordingAsPicture();
83
84        auto bp = (const SkBigPicture*)pic.get();
85        const SkRecord* record = bp->record();
86
87        REPORTER_ASSERT(r, record->count() == 1);
88        REPORTER_ASSERT(r, record->visit(0, ClipRectVisitor{r})
89                                .isSorted());
90    }
91}
92
93DEF_TEST(canvas_clipbounds, reporter) {
94    SkCanvas canvas(10, 10);
95    SkIRect irect, irect2;
96    SkRect rect, rect2;
97
98    irect = canvas.getDeviceClipBounds();
99    REPORTER_ASSERT(reporter, irect == SkIRect::MakeWH(10, 10));
100    REPORTER_ASSERT(reporter, canvas.getDeviceClipBounds(&irect2));
101    REPORTER_ASSERT(reporter, irect == irect2);
102
103    // local bounds are always too big today -- can we trim them?
104    rect = canvas.getLocalClipBounds();
105    REPORTER_ASSERT(reporter, rect.contains(SkRect::MakeWH(10, 10)));
106    REPORTER_ASSERT(reporter, canvas.getLocalClipBounds(&rect2));
107    REPORTER_ASSERT(reporter, rect == rect2);
108
109    canvas.clipRect(SkRect::MakeEmpty());
110
111    irect = canvas.getDeviceClipBounds();
112    REPORTER_ASSERT(reporter, irect == SkIRect::MakeEmpty());
113    REPORTER_ASSERT(reporter, !canvas.getDeviceClipBounds(&irect2));
114    REPORTER_ASSERT(reporter, irect == irect2);
115
116    rect = canvas.getLocalClipBounds();
117    REPORTER_ASSERT(reporter, rect == SkRect::MakeEmpty());
118    REPORTER_ASSERT(reporter, !canvas.getLocalClipBounds(&rect2));
119    REPORTER_ASSERT(reporter, rect == rect2);
120
121    // Test for wacky sizes that we (historically) have guarded against
122    {
123        SkCanvas c(-10, -20);
124        REPORTER_ASSERT(reporter, c.getBaseLayerSize() == SkISize::MakeEmpty());
125
126        SkPictureRecorder().beginRecording({ 5, 5, 4, 4 });
127    }
128}
129
130// Will call proc with multiple styles of canvas (recording, raster, pdf)
131template <typename F> static void multi_canvas_driver(int w, int h, F proc) {
132    proc(SkPictureRecorder().beginRecording(SkRect::MakeIWH(w, h)));
133
134    SkNullWStream stream;
135    if (auto doc = SkPDF::MakeDocument(&stream)) {
136        proc(doc->beginPage(SkIntToScalar(w), SkIntToScalar(h)));
137    }
138
139    proc(SkSurface::MakeRasterN32Premul(w, h, nullptr)->getCanvas());
140}
141
142const SkIRect gBaseRestrictedR = { 0, 0, 10, 10 };
143
144static void test_restriction(skiatest::Reporter* reporter, SkCanvas* canvas) {
145    REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == gBaseRestrictedR);
146
147    const SkIRect restrictionR = { 2, 2, 8, 8 };
148    canvas->androidFramework_setDeviceClipRestriction(restrictionR);
149    REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == restrictionR);
150
151    const SkIRect clipR = { 4, 4, 6, 6 };
152    canvas->clipRect(SkRect::Make(clipR), SkClipOp::kIntersect);
153    REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == clipR);
154}
155
156/**
157 *  Clip restriction logic exists in the canvas itself, and in various kinds of devices.
158 *
159 *  This test explicitly tries to exercise that variety:
160 *  - picture : empty device but exercises canvas itself
161 *  - pdf : uses SkClipStack in its device (as does SVG and GPU)
162 *  - raster : uses SkRasterClip in its device
163 */
164DEF_TEST(canvas_clip_restriction, reporter) {
165    multi_canvas_driver(gBaseRestrictedR.width(), gBaseRestrictedR.height(),
166                        [reporter](SkCanvas* canvas) { test_restriction(reporter, canvas); });
167}
168
169DEF_TEST(canvas_empty_clip, reporter) {
170    multi_canvas_driver(50, 50, [reporter](SkCanvas* canvas) {
171        canvas->save();
172        canvas->clipRect({0, 0, 20, 40 });
173        REPORTER_ASSERT(reporter, !canvas->isClipEmpty());
174        canvas->clipRect({30, 0, 50, 40 });
175        REPORTER_ASSERT(reporter, canvas->isClipEmpty());
176    });
177}
178
179DEF_TEST(CanvasNewRasterTest, reporter) {
180    SkImageInfo info = SkImageInfo::MakeN32Premul(10, 10);
181    const size_t minRowBytes = info.minRowBytes();
182    const size_t size = info.computeByteSize(minRowBytes);
183    SkAutoTMalloc<SkPMColor> storage(size);
184    SkPMColor* baseAddr = storage.get();
185    sk_bzero(baseAddr, size);
186
187    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes);
188    REPORTER_ASSERT(reporter, canvas);
189
190    SkPixmap pmap;
191    const SkPMColor* addr = canvas->peekPixels(&pmap) ? pmap.addr32() : nullptr;
192    REPORTER_ASSERT(reporter, addr);
193    REPORTER_ASSERT(reporter, info == pmap.info());
194    REPORTER_ASSERT(reporter, minRowBytes == pmap.rowBytes());
195    for (int y = 0; y < info.height(); ++y) {
196        for (int x = 0; x < info.width(); ++x) {
197            REPORTER_ASSERT(reporter, 0 == addr[x]);
198        }
199        addr = (const SkPMColor*)((const char*)addr + pmap.rowBytes());
200    }
201
202    // unaligned rowBytes
203    REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr,
204                                                                    minRowBytes + 1));
205
206    // now try a deliberately bad info
207    info = info.makeWH(-1, info.height());
208    REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes));
209
210    // too big
211    info = info.makeWH(1 << 30, 1 << 30);
212    REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes));
213
214    // not a valid pixel type
215    info = SkImageInfo::Make(10, 10, kUnknown_SkColorType, info.alphaType());
216    REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes));
217
218    // We should not succeed with a zero-sized valid info
219    info = SkImageInfo::MakeN32Premul(0, 0);
220    canvas = SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes);
221    REPORTER_ASSERT(reporter, nullptr == canvas);
222}
223
224static SkPath make_path_from_rect(SkRect r) {
225    SkPath path;
226    path.addRect(r);
227    return path;
228}
229
230static SkRegion make_region_from_irect(SkIRect r) {
231    SkRegion region;
232    region.setRect(r);
233    return region;
234}
235
236static SkBitmap make_n32_bitmap(int w, int h, SkColor c = SK_ColorWHITE) {
237    SkBitmap bm;
238    bm.allocN32Pixels(w, h);
239    bm.eraseColor(c);
240    return bm;
241}
242
243// Constants used by test steps
244static constexpr SkRect kRect = {0, 0, 2, 1};
245static constexpr SkColor kColor = 0x01020304;
246static constexpr int kWidth = 2;
247static constexpr int kHeight = 2;
248
249using CanvasTest = void (*)(SkCanvas*, skiatest::Reporter*);
250
251static CanvasTest kCanvasTests[] = {
252    [](SkCanvas* c, skiatest::Reporter* r) {
253        c->translate(SkIntToScalar(1), SkIntToScalar(2));
254    },
255    [](SkCanvas* c, skiatest::Reporter* r) {
256        c->scale(SkIntToScalar(1), SkIntToScalar(2));
257    },
258    [](SkCanvas* c, skiatest::Reporter* r) {
259        c->rotate(SkIntToScalar(1));
260    },
261    [](SkCanvas* c, skiatest::Reporter* r) {
262        c->skew(SkIntToScalar(1), SkIntToScalar(2));
263    },
264    [](SkCanvas* c, skiatest::Reporter* r) {
265        c->concat(SkMatrix::Scale(2, 3));
266    },
267    [](SkCanvas* c, skiatest::Reporter* r) {
268        c->setMatrix(SkMatrix::Scale(2, 3));
269    },
270    [](SkCanvas* c, skiatest::Reporter* r) {
271        c->clipRect(kRect);
272    },
273    [](SkCanvas* c, skiatest::Reporter* r) {
274        c->clipPath(make_path_from_rect(SkRect{0, 0, 2, 1}));
275    },
276    [](SkCanvas* c, skiatest::Reporter* r) {
277        c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1}));
278    },
279    [](SkCanvas* c, skiatest::Reporter* r) {
280        c->clear(kColor);
281    },
282    [](SkCanvas* c, skiatest::Reporter* r) {
283        int saveCount = c->getSaveCount();
284        c->save();
285        c->translate(SkIntToScalar(1), SkIntToScalar(2));
286        c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1}));
287        c->restore();
288        REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
289        REPORTER_ASSERT(r, c->getTotalMatrix().isIdentity());
290        //REPORTER_ASSERT(reporter, c->getTotalClip() != kTestRegion);
291    },
292    [](SkCanvas* c, skiatest::Reporter* r) {
293        int saveCount = c->getSaveCount();
294        c->saveLayer(nullptr, nullptr);
295        c->restore();
296        REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
297    },
298    [](SkCanvas* c, skiatest::Reporter* r) {
299        int saveCount = c->getSaveCount();
300        c->saveLayer(&kRect, nullptr);
301        c->restore();
302        REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
303    },
304    [](SkCanvas* c, skiatest::Reporter* r) {
305        int saveCount = c->getSaveCount();
306        SkPaint p;
307        c->saveLayer(nullptr, &p);
308        c->restore();
309        REPORTER_ASSERT(r, c->getSaveCount() == saveCount);
310    },
311    [](SkCanvas* c, skiatest::Reporter* r) {
312        // This test exercises a functionality in SkPicture that leads to the
313        // recording of restore offset placeholders.  This test will trigger an
314        // assertion at playback time if the placeholders are not properly
315        // filled when the recording ends.
316        c->clipRect(kRect);
317        c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1}));
318    },
319    [](SkCanvas* c, skiatest::Reporter* r) {
320        // exercise fix for http://code.google.com/p/skia/issues/detail?id=560
321        // ('SkPathStroker::lineTo() fails for line with length SK_ScalarNearlyZero')
322        SkPaint paint;
323        paint.setStrokeWidth(SkIntToScalar(1));
324        paint.setStyle(SkPaint::kStroke_Style);
325        SkPath path;
326        path.moveTo(SkPoint{ 0, 0 });
327        path.lineTo(SkPoint{ 0, SK_ScalarNearlyZero });
328        path.lineTo(SkPoint{ SkIntToScalar(1), 0 });
329        path.lineTo(SkPoint{ SkIntToScalar(1), SK_ScalarNearlyZero/2 });
330        // test nearly zero length path
331        c->drawPath(path, paint);
332    },
333    [](SkCanvas* c, skiatest::Reporter* r) {
334        SkPictureRecorder recorder;
335        SkCanvas* testCanvas = recorder.beginRecording(SkIntToScalar(kWidth),
336                                                       SkIntToScalar(kHeight));
337        testCanvas->scale(SkIntToScalar(2), SkIntToScalar(1));
338        testCanvas->clipRect(kRect);
339        testCanvas->drawRect(kRect, SkPaint());
340        c->drawPicture(recorder.finishRecordingAsPicture());
341    },
342    [](SkCanvas* c, skiatest::Reporter* r) {
343        int baseSaveCount = c->getSaveCount();
344        int n = c->save();
345        REPORTER_ASSERT(r, baseSaveCount == n);
346        REPORTER_ASSERT(r, baseSaveCount + 1 == c->getSaveCount());
347        c->save();
348        c->save();
349        REPORTER_ASSERT(r, baseSaveCount + 3 == c->getSaveCount());
350        c->restoreToCount(baseSaveCount + 1);
351        REPORTER_ASSERT(r, baseSaveCount + 1 == c->getSaveCount());
352
353       // should this pin to 1, or be a no-op, or crash?
354       c->restoreToCount(0);
355       REPORTER_ASSERT(r, 1 == c->getSaveCount());
356    },
357    [](SkCanvas* c, skiatest::Reporter* r) {
358       // This test step challenges the TestDeferredCanvasStateConsistency
359       // test cases because the opaque paint can trigger an optimization
360       // that discards previously recorded commands. The challenge is to maintain
361       // correct clip and matrix stack state.
362       c->resetMatrix();
363       c->rotate(SkIntToScalar(30));
364       c->save();
365       c->translate(SkIntToScalar(2), SkIntToScalar(1));
366       c->save();
367       c->scale(SkIntToScalar(3), SkIntToScalar(3));
368       SkPaint paint;
369       paint.setColor(0xFFFFFFFF);
370       c->drawPaint(paint);
371       c->restore();
372       c->restore();
373    },
374    [](SkCanvas* c, skiatest::Reporter* r) {
375       // This test step challenges the TestDeferredCanvasStateConsistency
376       // test case because the canvas flush on a deferred canvas will
377       // reset the recording session. The challenge is to maintain correct
378       // clip and matrix stack state on the playback canvas.
379       c->resetMatrix();
380       c->rotate(SkIntToScalar(30));
381       c->save();
382       c->translate(SkIntToScalar(2), SkIntToScalar(1));
383       c->save();
384       c->scale(SkIntToScalar(3), SkIntToScalar(3));
385       c->drawRect(kRect, SkPaint());
386       c->flush();
387       c->restore();
388       c->restore();
389    },
390    [](SkCanvas* c, skiatest::Reporter* r) {
391        SkPoint pts[4];
392        pts[0].set(0, 0);
393        pts[1].set(SkIntToScalar(kWidth), 0);
394        pts[2].set(SkIntToScalar(kWidth), SkIntToScalar(kHeight));
395        pts[3].set(0, SkIntToScalar(kHeight));
396        SkPaint paint;
397        SkBitmap bitmap(make_n32_bitmap(kWidth, kHeight, 0x05060708));
398        paint.setShader(bitmap.makeShader(SkSamplingOptions()));
399        c->drawVertices(
400            SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode, 4, pts, pts, nullptr),
401            SkBlendMode::kModulate, paint);
402    }
403};
404
405DEF_TEST(Canvas_bitmap, reporter) {
406    for (const CanvasTest& test : kCanvasTests) {
407        SkBitmap referenceStore = make_n32_bitmap(kWidth, kHeight);
408        SkCanvas referenceCanvas(referenceStore);
409        test(&referenceCanvas, reporter);
410    }
411}
412
413DEF_TEST(Canvas_pdf, reporter) {
414    for (const CanvasTest& test : kCanvasTests) {
415        SkNullWStream outStream;
416        if (auto doc = SkPDF::MakeDocument(&outStream)) {
417            SkCanvas* canvas = doc->beginPage(SkIntToScalar(kWidth),
418                                              SkIntToScalar(kHeight));
419            REPORTER_ASSERT(reporter, canvas);
420            test(canvas, reporter);
421        }
422    }
423}
424
425DEF_TEST(Canvas_SaveState, reporter) {
426    SkCanvas canvas(10, 10);
427    REPORTER_ASSERT(reporter, 1 == canvas.getSaveCount());
428
429    int n = canvas.save();
430    REPORTER_ASSERT(reporter, 1 == n);
431    REPORTER_ASSERT(reporter, 2 == canvas.getSaveCount());
432
433    n = canvas.saveLayer(nullptr, nullptr);
434    REPORTER_ASSERT(reporter, 2 == n);
435    REPORTER_ASSERT(reporter, 3 == canvas.getSaveCount());
436
437    canvas.restore();
438    REPORTER_ASSERT(reporter, 2 == canvas.getSaveCount());
439    canvas.restore();
440    REPORTER_ASSERT(reporter, 1 == canvas.getSaveCount());
441}
442
443DEF_TEST(Canvas_ClipEmptyPath, reporter) {
444    SkCanvas canvas(10, 10);
445    canvas.save();
446    SkPath path;
447    canvas.clipPath(path);
448    canvas.restore();
449    canvas.save();
450    path.moveTo(5, 5);
451    canvas.clipPath(path);
452    canvas.restore();
453    canvas.save();
454    path.moveTo(7, 7);
455    canvas.clipPath(path);  // should not assert here
456    canvas.restore();
457}
458
459namespace {
460
461class MockFilterCanvas : public SkPaintFilterCanvas {
462public:
463    MockFilterCanvas(SkCanvas* canvas) : INHERITED(canvas) { }
464
465protected:
466    bool onFilter(SkPaint&) const override { return true; }
467
468private:
469    using INHERITED = SkPaintFilterCanvas;
470};
471
472} // anonymous namespace
473
474// SkPaintFilterCanvas should inherit the initial target canvas state.
475DEF_TEST(PaintFilterCanvas_ConsistentState, reporter) {
476    SkCanvas canvas(100, 100);
477    canvas.clipRect(SkRect::MakeXYWH(12.7f, 12.7f, 75, 75));
478    canvas.scale(0.5f, 0.75f);
479
480    MockFilterCanvas filterCanvas(&canvas);
481    REPORTER_ASSERT(reporter, canvas.getTotalMatrix() == filterCanvas.getTotalMatrix());
482    REPORTER_ASSERT(reporter, canvas.getLocalClipBounds() == filterCanvas.getLocalClipBounds());
483
484    filterCanvas.clipRect(SkRect::MakeXYWH(30.5f, 30.7f, 100, 100));
485    filterCanvas.scale(0.75f, 0.5f);
486    REPORTER_ASSERT(reporter, canvas.getTotalMatrix() == filterCanvas.getTotalMatrix());
487    REPORTER_ASSERT(reporter, filterCanvas.getLocalClipBounds().contains(canvas.getLocalClipBounds()));
488}
489
490///////////////////////////////////////////////////////////////////////////////////////////////////
491
492namespace {
493
494// Subclass that takes a bool*, which it updates in its construct (true) and destructor (false)
495// to allow the caller to know how long the object is alive.
496class LifeLineCanvas : public SkCanvas {
497    bool*   fLifeLine;
498public:
499    LifeLineCanvas(int w, int h, bool* lifeline) : SkCanvas(w, h), fLifeLine(lifeline) {
500        *fLifeLine = true;
501    }
502    ~LifeLineCanvas() override {
503        *fLifeLine = false;
504    }
505};
506
507}  // namespace
508
509// Check that NWayCanvas does NOT try to manage the lifetime of its sub-canvases
510DEF_TEST(NWayCanvas, r) {
511    const int w = 10;
512    const int h = 10;
513    bool life[2];
514    {
515        LifeLineCanvas c0(w, h, &life[0]);
516        REPORTER_ASSERT(r, life[0]);
517    }
518    REPORTER_ASSERT(r, !life[0]);
519
520
521    std::unique_ptr<SkCanvas> c0 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[0]));
522    std::unique_ptr<SkCanvas> c1 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[1]));
523    REPORTER_ASSERT(r, life[0]);
524    REPORTER_ASSERT(r, life[1]);
525
526    {
527        SkNWayCanvas nway(w, h);
528        nway.addCanvas(c0.get());
529        nway.addCanvas(c1.get());
530        REPORTER_ASSERT(r, life[0]);
531        REPORTER_ASSERT(r, life[1]);
532    }
533    // Now assert that the death of the nway has NOT also killed the sub-canvases
534    REPORTER_ASSERT(r, life[0]);
535    REPORTER_ASSERT(r, life[1]);
536}
537
538// Check that CanvasStack DOES manage the lifetime of its sub-canvases
539DEF_TEST(CanvasStack, r) {
540    const int w = 10;
541    const int h = 10;
542    bool life[2];
543    std::unique_ptr<SkCanvas> c0 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[0]));
544    std::unique_ptr<SkCanvas> c1 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[1]));
545    REPORTER_ASSERT(r, life[0]);
546    REPORTER_ASSERT(r, life[1]);
547
548    {
549        SkCanvasStack stack(w, h);
550        stack.pushCanvas(std::move(c0), {0,0});
551        stack.pushCanvas(std::move(c1), {0,0});
552        REPORTER_ASSERT(r, life[0]);
553        REPORTER_ASSERT(r, life[1]);
554    }
555    // Now assert that the death of the canvasstack has also killed the sub-canvases
556    REPORTER_ASSERT(r, !life[0]);
557    REPORTER_ASSERT(r, !life[1]);
558}
559
560static void test_cliptype(SkCanvas* canvas, skiatest::Reporter* r) {
561    REPORTER_ASSERT(r, !canvas->isClipEmpty());
562    REPORTER_ASSERT(r, canvas->isClipRect());
563
564    canvas->save();
565    canvas->clipRect({0, 0, 0, 0});
566    REPORTER_ASSERT(r, canvas->isClipEmpty());
567    REPORTER_ASSERT(r, !canvas->isClipRect());
568    canvas->restore();
569
570    canvas->save();
571    canvas->clipRect({2, 2, 6, 6});
572    REPORTER_ASSERT(r, !canvas->isClipEmpty());
573    REPORTER_ASSERT(r, canvas->isClipRect());
574    canvas->restore();
575
576    canvas->save();
577    canvas->clipRect({2, 2, 6, 6}, SkClipOp::kDifference);  // punch a hole in the clip
578    REPORTER_ASSERT(r, !canvas->isClipEmpty());
579    REPORTER_ASSERT(r, !canvas->isClipRect());
580    canvas->restore();
581
582    REPORTER_ASSERT(r, !canvas->isClipEmpty());
583    REPORTER_ASSERT(r, canvas->isClipRect());
584}
585
586DEF_TEST(CanvasClipType, r) {
587    // test rasterclip backend
588    test_cliptype(SkSurface::MakeRasterN32Premul(10, 10)->getCanvas(), r);
589
590    // test clipstack backend
591    SkDynamicMemoryWStream stream;
592    if (auto doc = SkPDF::MakeDocument(&stream)) {
593        test_cliptype(doc->beginPage(100, 100), r);
594    }
595}
596
597#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
598DEF_TEST(Canvas_LegacyColorBehavior, r) {
599    sk_sp<SkColorSpace> cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
600                                                   SkNamedGamut::kAdobeRGB);
601
602    // Make a Adobe RGB bitmap.
603    SkBitmap bitmap;
604    bitmap.allocPixels(SkImageInfo::MakeN32(1, 1, kOpaque_SkAlphaType, cs));
605    bitmap.eraseColor(0xFF000000);
606
607    // Wrap it in a legacy canvas.  Test that the canvas behaves like a legacy canvas.
608    SkCanvas canvas(bitmap, SkCanvas::ColorBehavior::kLegacy);
609    REPORTER_ASSERT(r, !canvas.imageInfo().colorSpace());
610    SkPaint p;
611    p.setColor(SK_ColorRED);
612    canvas.drawIRect(SkIRect::MakeWH(1, 1), p);
613    REPORTER_ASSERT(r, SK_ColorRED == SkSwizzle_BGRA_to_PMColor(*bitmap.getAddr32(0, 0)));
614}
615#endif
616
617namespace {
618
619class ZeroBoundsImageFilter : public SkImageFilter_Base {
620public:
621    static sk_sp<SkImageFilter> Make() { return sk_sp<SkImageFilter>(new ZeroBoundsImageFilter); }
622
623protected:
624    sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint*) const override {
625        return nullptr;
626    }
627    SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&,
628                               MapDirection, const SkIRect* inputRect) const override {
629        return SkIRect::MakeEmpty();
630    }
631
632private:
633    SK_FLATTENABLE_HOOKS(ZeroBoundsImageFilter)
634
635    ZeroBoundsImageFilter() : INHERITED(nullptr, 0, nullptr) {}
636
637    using INHERITED = SkImageFilter_Base;
638};
639
640sk_sp<SkFlattenable> ZeroBoundsImageFilter::CreateProc(SkReadBuffer& buffer) {
641    SkDEBUGFAIL("Should never get here");
642    return nullptr;
643}
644
645}  // anonymous namespace
646
647DEF_TEST(Canvas_SaveLayerWithNullBoundsAndZeroBoundsImageFilter, r) {
648    SkCanvas canvas(10, 10);
649    SkPaint p;
650    p.setImageFilter(ZeroBoundsImageFilter::Make());
651    // This should not fail any assert.
652    canvas.saveLayer(nullptr, &p);
653    REPORTER_ASSERT(r, canvas.getDeviceClipBounds().isEmpty());
654    canvas.restore();
655}
656
657// Test that we don't crash/assert when building a canvas with degenerate coordintes
658// (esp. big ones, that might invoke tiling).
659DEF_TEST(Canvas_degenerate_dimension, reporter) {
660    // Need a paint that will sneak us past the quickReject in SkCanvas, so we can test the
661    // raster code further downstream.
662    SkPaint paint;
663    paint.setImageFilter(SkImageFilters::Shader(SkShaders::Color(SK_ColorBLACK), nullptr));
664    REPORTER_ASSERT(reporter, !paint.canComputeFastBounds());
665
666    const int big = 100 * 1024; // big enough to definitely trigger tiling
667    const SkISize sizes[] {SkISize{0, big}, {big, 0}, {0, 0}};
668    for (SkISize size : sizes) {
669        SkBitmap bm;
670        bm.setInfo(SkImageInfo::MakeN32Premul(size.width(), size.height()));
671        SkCanvas canvas(bm);
672        canvas.drawRect({0, 0, 100, 90*1024}, paint);
673    }
674}
675
676DEF_TEST(Canvas_ClippedOutImageFilter, reporter) {
677    SkCanvas canvas(100, 100);
678
679    SkPaint p;
680    p.setColor(SK_ColorGREEN);
681    p.setImageFilter(SkImageFilters::Blur(3.0f, 3.0f, nullptr, nullptr));
682
683    SkRect blurredRect = SkRect::MakeXYWH(60, 10, 30, 30);
684
685    SkMatrix invM;
686    invM.setRotate(-45);
687    invM.mapRect(&blurredRect);
688
689    const SkRect clipRect = SkRect::MakeXYWH(0, 50, 50, 50);
690
691    canvas.clipRect(clipRect);
692
693    canvas.rotate(45);
694    const SkMatrix preCTM = canvas.getTotalMatrix();
695    canvas.drawRect(blurredRect, p);
696    const SkMatrix postCTM = canvas.getTotalMatrix();
697    REPORTER_ASSERT(reporter, preCTM == postCTM);
698}
699
700DEF_TEST(canvas_markctm, reporter) {
701    SkCanvas canvas(10, 10);
702
703    SkM44    m;
704    const char* id_a = "a";
705    const char* id_b = "b";
706
707    REPORTER_ASSERT(reporter, !canvas.findMarkedCTM(id_a, nullptr));
708    REPORTER_ASSERT(reporter, !canvas.findMarkedCTM(id_b, nullptr));
709
710    // remember the starting state
711    SkM44 b = canvas.getLocalToDevice();
712    canvas.markCTM(id_b);
713
714    // test add
715    canvas.concat(SkM44::Scale(2, 4, 6));
716    SkM44 a = canvas.getLocalToDevice();
717    canvas.markCTM(id_a);
718    REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a);
719
720    // test replace
721    canvas.translate(1, 2);
722    SkM44 a1 = canvas.getLocalToDevice();
723    SkASSERT(a != a1);
724    canvas.markCTM(id_a);
725    REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1);
726
727    // test nested
728    canvas.save();
729    // no change
730    REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_b, &m) && m == b);
731    REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1);
732    canvas.translate(2, 3);
733    SkM44 a2 = canvas.getLocalToDevice();
734    SkASSERT(a2 != a1);
735    canvas.markCTM(id_a);
736    // found the new one
737    REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a2);
738    canvas.restore();
739    // found the previous one
740    REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1);
741}
742
743DEF_TEST(canvas_savelayer_destructor, reporter) {
744    // What should happen in our destructor if we have unbalanced saveLayers?
745
746    SkPMColor pixels[16];
747    const SkImageInfo info = SkImageInfo::MakeN32Premul(4, 4);
748    SkPixmap pm(info, pixels, 4 * sizeof(SkPMColor));
749
750    // check all of the pixel values in pm
751    auto check_pixels = [&](SkColor expected) {
752        const SkPMColor pmc = SkPreMultiplyColor(expected);
753        for (int y = 0; y < pm.info().height(); ++y) {
754            for (int x = 0; x < pm.info().width(); ++x) {
755                if (*pm.addr32(x, y) != pmc) {
756                    ERRORF(reporter, "check_pixels_failed");
757                    return;
758                }
759            }
760        }
761    };
762
763    auto do_test = [&](int saveCount, int restoreCount) {
764        SkASSERT(restoreCount <= saveCount);
765
766        auto surf = SkSurface::MakeRasterDirect(pm);
767        auto canvas = surf->getCanvas();
768
769        canvas->clear(SK_ColorRED);
770        check_pixels(SK_ColorRED);
771
772        for (int i = 0; i < saveCount; ++i) {
773            canvas->saveLayer(nullptr, nullptr);
774        }
775
776        canvas->clear(SK_ColorBLUE);
777        // so far, we still expect to see the red, since the blue was drawn in a layer
778        check_pixels(SK_ColorRED);
779
780        for (int i = 0; i < restoreCount; ++i) {
781            canvas->restore();
782        }
783        // by returning, we are implicitly deleting the surface, and its associated canvas
784    };
785
786    do_test(1, 1);
787    // since we called restore, we expect to see now see blue
788    check_pixels(SK_ColorBLUE);
789
790    // Now repeat that, but delete the canvas before we restore it
791    do_test(1, 0);
792    // We don't blit the unbalanced saveLayers, so we expect to see red (not the layer's blue)
793    check_pixels(SK_ColorRED);
794
795    // Finally, test with multiple unbalanced saveLayers. This led to a crash in an earlier
796    // implementation (crbug.com/1238731)
797    do_test(2, 0);
798    check_pixels(SK_ColorRED);
799}
800