1/*
2 * Copyright 2017 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 "SkCanvas.h"
9#include "SkDrawShadowInfo.h"
10#include "SkPath.h"
11#include "SkShadowTessellator.h"
12#include "SkShadowUtils.h"
13#include "SkVertices.h"
14#include "Test.h"
15
16void tessellate_shadow(skiatest::Reporter* reporter, const SkPath& path, const SkMatrix& ctm,
17                       bool expectSuccess) {
18
19    auto heightParams = SkPoint3::Make(0, 0, 4);
20
21    auto verts = SkShadowTessellator::MakeAmbient(path, ctm, heightParams, true);
22    if (expectSuccess != SkToBool(verts)) {
23        ERRORF(reporter, "Expected shadow tessellation to %s but it did not.",
24               expectSuccess ? "succeed" : "fail");
25    }
26    verts = SkShadowTessellator::MakeAmbient(path, ctm, heightParams, false);
27    if (expectSuccess != SkToBool(verts)) {
28        ERRORF(reporter, "Expected shadow tessellation to %s but it did not.",
29               expectSuccess ? "succeed" : "fail");
30    }
31    verts = SkShadowTessellator::MakeSpot(path, ctm, heightParams, {0, 0, 128}, 128.f, false);
32    if (expectSuccess != SkToBool(verts)) {
33        ERRORF(reporter, "Expected shadow tessellation to %s but it did not.",
34               expectSuccess ? "succeed" : "fail");
35    }
36    verts = SkShadowTessellator::MakeSpot(path, ctm, heightParams, {0, 0, 128}, 128.f, false);
37    if (expectSuccess != SkToBool(verts)) {
38        ERRORF(reporter, "Expected shadow tessellation to %s but it did not.",
39               expectSuccess ? "succeed" : "fail");
40    }
41}
42
43DEF_TEST(ShadowUtils, reporter) {
44    SkCanvas canvas(100, 100);
45
46    SkPath path;
47    path.cubicTo(100, 50, 20, 100, 0, 0);
48    tessellate_shadow(reporter, path, canvas.getTotalMatrix(), true);
49
50    // This line segment has no area and no shadow.
51    path.reset();
52    path.lineTo(10.f, 10.f);
53    tessellate_shadow(reporter, path, canvas.getTotalMatrix(), false);
54
55    // A series of colinear line segments
56    path.reset();
57    for (int i = 0; i < 10; ++i) {
58        path.lineTo((SkScalar)i, (SkScalar)i);
59    }
60    tessellate_shadow(reporter, path, canvas.getTotalMatrix(), false);
61}
62
63void check_xformed_bounds(skiatest::Reporter* reporter, const SkPath& path, const SkMatrix& ctm) {
64    const SkDrawShadowRec rec = {
65        SkPoint3::Make(0, 0, 4),
66        SkPoint3::Make(100, 0, 600),
67        800.f,
68        0x08000000,
69        0x40000000,
70        0
71    };
72    SkRect bounds;
73    SkDrawShadowMetrics::GetLocalBounds(path, rec, ctm, &bounds);
74    ctm.mapRect(&bounds);
75
76    auto verts = SkShadowTessellator::MakeAmbient(path, ctm, rec.fZPlaneParams, true);
77    if (verts) {
78        REPORTER_ASSERT(reporter, bounds.contains(verts->bounds()));
79    }
80
81    SkPoint mapXY = ctm.mapXY(rec.fLightPos.fX, rec.fLightPos.fY);
82    SkPoint3 devLightPos = SkPoint3::Make(mapXY.fX, mapXY.fY, rec.fLightPos.fZ);
83    verts = SkShadowTessellator::MakeSpot(path, ctm, rec.fZPlaneParams, devLightPos,
84                                          rec.fLightRadius, false);
85    if (verts) {
86        REPORTER_ASSERT(reporter, bounds.contains(verts->bounds()));
87    }
88}
89
90void check_bounds(skiatest::Reporter* reporter, const SkPath& path) {
91    SkMatrix ctm;
92    ctm.setTranslate(100, 100);
93    check_xformed_bounds(reporter, path, ctm);
94    ctm.postScale(2, 2);
95    check_xformed_bounds(reporter, path, ctm);
96    ctm.preRotate(45);
97    check_xformed_bounds(reporter, path, ctm);
98    ctm.preSkew(40, -20);
99    check_xformed_bounds(reporter, path, ctm);
100    ctm[SkMatrix::kMPersp0] = 0.0001f;
101    ctm[SkMatrix::kMPersp1] = 12.f;
102    check_xformed_bounds(reporter, path, ctm);
103}
104
105DEF_TEST(ShadowBounds, reporter) {
106    SkPath path;
107    path.addRRect(SkRRect::MakeRectXY(SkRect::MakeLTRB(-50, -20, 40, 30), 4, 4));
108    check_bounds(reporter, path);
109
110    path.reset();
111    path.addOval(SkRect::MakeLTRB(300, 300, 900, 900));
112    check_bounds(reporter, path);
113
114    path.reset();
115    path.cubicTo(100, 50, 20, 100, 0, 0);
116    check_bounds(reporter, path);
117}
118