• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkPaint.h"
13 #include "include/core/SkPath.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkSize.h"
17 #include "include/core/SkString.h"
18 #include "include/core/SkTypes.h"
19 #include "include/gpu/GrContextOptions.h"
20 #include "include/gpu/GrDirectContext.h"
21 #include "src/base/SkRandom.h"
22 #include "src/core/SkGeometry.h"
23 #include "src/gpu/ganesh/GrCaps.h"
24 #include "src/gpu/ganesh/GrDirectContextPriv.h"
25 #include "src/gpu/ganesh/GrDrawingManager.h"
26 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
27 
28 static constexpr float kStrokeWidth = 30;
29 static constexpr int kCellSize = 200;
30 static constexpr int kNumCols = 5;
31 static constexpr int kNumRows = 5;
32 static constexpr int kTestWidth = kNumCols * kCellSize;
33 static constexpr int kTestHeight = kNumRows * kCellSize;
34 
35 enum class CellFillMode {
36     kStretch,
37     kCenter
38 };
39 
40 struct TrickyCubic {
41     SkPoint fPoints[4];
42     int fNumPts;
43     CellFillMode fFillMode;
44     float fScale = 1;
45 };
46 
47 // This is a compilation of cubics that have given strokers grief. Feel free to add more.
48 static const TrickyCubic kTrickyCubics[] = {
49     {{{122, 737}, {348, 553}, {403, 761}, {400, 760}}, 4, CellFillMode::kStretch},
50     {{{244, 520}, {244, 518}, {1141, 634}, {394, 688}}, 4, CellFillMode::kStretch},
51     {{{550, 194}, {138, 130}, {1035, 246}, {288, 300}}, 4, CellFillMode::kStretch},
52     {{{226, 733}, {556, 779}, {-43, 471}, {348, 683}}, 4, CellFillMode::kStretch},
53     {{{268, 204}, {492, 304}, {352, 23}, {433, 412}}, 4, CellFillMode::kStretch},
54     {{{172, 480}, {396, 580}, {256, 299}, {338, 677}}, 4, CellFillMode::kStretch},
55     {{{731, 340}, {318, 252}, {1026, -64}, {367, 265}}, 4, CellFillMode::kStretch},
56     {{{475, 708}, {62, 620}, {770, 304}, {220, 659}}, 4, CellFillMode::kStretch},
57     {{{0, 0}, {128, 128}, {128, 0}, {0, 128}}, 4, CellFillMode::kCenter},  // Perfect cusp
58     {{{0,.01f}, {128,127.999f}, {128,.01f}, {0,127.99f}}, 4, CellFillMode::kCenter},  // Near-cusp
59     {{{0,-.01f}, {128,128.001f}, {128,-.01f}, {0,128.001f}}, 4, CellFillMode::kCenter}, // Near-cusp
60     {{{0,0}, {0,-10}, {0,-10}, {0,10}}, 4, CellFillMode::kCenter, 1.098283f},  // Flat line with 180
61     {{{10,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with 2 180s
62     {{{39,-39}, {40,-40}, {40,-40}, {0,0}}, 4, CellFillMode::kStretch},  // Flat diagonal with 180
63     {{{40, 40}, {0, 0}, {200, 200}, {0, 0}}, 4, CellFillMode::kStretch},  // Diag w/ an internal 180
64     {{{0,0}, {1e-2f,0}, {-1e-2f,0}, {0,0}}, 4, CellFillMode::kCenter},  // Circle
65     {{{400.75f,100.05f}, {400.75f,100.05f}, {100.05f,300.95f}, {100.05f,300.95f}}, 4,
66      CellFillMode::kStretch},  // Flat line with no turns
67     {{{0.5f,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with 2 180s
68     {{{10,0}, {0,0}, {10,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with a 180
69     {{{1,1}, {2,1}, {1,1}, {1, std::numeric_limits<float>::quiet_NaN()}}, 3,
70      CellFillMode::kStretch},  // Flat QUAD with a cusp
71     {{{1,1}, {100,1}, {25,1}, {.3f, std::numeric_limits<float>::quiet_NaN()}}, 3,
72      CellFillMode::kStretch},  // Flat CONIC with a cusp
73     {{{1,1}, {100,1}, {25,1}, {1.5f, std::numeric_limits<float>::quiet_NaN()}}, 3,
74      CellFillMode::kStretch},  // Flat CONIC with a cusp
75 };
76 
calc_tight_cubic_bounds(const SkPoint P[4],int depth=5)77 static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) {
78     if (0 == depth) {
79         SkRect bounds;
80         bounds.fLeft = std::min(std::min(P[0].x(), P[1].x()), std::min(P[2].x(), P[3].x()));
81         bounds.fTop = std::min(std::min(P[0].y(), P[1].y()), std::min(P[2].y(), P[3].y()));
82         bounds.fRight = std::max(std::max(P[0].x(), P[1].x()), std::max(P[2].x(), P[3].x()));
83         bounds.fBottom = std::max(std::max(P[0].y(), P[1].y()), std::max(P[2].y(), P[3].y()));
84         return bounds;
85     }
86 
87     SkPoint chopped[7];
88     SkChopCubicAt(P, chopped, .5f);
89     SkRect bounds = calc_tight_cubic_bounds(chopped, depth - 1);
90     bounds.join(calc_tight_cubic_bounds(chopped+3, depth - 1));
91     return bounds;
92 }
93 
lerp(const SkPoint & a,const SkPoint & b,float T)94 static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
95     SkASSERT(1 != T);  // The below does not guarantee lerp(a, b, 1) === b.
96     return (b - a) * T + a;
97 }
98 
99 enum class FillMode {
100     kCenter,
101     kScale
102 };
103 
draw_test(SkCanvas * canvas,SkPaint::Cap cap,SkPaint::Join join)104 static void draw_test(SkCanvas* canvas, SkPaint::Cap cap, SkPaint::Join join) {
105     SkRandom rand;
106 
107     canvas->clear(SK_ColorBLACK);
108 
109     SkPaint strokePaint;
110     strokePaint.setAntiAlias(true);
111     strokePaint.setStrokeWidth(kStrokeWidth);
112     strokePaint.setStyle(SkPaint::kStroke_Style);
113     strokePaint.setStrokeCap(cap);
114     strokePaint.setStrokeJoin(join);
115 
116     for (size_t i = 0; i < std::size(kTrickyCubics); ++i) {
117         auto [originalPts, numPts, fillMode, scale] = kTrickyCubics[i];
118 
119         SkASSERT(numPts <= 4);
120         SkPoint p[4];
121         memcpy(p, originalPts, sizeof(SkPoint) * numPts);
122         for (int j = 0; j < numPts; ++j) {
123             p[j] *= scale;
124         }
125         float w = originalPts[3].fX;
126 
127         auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize,
128                                          kCellSize, kCellSize);
129 
130         SkRect strokeBounds;
131         if (numPts == 4) {
132             strokeBounds = calc_tight_cubic_bounds(p);
133         } else {
134             SkASSERT(numPts == 3);
135             SkPoint asCubic[4] = {p[0], lerp(p[0], p[1], 2/3.f), lerp(p[1], p[2], 1/3.f), p[2]};
136             strokeBounds = calc_tight_cubic_bounds(asCubic);
137         }
138         strokeBounds.outset(kStrokeWidth, kStrokeWidth);
139 
140         SkMatrix matrix;
141         if (fillMode == CellFillMode::kStretch) {
142             matrix = SkMatrix::RectToRect(strokeBounds, cellRect, SkMatrix::kCenter_ScaleToFit);
143         } else {
144             matrix.setTranslate(cellRect.x() + kStrokeWidth +
145                                 (cellRect.width() - strokeBounds.width()) / 2,
146                                 cellRect.y() + kStrokeWidth +
147                                 (cellRect.height() - strokeBounds.height()) / 2);
148         }
149 
150         SkAutoCanvasRestore acr(canvas, true);
151         canvas->concat(matrix);
152         strokePaint.setStrokeWidth(kStrokeWidth / matrix.getMaxScale());
153         strokePaint.setColor(rand.nextU() | 0xff808080);
154         SkPath path = SkPath().moveTo(p[0]);
155         if (numPts == 4) {
156             path.cubicTo(p[1], p[2], p[3]);
157         } else if (w == 1) {
158             SkASSERT(numPts == 3);
159             path.quadTo(p[1], p[2]);
160         } else {
161             SkASSERT(numPts == 3);
162             path.conicTo(p[1], p[2], w);
163         }
164         canvas->drawPath(path, strokePaint);
165     }
166 }
167 
DEF_SIMPLE_GM(trickycubicstrokes,canvas,kTestWidth,kTestHeight)168 DEF_SIMPLE_GM(trickycubicstrokes, canvas, kTestWidth, kTestHeight) {
169     draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join);
170 }
171 
DEF_SIMPLE_GM(trickycubicstrokes_roundcaps,canvas,kTestWidth,kTestHeight)172 DEF_SIMPLE_GM(trickycubicstrokes_roundcaps, canvas, kTestWidth, kTestHeight) {
173     draw_test(canvas, SkPaint::kRound_Cap, SkPaint::kRound_Join);
174 }
175