1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2022 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkM44.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRRect.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkVertices.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "tools/viewer/ClickHandlerSlide.h"
15*c8dee2aaSAndroid Build Coastguard Worker
16*c8dee2aaSAndroid Build Coastguard Worker #include <unordered_set>
17*c8dee2aaSAndroid Build Coastguard Worker
paint(SkColor color,float strokeWidth=-1.f,SkPaint::Join join=SkPaint::kMiter_Join)18*c8dee2aaSAndroid Build Coastguard Worker static SkPaint paint(SkColor color,
19*c8dee2aaSAndroid Build Coastguard Worker float strokeWidth = -1.f,
20*c8dee2aaSAndroid Build Coastguard Worker SkPaint::Join join = SkPaint::kMiter_Join) {
21*c8dee2aaSAndroid Build Coastguard Worker SkPaint paint;
22*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(color);
23*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(true);
24*c8dee2aaSAndroid Build Coastguard Worker if (strokeWidth >= 0.f) {
25*c8dee2aaSAndroid Build Coastguard Worker paint.setStyle(SkPaint::kStroke_Style);
26*c8dee2aaSAndroid Build Coastguard Worker paint.setStrokeWidth(strokeWidth);
27*c8dee2aaSAndroid Build Coastguard Worker paint.setStrokeJoin(join);
28*c8dee2aaSAndroid Build Coastguard Worker }
29*c8dee2aaSAndroid Build Coastguard Worker return paint;
30*c8dee2aaSAndroid Build Coastguard Worker }
31*c8dee2aaSAndroid Build Coastguard Worker
32*c8dee2aaSAndroid Build Coastguard Worker // Singular values for [a b][c d] 2x2 matrix, unordered.
singular_values(float a,float b,float c,float d)33*c8dee2aaSAndroid Build Coastguard Worker static std::pair<float, float> singular_values(float a, float b, float c, float d) {
34*c8dee2aaSAndroid Build Coastguard Worker float s1 = a*a + b*b + c*c + d*d;
35*c8dee2aaSAndroid Build Coastguard Worker
36*c8dee2aaSAndroid Build Coastguard Worker float e = a*a + b*b - c*c - d*d;
37*c8dee2aaSAndroid Build Coastguard Worker float f = a*c + b*d;
38*c8dee2aaSAndroid Build Coastguard Worker float s2 = SkScalarSqrt(e*e + 4*f*f);
39*c8dee2aaSAndroid Build Coastguard Worker
40*c8dee2aaSAndroid Build Coastguard Worker float singular1 = SkScalarSqrt(0.5f * (s1 + s2));
41*c8dee2aaSAndroid Build Coastguard Worker float singular2 = SkScalarSqrt(0.5f * (s1 - s2));
42*c8dee2aaSAndroid Build Coastguard Worker
43*c8dee2aaSAndroid Build Coastguard Worker return {singular1, singular2};
44*c8dee2aaSAndroid Build Coastguard Worker }
45*c8dee2aaSAndroid Build Coastguard Worker
46*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kAARadius = 10.f;
47*c8dee2aaSAndroid Build Coastguard Worker
48*c8dee2aaSAndroid Build Coastguard Worker // [m00 m01 * m03] [f(u,v)]
49*c8dee2aaSAndroid Build Coastguard Worker // Assuming M = [m10 m11 * m13], define the projected p'(u,v) = [g(u,v)] where
50*c8dee2aaSAndroid Build Coastguard Worker // [ * * * * ]
51*c8dee2aaSAndroid Build Coastguard Worker // [m30 m31 * m33]
52*c8dee2aaSAndroid Build Coastguard Worker // [x] [u]
53*c8dee2aaSAndroid Build Coastguard Worker // f(u,v) = x(u,v) / w(u,v), g(u,v) = y(u,v) / w(u,v) and [y] = M*[v]
54*c8dee2aaSAndroid Build Coastguard Worker // [*] = [0]
55*c8dee2aaSAndroid Build Coastguard Worker // [w] [1]
56*c8dee2aaSAndroid Build Coastguard Worker //
57*c8dee2aaSAndroid Build Coastguard Worker // x(u,v) = m00*u + m01*v + m03
58*c8dee2aaSAndroid Build Coastguard Worker // y(u,v) = m10*u + m11*v + m13
59*c8dee2aaSAndroid Build Coastguard Worker // w(u,v) = m30*u + m31*v + m33
60*c8dee2aaSAndroid Build Coastguard Worker //
61*c8dee2aaSAndroid Build Coastguard Worker // dx/du = m00, dx/dv = m01,
62*c8dee2aaSAndroid Build Coastguard Worker // dy/du = m10, dy/dv = m11
63*c8dee2aaSAndroid Build Coastguard Worker // dw/du = m30, dw/dv = m31
64*c8dee2aaSAndroid Build Coastguard Worker //
65*c8dee2aaSAndroid Build Coastguard Worker // df/du = (dx/du*w - x*dw/du)/w^2 = (m00*w - m30*x)/w^2
66*c8dee2aaSAndroid Build Coastguard Worker // df/dv = (dx/dv*w - x*dw/dv)/w^2 = (m01*w - m31*x)/w^2
67*c8dee2aaSAndroid Build Coastguard Worker // dg/du = (dy/du*w - y*dw/du)/w^2 = (m10*w - m30*y)/w^2
68*c8dee2aaSAndroid Build Coastguard Worker // dg/dv = (dy/dv*w - y*dw/du)/w^2 = (m11*w - m31*y)/w^2
69*c8dee2aaSAndroid Build Coastguard Worker //
70*c8dee2aaSAndroid Build Coastguard Worker // Singular values of [df/du df/dv] define perspective correct minimum and maximum scale factors
71*c8dee2aaSAndroid Build Coastguard Worker // [dg/du dg/dv]
72*c8dee2aaSAndroid Build Coastguard Worker // for M evaluated at (u,v)
local_aa_radius(const SkM44 & matrix,const SkV2 & p)73*c8dee2aaSAndroid Build Coastguard Worker static float local_aa_radius(const SkM44& matrix, const SkV2& p) {
74*c8dee2aaSAndroid Build Coastguard Worker SkV4 devP = matrix.map(p.x, p.y, 0.f, 1.f);
75*c8dee2aaSAndroid Build Coastguard Worker
76*c8dee2aaSAndroid Build Coastguard Worker const float dxdu = matrix.rc(0,0);
77*c8dee2aaSAndroid Build Coastguard Worker const float dxdv = matrix.rc(0,1);
78*c8dee2aaSAndroid Build Coastguard Worker const float dydu = matrix.rc(1,0);
79*c8dee2aaSAndroid Build Coastguard Worker const float dydv = matrix.rc(1,1);
80*c8dee2aaSAndroid Build Coastguard Worker const float dwdu = matrix.rc(3,0);
81*c8dee2aaSAndroid Build Coastguard Worker const float dwdv = matrix.rc(3,1);
82*c8dee2aaSAndroid Build Coastguard Worker
83*c8dee2aaSAndroid Build Coastguard Worker float invW2 = 1.f / (devP.w * devP.w);
84*c8dee2aaSAndroid Build Coastguard Worker // non-persp has invW2 = 1, devP.w = 1, dwdu = 0, dwdv = 0
85*c8dee2aaSAndroid Build Coastguard Worker float dfdu = (devP.w*dxdu - devP.x*dwdu) * invW2; // non-persp -> dxdu -> m00
86*c8dee2aaSAndroid Build Coastguard Worker float dfdv = (devP.w*dxdv - devP.x*dwdv) * invW2; // non-persp -> dxdv -> m01
87*c8dee2aaSAndroid Build Coastguard Worker float dgdu = (devP.w*dydu - devP.y*dwdu) * invW2; // non-persp -> dydu -> m10
88*c8dee2aaSAndroid Build Coastguard Worker float dgdv = (devP.w*dydv - devP.y*dwdv) * invW2; // non-persp -> dydv -> m11
89*c8dee2aaSAndroid Build Coastguard Worker
90*c8dee2aaSAndroid Build Coastguard Worker // no-persp, this is the singular values of [m00,m01][m10,m11], which is just the upper 2x2
91*c8dee2aaSAndroid Build Coastguard Worker // and equivalent to SkMatrix::getMinmaxScales().
92*c8dee2aaSAndroid Build Coastguard Worker auto [sv1, sv2] = singular_values(dfdu, dfdv, dgdu, dgdv);
93*c8dee2aaSAndroid Build Coastguard Worker
94*c8dee2aaSAndroid Build Coastguard Worker // The minimum and maximum singular values of the above matrix represent the min and maximum
95*c8dee2aaSAndroid Build Coastguard Worker // scale factors that could be applied by the 'matrix'. So if 'p' is moved 1px locally it will
96*c8dee2aaSAndroid Build Coastguard Worker // move between [min, max]px after transformation. Thus, moving 1/min px locally will move
97*c8dee2aaSAndroid Build Coastguard Worker // between [1, max/min]px after transformation, ensuring the device-space offset exceeds the
98*c8dee2aaSAndroid Build Coastguard Worker // minimum AA offset for analytic AA.
99*c8dee2aaSAndroid Build Coastguard Worker float minScale = std::min(sv1, sv2);
100*c8dee2aaSAndroid Build Coastguard Worker return kAARadius / minScale;
101*c8dee2aaSAndroid Build Coastguard Worker }
102*c8dee2aaSAndroid Build Coastguard Worker
103*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kMiterScale = 1.f;
104*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kBevelScale = 0.0f;
105*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kRoundScale = SK_FloatSqrt2 - 1.f;
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Worker struct LocalCornerVert {
108*c8dee2aaSAndroid Build Coastguard Worker SkV2 fPosition; // In unit square that each corner is normalized to
109*c8dee2aaSAndroid Build Coastguard Worker SkV2 fNormal; // Direction that AA outset is applied in
110*c8dee2aaSAndroid Build Coastguard Worker
111*c8dee2aaSAndroid Build Coastguard Worker float fStrokeScale; // Signed scale factor applied to external stroke radius, should be [-1,1]
112*c8dee2aaSAndroid Build Coastguard Worker float fMirrorScale; // Scale fPosition.yx, along with external join-scale, should be [0,1].
113*c8dee2aaSAndroid Build Coastguard Worker float fCenterWeight; // Added to external center scale, > 0 forces point to center instead.
114*c8dee2aaSAndroid Build Coastguard Worker
115*c8dee2aaSAndroid Build Coastguard Worker // 'cornerMapping' is a row-major 2x2 matrix [[x y], [z w]] to flip and rotate the normalized
116*c8dee2aaSAndroid Build Coastguard Worker // positions into the local coord space.
transformLocalCornerVert117*c8dee2aaSAndroid Build Coastguard Worker SkV3 transform(const SkM44& m, const SkV4& cornerMapping, const SkV2& cornerPt,
118*c8dee2aaSAndroid Build Coastguard Worker const SkV2& cornerRadii, const SkV4& devCenter, float centerWeight,
119*c8dee2aaSAndroid Build Coastguard Worker float strokeRadius, float joinScale, float localAARadius) const {
120*c8dee2aaSAndroid Build Coastguard Worker const bool snapToCenter = centerWeight + fCenterWeight > 0.f;
121*c8dee2aaSAndroid Build Coastguard Worker if (snapToCenter) {
122*c8dee2aaSAndroid Build Coastguard Worker return {devCenter.x, devCenter.y, devCenter.w};
123*c8dee2aaSAndroid Build Coastguard Worker } else {
124*c8dee2aaSAndroid Build Coastguard Worker // Normalized position before any additional AA offsets
125*c8dee2aaSAndroid Build Coastguard Worker SkV2 normalizedPos = fPosition + joinScale*fMirrorScale*SkV2{fPosition.y, fPosition.x};
126*c8dee2aaSAndroid Build Coastguard Worker // scales the normalized unit corner to the actual radii of the corner, before any AA
127*c8dee2aaSAndroid Build Coastguard Worker // offsets are added.
128*c8dee2aaSAndroid Build Coastguard Worker SkV2 scale = cornerRadii + SkV2{fStrokeScale*strokeRadius, fStrokeScale*strokeRadius};
129*c8dee2aaSAndroid Build Coastguard Worker normalizedPos = scale*normalizedPos - cornerRadii;
130*c8dee2aaSAndroid Build Coastguard Worker
131*c8dee2aaSAndroid Build Coastguard Worker if (fStrokeScale < 0.f) {
132*c8dee2aaSAndroid Build Coastguard Worker // An inset, which means it might cross over or might be forced to the center
133*c8dee2aaSAndroid Build Coastguard Worker SkV2 maxInset = scale - SkV2{localAARadius, localAARadius};
134*c8dee2aaSAndroid Build Coastguard Worker if (maxInset.x < 0.f || maxInset.y < 0.f) {
135*c8dee2aaSAndroid Build Coastguard Worker normalizedPos =
136*c8dee2aaSAndroid Build Coastguard Worker SkV2{std::min(maxInset.x, 0.f), std::min(maxInset.y, 0.f)}
137*c8dee2aaSAndroid Build Coastguard Worker - cornerRadii;
138*c8dee2aaSAndroid Build Coastguard Worker } else {
139*c8dee2aaSAndroid Build Coastguard Worker normalizedPos += localAARadius * fNormal;
140*c8dee2aaSAndroid Build Coastguard Worker }
141*c8dee2aaSAndroid Build Coastguard Worker } // else no normal offsetting, or device-space offsetting
142*c8dee2aaSAndroid Build Coastguard Worker
143*c8dee2aaSAndroid Build Coastguard Worker SkV2 localPos =
144*c8dee2aaSAndroid Build Coastguard Worker {cornerMapping.x*normalizedPos.x + cornerMapping.y*normalizedPos.y + cornerPt.x,
145*c8dee2aaSAndroid Build Coastguard Worker cornerMapping.z*normalizedPos.x + cornerMapping.w*normalizedPos.y + cornerPt.y};
146*c8dee2aaSAndroid Build Coastguard Worker SkV4 devPos = m.map(localPos.x, localPos.y, 0.f, 1.f);
147*c8dee2aaSAndroid Build Coastguard Worker
148*c8dee2aaSAndroid Build Coastguard Worker const bool deviceSpaceNormal =
149*c8dee2aaSAndroid Build Coastguard Worker fStrokeScale > 0.f && (fNormal.x > 0.f || fNormal.y > 0.f);
150*c8dee2aaSAndroid Build Coastguard Worker if (deviceSpaceNormal) {
151*c8dee2aaSAndroid Build Coastguard Worker SkV2 devNorm;
152*c8dee2aaSAndroid Build Coastguard Worker {
153*c8dee2aaSAndroid Build Coastguard Worker // To calculate a device-space normal, we use the normal matrix (A^-1)^T where
154*c8dee2aaSAndroid Build Coastguard Worker // A is CTM * T(cornerPt) * cornerMapping * scale. We inline the calculation
155*c8dee2aaSAndroid Build Coastguard Worker // of (T(cornerPt)*cornerMapping*scale)^-1^T * [nx, ny, 0, 0] = N', which means
156*c8dee2aaSAndroid Build Coastguard Worker // that CTM^-1^T * N' is equivalent to N'^T*CTM^-1, which can be calculated with
157*c8dee2aaSAndroid Build Coastguard Worker // two dot products if the CTM inverse is uploaded to the GPU.
158*c8dee2aaSAndroid Build Coastguard Worker
159*c8dee2aaSAndroid Build Coastguard Worker // We add epsilon so that rectangular corners are not degenerate, and circular
160*c8dee2aaSAndroid Build Coastguard Worker // corners remain unmodified. This only slightly increases inaccuracy for
161*c8dee2aaSAndroid Build Coastguard Worker // elliptical corners.
162*c8dee2aaSAndroid Build Coastguard Worker float sx = (scale.y + SK_ScalarNearlyZero) / (scale.x + SK_ScalarNearlyZero);
163*c8dee2aaSAndroid Build Coastguard Worker // Needed to calculate intermediate W of transformed normal.
164*c8dee2aaSAndroid Build Coastguard Worker float px = cornerMapping.y*cornerPt.y - cornerMapping.w*cornerPt.x;
165*c8dee2aaSAndroid Build Coastguard Worker float py = cornerMapping.z*cornerPt.x - cornerMapping.x*cornerPt.y;
166*c8dee2aaSAndroid Build Coastguard Worker // Inverse CTM, presumably calculated once as a uniform
167*c8dee2aaSAndroid Build Coastguard Worker SkM44 inv;
168*c8dee2aaSAndroid Build Coastguard Worker SkAssertResult(m.invert(&inv));
169*c8dee2aaSAndroid Build Coastguard Worker
170*c8dee2aaSAndroid Build Coastguard Worker SkV4 normX4 = { sx*cornerMapping.w*fNormal.x,
171*c8dee2aaSAndroid Build Coastguard Worker -sx*cornerMapping.y*fNormal.x,
172*c8dee2aaSAndroid Build Coastguard Worker 0.f,
173*c8dee2aaSAndroid Build Coastguard Worker sx*px*fNormal.x};
174*c8dee2aaSAndroid Build Coastguard Worker SkV4 normY4 = {-cornerMapping.z*fNormal.y,
175*c8dee2aaSAndroid Build Coastguard Worker cornerMapping.x*fNormal.y,
176*c8dee2aaSAndroid Build Coastguard Worker 0.f,
177*c8dee2aaSAndroid Build Coastguard Worker py*fNormal.y};
178*c8dee2aaSAndroid Build Coastguard Worker
179*c8dee2aaSAndroid Build Coastguard Worker SkV2 normX = {inv.col(0).dot(normX4), inv.col(1).dot(normX4)};
180*c8dee2aaSAndroid Build Coastguard Worker SkV2 normY = {inv.col(0).dot(normY4), inv.col(1).dot(normY4)};
181*c8dee2aaSAndroid Build Coastguard Worker
182*c8dee2aaSAndroid Build Coastguard Worker if (joinScale == kMiterScale && fNormal.x > 0.f && fNormal.y > 0.f) {
183*c8dee2aaSAndroid Build Coastguard Worker // normX and normY represent adjacent edges' normals, so if we normalize
184*c8dee2aaSAndroid Build Coastguard Worker // them before adding together, we'll have a vector that bisects the edge
185*c8dee2aaSAndroid Build Coastguard Worker // normals instead of a vector matching fNormal, which is what we want when
186*c8dee2aaSAndroid Build Coastguard Worker // we're at a miter corner.
187*c8dee2aaSAndroid Build Coastguard Worker normX = normX.normalize();
188*c8dee2aaSAndroid Build Coastguard Worker normY = normY.normalize();
189*c8dee2aaSAndroid Build Coastguard Worker if (normX.dot(normY) < -0.8) {
190*c8dee2aaSAndroid Build Coastguard Worker // Nearly opposite directions, so the sum could have cancellation, so
191*c8dee2aaSAndroid Build Coastguard Worker // instead bisect orthogonal vectors and flip to keep consistent
192*c8dee2aaSAndroid Build Coastguard Worker float sign = normX.cross(normY) >= 0.f ? 1.f : -1.f;
193*c8dee2aaSAndroid Build Coastguard Worker normX = sign*SkV2{-normX.y, normX.x};
194*c8dee2aaSAndroid Build Coastguard Worker normY = sign*SkV2{normY.y, -normY.x};
195*c8dee2aaSAndroid Build Coastguard Worker }
196*c8dee2aaSAndroid Build Coastguard Worker }
197*c8dee2aaSAndroid Build Coastguard Worker
198*c8dee2aaSAndroid Build Coastguard Worker devNorm = (normX + normY).normalize();
199*c8dee2aaSAndroid Build Coastguard Worker }
200*c8dee2aaSAndroid Build Coastguard Worker
201*c8dee2aaSAndroid Build Coastguard Worker // The local coordinates for a device-space AA outset are clamped to the non-outset
202*c8dee2aaSAndroid Build Coastguard Worker // point, which means we don't care about remaining in the same pre-homogenous
203*c8dee2aaSAndroid Build Coastguard Worker // divide plane. This makes it very easy to determine a homogenous coordinate that
204*c8dee2aaSAndroid Build Coastguard Worker // projects to the correct device-space position.
205*c8dee2aaSAndroid Build Coastguard Worker devPos.x += devPos.w * kAARadius * devNorm.x;
206*c8dee2aaSAndroid Build Coastguard Worker devPos.y += devPos.w * kAARadius * devNorm.y;
207*c8dee2aaSAndroid Build Coastguard Worker }
208*c8dee2aaSAndroid Build Coastguard Worker
209*c8dee2aaSAndroid Build Coastguard Worker return SkV3{devPos.x, devPos.y, devPos.w};
210*c8dee2aaSAndroid Build Coastguard Worker }
211*c8dee2aaSAndroid Build Coastguard Worker }
212*c8dee2aaSAndroid Build Coastguard Worker };
213*c8dee2aaSAndroid Build Coastguard Worker
214*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kHR2 = SK_ScalarRoot2Over2; // "half root 2"
215*c8dee2aaSAndroid Build Coastguard Worker
216*c8dee2aaSAndroid Build Coastguard Worker static constexpr LocalCornerVert kCornerTemplate[19] = {
217*c8dee2aaSAndroid Build Coastguard Worker // Stroke-scale should be -1, 0, or 1.
218*c8dee2aaSAndroid Build Coastguard Worker // Mirror-scale should be 0 or 1.
219*c8dee2aaSAndroid Build Coastguard Worker // Center-weight should be -2 to never snap to center, -1 to snap when stroke coords would
220*c8dee2aaSAndroid Build Coastguard Worker // overlap, and 0 to snap for fill-style or overlapping coords.
221*c8dee2aaSAndroid Build Coastguard Worker // Local-aa-scale should be 0 or 1.
222*c8dee2aaSAndroid Build Coastguard Worker
223*c8dee2aaSAndroid Build Coastguard Worker // position, normal, stroke-scale mirror-scale center-weight
224*c8dee2aaSAndroid Build Coastguard Worker // Device-space AA outsets from outer curve
225*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { 0.0f, 1.0f}, 1.0f, 0.0f, -2.f },
226*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { 0.0f, 1.0f}, 1.0f, 1.0f, -2.f },
227*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { kHR2, kHR2}, 1.0f, 1.0f, -2.f },
228*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, { kHR2, kHR2}, 1.0f, 1.0f, -2.f },
229*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, { 1.0f, 0.0f}, 1.0f, 1.0f, -2.f },
230*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, { 1.0f, 0.0f}, 1.0f, 0.0f, -2.f },
231*c8dee2aaSAndroid Build Coastguard Worker
232*c8dee2aaSAndroid Build Coastguard Worker // Outer anchors (no local or device-space normal outset)
233*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { 0.0f, 0.0f}, 1.0f, 0.0f, -2.f },
234*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { 0.0f, 0.0f}, 1.0f, 1.0f, -2.f },
235*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, { 0.0f, 0.0f}, 1.0f, 1.0f, -2.f },
236*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, { 0.0f, 0.0f}, 1.0f, 0.0f, -2.f },
237*c8dee2aaSAndroid Build Coastguard Worker
238*c8dee2aaSAndroid Build Coastguard Worker // Center of stroke (equivalent to outer anchors when filling)
239*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { 0.0f, 0.0f}, 0.0f, 0.0f, -2.f },
240*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { 0.0f, 0.0f}, 0.0f, 1.0f, -2.f },
241*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, { 0.0f, 0.0f}, 0.0f, 1.0f, -2.f },
242*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, { 0.0f, 0.0f}, 0.0f, 0.0f, -2.f },
243*c8dee2aaSAndroid Build Coastguard Worker
244*c8dee2aaSAndroid Build Coastguard Worker // Inner AA insets from inner curve
245*c8dee2aaSAndroid Build Coastguard Worker { {0.0f, 1.0f}, { 0.0f, -1.0f}, -1.0f, 0.0f, -1.f },
246*c8dee2aaSAndroid Build Coastguard Worker { {0.5f, 0.5f}, {-kHR2, -kHR2}, -1.0f, 1.0f, -1.f },
247*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, {-1.0f, 0.0f}, -1.0f, 0.0f, -1.f },
248*c8dee2aaSAndroid Build Coastguard Worker
249*c8dee2aaSAndroid Build Coastguard Worker // Center filling vertices (equal to inner AA insets unless center-weight = 1)
250*c8dee2aaSAndroid Build Coastguard Worker { {0.5f, 0.5f}, {-kHR2, -kHR2}, -1.0f, 1.0f, 0.f },
251*c8dee2aaSAndroid Build Coastguard Worker { {1.0f, 0.0f}, {-1.0f, 0.0f}, -1.0f, 0.0f, 0.f },
252*c8dee2aaSAndroid Build Coastguard Worker };
253*c8dee2aaSAndroid Build Coastguard Worker
compute_corner(SkV3 devPts[19],const SkM44 & m,const SkV4 & cornerMapping,const SkV2 & cornerPt,const SkV2 & cornerRadii,const SkV4 & center,float centerWeight,float localAARadius,float strokeRadius,SkPaint::Join join)254*c8dee2aaSAndroid Build Coastguard Worker static void compute_corner(SkV3 devPts[19], const SkM44& m, const SkV4& cornerMapping,
255*c8dee2aaSAndroid Build Coastguard Worker const SkV2& cornerPt, const SkV2& cornerRadii, const SkV4& center,
256*c8dee2aaSAndroid Build Coastguard Worker float centerWeight, float localAARadius, float strokeRadius,
257*c8dee2aaSAndroid Build Coastguard Worker SkPaint::Join join) {
258*c8dee2aaSAndroid Build Coastguard Worker float joinScale;
259*c8dee2aaSAndroid Build Coastguard Worker
260*c8dee2aaSAndroid Build Coastguard Worker // TODO: checking against localAARadius can snap to rect corner unexpectedly under high skew
261*c8dee2aaSAndroid Build Coastguard Worker // because localAARadius gets so big, but would be nice to be fuzzy here.
262*c8dee2aaSAndroid Build Coastguard Worker if (cornerRadii.x <= 0.f || cornerRadii.y <= 0.f) {
263*c8dee2aaSAndroid Build Coastguard Worker // Effectively a rectangular corner
264*c8dee2aaSAndroid Build Coastguard Worker joinScale = kMiterScale; // default for rect corners
265*c8dee2aaSAndroid Build Coastguard Worker if (strokeRadius > 0.f) {
266*c8dee2aaSAndroid Build Coastguard Worker // Non-hairline strokes need to adjust the join scale factor to match style.
267*c8dee2aaSAndroid Build Coastguard Worker if (join == SkPaint::kBevel_Join) {
268*c8dee2aaSAndroid Build Coastguard Worker joinScale = kBevelScale;
269*c8dee2aaSAndroid Build Coastguard Worker } else if (join == SkPaint::kRound_Join) {
270*c8dee2aaSAndroid Build Coastguard Worker joinScale = kRoundScale;
271*c8dee2aaSAndroid Build Coastguard Worker }
272*c8dee2aaSAndroid Build Coastguard Worker }
273*c8dee2aaSAndroid Build Coastguard Worker } else {
274*c8dee2aaSAndroid Build Coastguard Worker // Rounded filled corner vertices are always positioned for a round join since the
275*c8dee2aaSAndroid Build Coastguard Worker // underlying geometry has no real tangent discontinuity.
276*c8dee2aaSAndroid Build Coastguard Worker joinScale = kRoundScale;
277*c8dee2aaSAndroid Build Coastguard Worker }
278*c8dee2aaSAndroid Build Coastguard Worker
279*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < std::size(kCornerTemplate); ++i) {
280*c8dee2aaSAndroid Build Coastguard Worker devPts[i] = kCornerTemplate[i].transform(m, cornerMapping, cornerPt, cornerRadii,
281*c8dee2aaSAndroid Build Coastguard Worker center, centerWeight, strokeRadius, joinScale,
282*c8dee2aaSAndroid Build Coastguard Worker localAARadius);
283*c8dee2aaSAndroid Build Coastguard Worker }
284*c8dee2aaSAndroid Build Coastguard Worker }
285*c8dee2aaSAndroid Build Coastguard Worker
286*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kBR = 0*std::size(kCornerTemplate);
287*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kTR = 1*std::size(kCornerTemplate);
288*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kTL = 2*std::size(kCornerTemplate);
289*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kBL = 3*std::size(kCornerTemplate);
290*c8dee2aaSAndroid Build Coastguard Worker static const size_t kVertexCount = 4*std::size(kCornerTemplate);
compute_vertices(SkV3 devPts[kVertexCount],const SkM44 & m,const SkRRect & rrect,float strokeRadius,SkPaint::Join join)291*c8dee2aaSAndroid Build Coastguard Worker static void compute_vertices(SkV3 devPts[kVertexCount],
292*c8dee2aaSAndroid Build Coastguard Worker const SkM44& m,
293*c8dee2aaSAndroid Build Coastguard Worker const SkRRect& rrect,
294*c8dee2aaSAndroid Build Coastguard Worker float strokeRadius,
295*c8dee2aaSAndroid Build Coastguard Worker SkPaint::Join join) {
296*c8dee2aaSAndroid Build Coastguard Worker SkV4 devCenter = m.map(rrect.getBounds().centerX(), rrect.getBounds().centerY(), 0.f, 1.f);
297*c8dee2aaSAndroid Build Coastguard Worker
298*c8dee2aaSAndroid Build Coastguard Worker float localAARadius = std::max({
299*c8dee2aaSAndroid Build Coastguard Worker local_aa_radius(m, {rrect.getBounds().fRight, rrect.getBounds().fBottom}),
300*c8dee2aaSAndroid Build Coastguard Worker local_aa_radius(m, {rrect.getBounds().fRight, rrect.getBounds().fTop}),
301*c8dee2aaSAndroid Build Coastguard Worker local_aa_radius(m, {rrect.getBounds().fLeft, rrect.getBounds().fTop}),
302*c8dee2aaSAndroid Build Coastguard Worker local_aa_radius(m, {rrect.getBounds().fLeft, rrect.getBounds().fBottom})
303*c8dee2aaSAndroid Build Coastguard Worker });
304*c8dee2aaSAndroid Build Coastguard Worker
305*c8dee2aaSAndroid Build Coastguard Worker float centerWeight = 0.f; // No center snapping
306*c8dee2aaSAndroid Build Coastguard Worker if (strokeRadius < 0.f) {
307*c8dee2aaSAndroid Build Coastguard Worker // A fill, so inner vertices need to snap to the center and then adjust the stroke radius
308*c8dee2aaSAndroid Build Coastguard Worker // to 0 for later math to work out nicely.
309*c8dee2aaSAndroid Build Coastguard Worker strokeRadius = 0.f;
310*c8dee2aaSAndroid Build Coastguard Worker centerWeight = 1.f;
311*c8dee2aaSAndroid Build Coastguard Worker }
312*c8dee2aaSAndroid Build Coastguard Worker
313*c8dee2aaSAndroid Build Coastguard Worker // Check if the inset amount (max stroke-radius + local-aa-radius) would interfere with the
314*c8dee2aaSAndroid Build Coastguard Worker // opposite edge's inset or interfere with the adjacent corner's curve. When this happens, snap
315*c8dee2aaSAndroid Build Coastguard Worker // all the interior vertices to the center and let the fragment shader work through it.
316*c8dee2aaSAndroid Build Coastguard Worker // TODO: Could force centerWeight = 2 for filled rects and quads for simplicity around non
317*c8dee2aaSAndroid Build Coastguard Worker // orthogonal inset overlap calculations.
318*c8dee2aaSAndroid Build Coastguard Worker float maxInset = strokeRadius + localAARadius;
319*c8dee2aaSAndroid Build Coastguard Worker if (maxInset >= rrect.width() - maxInset || // L/R stroke insets would cross over
320*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.height() - maxInset || // T/B stroke insets would cross over
321*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerLeft_Corner).fX || // X corner cross
322*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerRight_Corner).fX ||
323*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperLeft_Corner).fX ||
324*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperRight_Corner).fX ||
325*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerLeft_Corner).fY || // Y corner cross
326*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerRight_Corner).fY ||
327*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperLeft_Corner).fY ||
328*c8dee2aaSAndroid Build Coastguard Worker maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperRight_Corner).fY) {
329*c8dee2aaSAndroid Build Coastguard Worker // All interior vertices need to snap to the center
330*c8dee2aaSAndroid Build Coastguard Worker centerWeight = 2.f;
331*c8dee2aaSAndroid Build Coastguard Worker }
332*c8dee2aaSAndroid Build Coastguard Worker
333*c8dee2aaSAndroid Build Coastguard Worker // The normalized corner template is defined relative to the quarter circle with positive X
334*c8dee2aaSAndroid Build Coastguard Worker // and positive Y, with a counter clockwise winding (if +Y points down). This corresponds to
335*c8dee2aaSAndroid Build Coastguard Worker // the bottom-right corner.
336*c8dee2aaSAndroid Build Coastguard Worker static constexpr SkV4 kBRBasis = { 1.f, 0.f, 0.f, 1.f};
337*c8dee2aaSAndroid Build Coastguard Worker static constexpr SkV4 kTRBasis = { 0.f, 1.f, -1.f, 0.f};
338*c8dee2aaSAndroid Build Coastguard Worker static constexpr SkV4 kTLBasis = {-1.f, 0.f, 0.f, -1.f};
339*c8dee2aaSAndroid Build Coastguard Worker static constexpr SkV4 kBLBasis = { 0.f, -1.f, 1.f, 0.f};
340*c8dee2aaSAndroid Build Coastguard Worker
341*c8dee2aaSAndroid Build Coastguard Worker compute_corner(devPts + kBR, m, kBRBasis,
342*c8dee2aaSAndroid Build Coastguard Worker {rrect.getBounds().fRight,
343*c8dee2aaSAndroid Build Coastguard Worker rrect.getBounds().fBottom},
344*c8dee2aaSAndroid Build Coastguard Worker {rrect.radii(SkRRect::kLowerRight_Corner).fX,
345*c8dee2aaSAndroid Build Coastguard Worker rrect.radii(SkRRect::kLowerRight_Corner).fY},
346*c8dee2aaSAndroid Build Coastguard Worker devCenter, centerWeight, localAARadius, strokeRadius, join);
347*c8dee2aaSAndroid Build Coastguard Worker compute_corner(devPts + kTR, m, kTRBasis,
348*c8dee2aaSAndroid Build Coastguard Worker {rrect.getBounds().fRight,
349*c8dee2aaSAndroid Build Coastguard Worker rrect.getBounds().fTop},
350*c8dee2aaSAndroid Build Coastguard Worker {rrect.radii(SkRRect::kUpperRight_Corner).fY,
351*c8dee2aaSAndroid Build Coastguard Worker rrect.radii(SkRRect::kUpperRight_Corner).fX},
352*c8dee2aaSAndroid Build Coastguard Worker devCenter, centerWeight, localAARadius,strokeRadius, join);
353*c8dee2aaSAndroid Build Coastguard Worker compute_corner(devPts + kTL, m, kTLBasis,
354*c8dee2aaSAndroid Build Coastguard Worker {rrect.getBounds().fLeft,
355*c8dee2aaSAndroid Build Coastguard Worker rrect.getBounds().fTop},
356*c8dee2aaSAndroid Build Coastguard Worker {rrect.radii(SkRRect::kUpperLeft_Corner).fX,
357*c8dee2aaSAndroid Build Coastguard Worker rrect.radii(SkRRect::kUpperLeft_Corner).fY},
358*c8dee2aaSAndroid Build Coastguard Worker devCenter, centerWeight, localAARadius,strokeRadius, join);
359*c8dee2aaSAndroid Build Coastguard Worker compute_corner(devPts + kBL, m, kBLBasis,
360*c8dee2aaSAndroid Build Coastguard Worker {rrect.getBounds().fLeft,
361*c8dee2aaSAndroid Build Coastguard Worker rrect.getBounds().fBottom},
362*c8dee2aaSAndroid Build Coastguard Worker {rrect.radii(SkRRect::kLowerLeft_Corner).fY,
363*c8dee2aaSAndroid Build Coastguard Worker rrect.radii(SkRRect::kLowerLeft_Corner).fX},
364*c8dee2aaSAndroid Build Coastguard Worker devCenter, centerWeight, localAARadius,strokeRadius, join);
365*c8dee2aaSAndroid Build Coastguard Worker }
366*c8dee2aaSAndroid Build Coastguard Worker
367*c8dee2aaSAndroid Build Coastguard Worker // All indices
368*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kIndices[] = {
369*c8dee2aaSAndroid Build Coastguard Worker // Exterior AA ramp outset
370*c8dee2aaSAndroid Build Coastguard Worker kBR+0,kBR+6,kBR+1,kBR+7,kBR+2,kBR+8,kBR+3,kBR+8,kBR+4,kBR+9,kBR+5,kBR+9,
371*c8dee2aaSAndroid Build Coastguard Worker kTR+0,kTR+6,kTR+1,kTR+7,kTR+2,kTR+8,kTR+3,kTR+8,kTR+4,kTR+9,kTR+5,kTR+9,
372*c8dee2aaSAndroid Build Coastguard Worker kTL+0,kTL+6,kTL+1,kTL+7,kTL+2,kTL+8,kTL+3,kTL+8,kTL+4,kTL+9,kTL+5,kTL+9,
373*c8dee2aaSAndroid Build Coastguard Worker kBL+0,kBL+6,kBL+1,kBL+7,kBL+2,kBL+8,kBL+3,kBL+8,kBL+4,kBL+9,kBL+5,kBL+9,
374*c8dee2aaSAndroid Build Coastguard Worker kBR+0,kBR+6,kBR+6, // close and extra vertex to jump to next strip
375*c8dee2aaSAndroid Build Coastguard Worker // Outer to central curve
376*c8dee2aaSAndroid Build Coastguard Worker kBR+6,kBR+10,kBR+7,kBR+11,kBR+8,kBR+12,kBR+9,kBR+13,
377*c8dee2aaSAndroid Build Coastguard Worker kTR+6,kTR+10,kTR+7,kTR+11,kTR+8,kTR+12,kTR+9,kTR+13,
378*c8dee2aaSAndroid Build Coastguard Worker kTL+6,kTL+10,kTL+7,kTL+11,kTL+8,kTL+12,kTL+9,kTL+13,
379*c8dee2aaSAndroid Build Coastguard Worker kBL+6,kBL+10,kBL+7,kBL+11,kBL+8,kBL+12,kBL+9,kBL+13,
380*c8dee2aaSAndroid Build Coastguard Worker kBR+6,kBR+10,kBR+10, // close and extra vertex to jump to next strip
381*c8dee2aaSAndroid Build Coastguard Worker // Center to inner curve's insets
382*c8dee2aaSAndroid Build Coastguard Worker kBR+10,kBR+14,kBR+11,kBR+15,kBR+12,kBR+16,kBR+13,kBR+16,
383*c8dee2aaSAndroid Build Coastguard Worker kTR+10,kTR+14,kTR+11,kTR+15,kTR+12,kTR+16,kTR+13,kTR+16,
384*c8dee2aaSAndroid Build Coastguard Worker kTL+10,kTL+14,kTL+11,kTL+15,kTL+12,kTL+16,kTL+13,kTL+16,
385*c8dee2aaSAndroid Build Coastguard Worker kBL+10,kBL+14,kBL+11,kBL+15,kBL+12,kBL+16,kBL+13,kBL+16,
386*c8dee2aaSAndroid Build Coastguard Worker kBR+10,kBR+14,kBR+14, // close and extra vertex to jump to next strip
387*c8dee2aaSAndroid Build Coastguard Worker // Inner inset to center of shape
388*c8dee2aaSAndroid Build Coastguard Worker kBR+14,kBR+17,kBR+15,kBR+17,kBR+16,kBR+16,kBR+18,kTR+14,
389*c8dee2aaSAndroid Build Coastguard Worker kTR+14,kTR+17,kTR+15,kTR+17,kTR+16,kTR+16,kTR+18,kTL+14,
390*c8dee2aaSAndroid Build Coastguard Worker kTL+14,kTL+17,kTL+15,kTL+17,kTL+16,kTL+16,kTL+18,kBL+14,
391*c8dee2aaSAndroid Build Coastguard Worker kBL+14,kBL+17,kBL+15,kBL+17,kBL+16,kBL+16,kBL+18,kBR+14 // close
392*c8dee2aaSAndroid Build Coastguard Worker };
393*c8dee2aaSAndroid Build Coastguard Worker
394*c8dee2aaSAndroid Build Coastguard Worker // Separated to draw with different colors (vs. duplicating vertices to change colors).
395*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kOuterCornerIndices[] = {
396*c8dee2aaSAndroid Build Coastguard Worker kBR+0, kBR+0,kBR+6,kBR+1,kBR+7,kBR+2,kBR+8,kBR+3,kBR+8,kBR+4,kBR+9,kBR+5, kBR+5,
397*c8dee2aaSAndroid Build Coastguard Worker kTR+0, kTR+0,kTR+6,kTR+1,kTR+7,kTR+2,kTR+8,kTR+3,kTR+8,kTR+4,kTR+9,kTR+5, kTR+5,
398*c8dee2aaSAndroid Build Coastguard Worker kTL+0, kTL+0,kTL+6,kTL+1,kTL+7,kTL+2,kTL+8,kTL+3,kTL+8,kTL+4,kTL+9,kTL+5, kTL+5,
399*c8dee2aaSAndroid Build Coastguard Worker kBL+0, kBL+0,kBL+6,kBL+1,kBL+7,kBL+2,kBL+8,kBL+3,kBL+8,kBL+4,kBL+9,kBL+5, kBL+5,
400*c8dee2aaSAndroid Build Coastguard Worker
401*c8dee2aaSAndroid Build Coastguard Worker kBR+6, kBR+6,kBR+10,kBR+7,kBR+11,kBR+8,kBR+12,kBR+9,kBR+13, kBR+13,
402*c8dee2aaSAndroid Build Coastguard Worker kTR+6, kTR+6,kTR+10,kTR+7,kTR+11,kTR+8,kTR+12,kTR+9,kTR+13, kTR+13,
403*c8dee2aaSAndroid Build Coastguard Worker kTL+6, kTL+6,kTL+10,kTL+7,kTL+11,kTL+8,kTL+12,kTL+9,kTL+13, kTL+13,
404*c8dee2aaSAndroid Build Coastguard Worker kBL+6, kBL+6,kBL+10,kBL+7,kBL+11,kBL+8,kBL+12,kBL+9,kBL+13, kBL+13
405*c8dee2aaSAndroid Build Coastguard Worker };
406*c8dee2aaSAndroid Build Coastguard Worker
407*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kInnerCornerIndices[] = {
408*c8dee2aaSAndroid Build Coastguard Worker kBR+10, kBR+10,kBR+14,kBR+11,kBR+15,kBR+12,kBR+16,kBR+13, kBR+13,
409*c8dee2aaSAndroid Build Coastguard Worker kTR+10, kTR+10,kTR+14,kTR+11,kTR+15,kTR+12,kTR+16,kTR+13, kTR+13,
410*c8dee2aaSAndroid Build Coastguard Worker kTL+10, kTL+10,kTL+14,kTL+11,kTL+15,kTL+12,kTL+16,kTL+13, kTL+13,
411*c8dee2aaSAndroid Build Coastguard Worker kBL+10, kBL+10,kBL+14,kBL+11,kBL+15,kBL+12,kBL+16,kBL+13, kBL+13,
412*c8dee2aaSAndroid Build Coastguard Worker };
413*c8dee2aaSAndroid Build Coastguard Worker
414*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kInteriorIndices[] = {
415*c8dee2aaSAndroid Build Coastguard Worker kBR+14,kBR+17,kBR+15,kBR+17,kBR+16,kBR+16,kBR+18,kTR+14,
416*c8dee2aaSAndroid Build Coastguard Worker kTR+14,kTR+17,kTR+15,kTR+17,kTR+16,kTR+16,kTR+18,kTL+14,
417*c8dee2aaSAndroid Build Coastguard Worker kTL+14,kTL+17,kTL+15,kTL+17,kTL+16,kTL+16,kTL+18,kBL+14,
418*c8dee2aaSAndroid Build Coastguard Worker kBL+14,kBL+17,kBL+15,kBL+17,kBL+16,kBL+16,kBL+18,kBR+14 // close
419*c8dee2aaSAndroid Build Coastguard Worker };
420*c8dee2aaSAndroid Build Coastguard Worker
421*c8dee2aaSAndroid Build Coastguard Worker // Implicit in the original mesh from the tri-strip connections between corners
422*c8dee2aaSAndroid Build Coastguard Worker static const uint16_t kEdgeIndices[] = {
423*c8dee2aaSAndroid Build Coastguard Worker kBR+5, kBR+5,kBR+9,kTR+0,kTR+6, kTR+6,
424*c8dee2aaSAndroid Build Coastguard Worker kBR+9, kBR+9,kBR+13,kTR+6,kTR+10, kTR+10,
425*c8dee2aaSAndroid Build Coastguard Worker kBR+13, kBR+13,kBR+16,kTR+10,kTR+14, kTR+14,
426*c8dee2aaSAndroid Build Coastguard Worker
427*c8dee2aaSAndroid Build Coastguard Worker kTR+5, kTR+5,kTR+9,kTL+0,kTL+6, kTL+6,
428*c8dee2aaSAndroid Build Coastguard Worker kTR+9, kTR+9,kTR+13,kTL+6,kTL+10, kTL+10,
429*c8dee2aaSAndroid Build Coastguard Worker kTR+13, kTR+13,kTR+16,kTL+10,kTL+14, kTL+14,
430*c8dee2aaSAndroid Build Coastguard Worker
431*c8dee2aaSAndroid Build Coastguard Worker kTL+5, kTL+5,kTL+9,kBL+0,kBL+6, kBL+6,
432*c8dee2aaSAndroid Build Coastguard Worker kTL+9, kTL+9,kTL+13,kBL+6,kBL+10, kBL+10,
433*c8dee2aaSAndroid Build Coastguard Worker kTL+13, kTL+13,kTL+16,kBL+10,kBL+14, kBL+14,
434*c8dee2aaSAndroid Build Coastguard Worker
435*c8dee2aaSAndroid Build Coastguard Worker kBL+5, kBL+5,kBL+9,kBR+0,kBR+6, kBR+6,
436*c8dee2aaSAndroid Build Coastguard Worker kBL+9, kBL+9,kBL+13,kBR+6,kBR+10, kBR+10,
437*c8dee2aaSAndroid Build Coastguard Worker kBL+13, kBL+13,kBL+16,kBR+10,kBR+14, kBR+14,
438*c8dee2aaSAndroid Build Coastguard Worker };
439*c8dee2aaSAndroid Build Coastguard Worker
440*c8dee2aaSAndroid Build Coastguard Worker class GraphitePrimitivesSlide : public ClickHandlerSlide {
441*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kControlPointRadius = 3.f;
442*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kBaseScale = 50.f;
443*c8dee2aaSAndroid Build Coastguard Worker
444*c8dee2aaSAndroid Build Coastguard Worker public:
GraphitePrimitivesSlide()445*c8dee2aaSAndroid Build Coastguard Worker GraphitePrimitivesSlide()
446*c8dee2aaSAndroid Build Coastguard Worker : fOrigin{300.f, 300.f}
447*c8dee2aaSAndroid Build Coastguard Worker , fXAxisPoint{300.f + kBaseScale, 300.f}
448*c8dee2aaSAndroid Build Coastguard Worker , fYAxisPoint{300.f, 300.f + kBaseScale}
449*c8dee2aaSAndroid Build Coastguard Worker , fStrokeWidth{10.f}
450*c8dee2aaSAndroid Build Coastguard Worker , fJoinMode(SkPaint::kMiter_Join)
451*c8dee2aaSAndroid Build Coastguard Worker , fMode(PrimitiveMode::kFillRect) {
452*c8dee2aaSAndroid Build Coastguard Worker fName = "GraphitePrimitives";
453*c8dee2aaSAndroid Build Coastguard Worker }
454*c8dee2aaSAndroid Build Coastguard Worker
draw(SkCanvas * canvas)455*c8dee2aaSAndroid Build Coastguard Worker void draw(SkCanvas* canvas) override {
456*c8dee2aaSAndroid Build Coastguard Worker canvas->save();
457*c8dee2aaSAndroid Build Coastguard Worker SkM44 viewMatrix = canvas->getLocalToDevice();
458*c8dee2aaSAndroid Build Coastguard Worker
459*c8dee2aaSAndroid Build Coastguard Worker canvas->concat(this->basisMatrix());
460*c8dee2aaSAndroid Build Coastguard Worker
461*c8dee2aaSAndroid Build Coastguard Worker SkM44 totalMatrix = canvas->getLocalToDevice();
462*c8dee2aaSAndroid Build Coastguard Worker
463*c8dee2aaSAndroid Build Coastguard Worker // Base shape + style
464*c8dee2aaSAndroid Build Coastguard Worker SkRRect rrect = this->primitiveShape();
465*c8dee2aaSAndroid Build Coastguard Worker canvas->drawRRect(rrect, paint(SK_ColorBLUE, this->strokeWidth(), fJoinMode));
466*c8dee2aaSAndroid Build Coastguard Worker canvas->restore();
467*c8dee2aaSAndroid Build Coastguard Worker
468*c8dee2aaSAndroid Build Coastguard Worker canvas->save();
469*c8dee2aaSAndroid Build Coastguard Worker canvas->resetMatrix();
470*c8dee2aaSAndroid Build Coastguard Worker // Draw the full mesh directly in device space
471*c8dee2aaSAndroid Build Coastguard Worker this->drawVertices(canvas, totalMatrix);
472*c8dee2aaSAndroid Build Coastguard Worker // Draw the controls in device space so we get consistent circles for the click points.
473*c8dee2aaSAndroid Build Coastguard Worker SkV4 origin = viewMatrix.map(fOrigin.x, fOrigin.y, 0.f, 1.f);
474*c8dee2aaSAndroid Build Coastguard Worker SkV4 xAxis = viewMatrix.map(fXAxisPoint.x, fXAxisPoint.y, 0.f, 1.f);
475*c8dee2aaSAndroid Build Coastguard Worker SkV4 yAxis = viewMatrix.map(fYAxisPoint.x, fYAxisPoint.y, 0.f, 1.f);
476*c8dee2aaSAndroid Build Coastguard Worker
477*c8dee2aaSAndroid Build Coastguard Worker // Axes
478*c8dee2aaSAndroid Build Coastguard Worker canvas->drawLine({origin.x/origin.w, origin.y/origin.w},
479*c8dee2aaSAndroid Build Coastguard Worker {xAxis.x/xAxis.w, xAxis.y/xAxis.w}, paint(SK_ColorRED, 0.f));
480*c8dee2aaSAndroid Build Coastguard Worker canvas->drawLine({origin.x/origin.w, origin.y/origin.w},
481*c8dee2aaSAndroid Build Coastguard Worker {yAxis.x/yAxis.w, yAxis.y/yAxis.w}, paint(SK_ColorGREEN, 0.f));
482*c8dee2aaSAndroid Build Coastguard Worker
483*c8dee2aaSAndroid Build Coastguard Worker // Control points
484*c8dee2aaSAndroid Build Coastguard Worker canvas->drawCircle({origin.x/origin.w, origin.y/origin.w},
485*c8dee2aaSAndroid Build Coastguard Worker kControlPointRadius, paint(SK_ColorBLACK));
486*c8dee2aaSAndroid Build Coastguard Worker canvas->drawCircle({xAxis.x/xAxis.w, xAxis.y/xAxis.w},
487*c8dee2aaSAndroid Build Coastguard Worker kControlPointRadius, paint(SK_ColorRED));
488*c8dee2aaSAndroid Build Coastguard Worker canvas->drawCircle({yAxis.x/yAxis.w, yAxis.y/yAxis.w},
489*c8dee2aaSAndroid Build Coastguard Worker kControlPointRadius, paint(SK_ColorGREEN));
490*c8dee2aaSAndroid Build Coastguard Worker canvas->restore();
491*c8dee2aaSAndroid Build Coastguard Worker }
492*c8dee2aaSAndroid Build Coastguard Worker
493*c8dee2aaSAndroid Build Coastguard Worker bool onChar(SkUnichar) override;
494*c8dee2aaSAndroid Build Coastguard Worker
495*c8dee2aaSAndroid Build Coastguard Worker protected:
496*c8dee2aaSAndroid Build Coastguard Worker Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
497*c8dee2aaSAndroid Build Coastguard Worker bool onClick(Click*) override;
498*c8dee2aaSAndroid Build Coastguard Worker
499*c8dee2aaSAndroid Build Coastguard Worker private:
500*c8dee2aaSAndroid Build Coastguard Worker class Click;
501*c8dee2aaSAndroid Build Coastguard Worker
502*c8dee2aaSAndroid Build Coastguard Worker enum class PrimitiveMode {
503*c8dee2aaSAndroid Build Coastguard Worker kFillRect,
504*c8dee2aaSAndroid Build Coastguard Worker kFillRRect,
505*c8dee2aaSAndroid Build Coastguard Worker kStrokeRect,
506*c8dee2aaSAndroid Build Coastguard Worker kStrokeRRect
507*c8dee2aaSAndroid Build Coastguard Worker };
508*c8dee2aaSAndroid Build Coastguard Worker
509*c8dee2aaSAndroid Build Coastguard Worker // Computed from 3 control points. Concat with CTM to get total matrix.
basisMatrix() const510*c8dee2aaSAndroid Build Coastguard Worker SkM44 basisMatrix() const {
511*c8dee2aaSAndroid Build Coastguard Worker SkV2 xAxis = (fXAxisPoint - fOrigin) / kBaseScale;
512*c8dee2aaSAndroid Build Coastguard Worker SkV2 yAxis = (fYAxisPoint - fOrigin) / kBaseScale;
513*c8dee2aaSAndroid Build Coastguard Worker
514*c8dee2aaSAndroid Build Coastguard Worker return SkM44::Cols({xAxis.x, xAxis.y, 0.f, 0.f},
515*c8dee2aaSAndroid Build Coastguard Worker {yAxis.x, yAxis.y, 0.f, 0.f},
516*c8dee2aaSAndroid Build Coastguard Worker {0.f, 0.f, 1.f, 0.f},
517*c8dee2aaSAndroid Build Coastguard Worker {fOrigin.x, fOrigin.y, 0.f, 1.f});
518*c8dee2aaSAndroid Build Coastguard Worker }
519*c8dee2aaSAndroid Build Coastguard Worker
strokeWidth() const520*c8dee2aaSAndroid Build Coastguard Worker float strokeWidth() const {
521*c8dee2aaSAndroid Build Coastguard Worker if (fMode == PrimitiveMode::kFillRect || fMode == PrimitiveMode::kFillRRect) {
522*c8dee2aaSAndroid Build Coastguard Worker return -1.f;
523*c8dee2aaSAndroid Build Coastguard Worker }
524*c8dee2aaSAndroid Build Coastguard Worker return fStrokeWidth;
525*c8dee2aaSAndroid Build Coastguard Worker }
526*c8dee2aaSAndroid Build Coastguard Worker
primitiveShape() const527*c8dee2aaSAndroid Build Coastguard Worker SkRRect primitiveShape() const {
528*c8dee2aaSAndroid Build Coastguard Worker static const SkRect kOuterBounds = SkRect::MakeLTRB(-kBaseScale, -kBaseScale,
529*c8dee2aaSAndroid Build Coastguard Worker kBaseScale, kBaseScale);
530*c8dee2aaSAndroid Build Coastguard Worker // Filled rounded rects can have arbitrary corners
531*c8dee2aaSAndroid Build Coastguard Worker static const SkVector kOuterRadii[4] = { { 0.25f * kBaseScale, 0.75f * kBaseScale },
532*c8dee2aaSAndroid Build Coastguard Worker { 0.f, 0.f},
533*c8dee2aaSAndroid Build Coastguard Worker { 0.5f * kBaseScale, 0.5f * kBaseScale },
534*c8dee2aaSAndroid Build Coastguard Worker { 0.75f * kBaseScale, 0.25f * kBaseScale } };
535*c8dee2aaSAndroid Build Coastguard Worker // // Stroked rounded rects will only have circular corners
536*c8dee2aaSAndroid Build Coastguard Worker static const SkVector kStrokeRadii[4] = { { 0.25f * kBaseScale, 0.25f * kBaseScale },
537*c8dee2aaSAndroid Build Coastguard Worker { 0.f, 0.f },
538*c8dee2aaSAndroid Build Coastguard Worker { 0.5f * kBaseScale, 0.5f * kBaseScale },
539*c8dee2aaSAndroid Build Coastguard Worker { 0.75f * kBaseScale, 0.75f * kBaseScale } };
540*c8dee2aaSAndroid Build Coastguard Worker
541*c8dee2aaSAndroid Build Coastguard Worker float strokeRadius = 0.5f * fStrokeWidth;
542*c8dee2aaSAndroid Build Coastguard Worker switch(fMode) {
543*c8dee2aaSAndroid Build Coastguard Worker case PrimitiveMode::kFillRect:
544*c8dee2aaSAndroid Build Coastguard Worker return SkRRect::MakeRect(kOuterBounds.makeOutset(strokeRadius, strokeRadius));
545*c8dee2aaSAndroid Build Coastguard Worker case PrimitiveMode::kFillRRect: {
546*c8dee2aaSAndroid Build Coastguard Worker SkRRect rrect;
547*c8dee2aaSAndroid Build Coastguard Worker rrect.setRectRadii(kOuterBounds, kOuterRadii);
548*c8dee2aaSAndroid Build Coastguard Worker rrect.outset(strokeRadius, strokeRadius);
549*c8dee2aaSAndroid Build Coastguard Worker return rrect; }
550*c8dee2aaSAndroid Build Coastguard Worker case PrimitiveMode::kStrokeRect:
551*c8dee2aaSAndroid Build Coastguard Worker return SkRRect::MakeRect(kOuterBounds);
552*c8dee2aaSAndroid Build Coastguard Worker case PrimitiveMode::kStrokeRRect: {
553*c8dee2aaSAndroid Build Coastguard Worker SkRRect rrect;
554*c8dee2aaSAndroid Build Coastguard Worker rrect.setRectRadii(kOuterBounds, kStrokeRadii);
555*c8dee2aaSAndroid Build Coastguard Worker return rrect;
556*c8dee2aaSAndroid Build Coastguard Worker }
557*c8dee2aaSAndroid Build Coastguard Worker }
558*c8dee2aaSAndroid Build Coastguard Worker
559*c8dee2aaSAndroid Build Coastguard Worker SkUNREACHABLE;
560*c8dee2aaSAndroid Build Coastguard Worker }
561*c8dee2aaSAndroid Build Coastguard Worker
drawVertices(SkCanvas * canvas,const SkM44 & ctm)562*c8dee2aaSAndroid Build Coastguard Worker void drawVertices(SkCanvas* canvas, const SkM44& ctm) {
563*c8dee2aaSAndroid Build Coastguard Worker SkRRect rrect = this->primitiveShape();
564*c8dee2aaSAndroid Build Coastguard Worker float strokeRadius = 0.5f * this->strokeWidth();
565*c8dee2aaSAndroid Build Coastguard Worker
566*c8dee2aaSAndroid Build Coastguard Worker SkV3 points[kVertexCount];
567*c8dee2aaSAndroid Build Coastguard Worker SkPoint vertices[kVertexCount];
568*c8dee2aaSAndroid Build Coastguard Worker compute_vertices(points, ctm, rrect, strokeRadius, fJoinMode);
569*c8dee2aaSAndroid Build Coastguard Worker // SkCanvas::drawVertices() wants SkPoint, but normally we'd let the GPU handle the
570*c8dee2aaSAndroid Build Coastguard Worker // perspective division and clipping.
571*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kVertexCount; ++i) {
572*c8dee2aaSAndroid Build Coastguard Worker vertices[i] = SkPoint{points[i].x/points[i].z, points[i].y/points[i].z};
573*c8dee2aaSAndroid Build Coastguard Worker }
574*c8dee2aaSAndroid Build Coastguard Worker
575*c8dee2aaSAndroid Build Coastguard Worker auto drawMeshSubset = [vertices, canvas](SkColor color,
576*c8dee2aaSAndroid Build Coastguard Worker const uint16_t* indices,
577*c8dee2aaSAndroid Build Coastguard Worker size_t indexCount) {
578*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkVertices> mesh = SkVertices::MakeCopy(
579*c8dee2aaSAndroid Build Coastguard Worker SkVertices::kTriangleStrip_VertexMode, kVertexCount, vertices,
580*c8dee2aaSAndroid Build Coastguard Worker nullptr, nullptr, (int) indexCount, indices);
581*c8dee2aaSAndroid Build Coastguard Worker SkPaint meshPaint;
582*c8dee2aaSAndroid Build Coastguard Worker meshPaint.setColor(color);
583*c8dee2aaSAndroid Build Coastguard Worker meshPaint.setAlphaf(0.5f);
584*c8dee2aaSAndroid Build Coastguard Worker canvas->drawVertices(mesh, SkBlendMode::kSrc, meshPaint);
585*c8dee2aaSAndroid Build Coastguard Worker };
586*c8dee2aaSAndroid Build Coastguard Worker if (fColorize) {
587*c8dee2aaSAndroid Build Coastguard Worker drawMeshSubset(SK_ColorGRAY,
588*c8dee2aaSAndroid Build Coastguard Worker kEdgeIndices,
589*c8dee2aaSAndroid Build Coastguard Worker std::size(kEdgeIndices));
590*c8dee2aaSAndroid Build Coastguard Worker drawMeshSubset(SK_ColorDKGRAY,
591*c8dee2aaSAndroid Build Coastguard Worker kInteriorIndices,
592*c8dee2aaSAndroid Build Coastguard Worker std::size(kInteriorIndices));
593*c8dee2aaSAndroid Build Coastguard Worker drawMeshSubset(SK_ColorMAGENTA,
594*c8dee2aaSAndroid Build Coastguard Worker kInnerCornerIndices,
595*c8dee2aaSAndroid Build Coastguard Worker std::size(kInnerCornerIndices));
596*c8dee2aaSAndroid Build Coastguard Worker drawMeshSubset(SK_ColorCYAN,
597*c8dee2aaSAndroid Build Coastguard Worker kOuterCornerIndices,
598*c8dee2aaSAndroid Build Coastguard Worker std::size(kOuterCornerIndices));
599*c8dee2aaSAndroid Build Coastguard Worker } else {
600*c8dee2aaSAndroid Build Coastguard Worker drawMeshSubset(SK_ColorGRAY, kIndices, std::size(kIndices));
601*c8dee2aaSAndroid Build Coastguard Worker }
602*c8dee2aaSAndroid Build Coastguard Worker
603*c8dee2aaSAndroid Build Coastguard Worker // Draw the edges over the triangle strip mesh, but keep track of edges already drawn so
604*c8dee2aaSAndroid Build Coastguard Worker // that we don't oversaturate AA on edges shared by multiple triangles.
605*c8dee2aaSAndroid Build Coastguard Worker std::unordered_set<uint32_t> edges;
606*c8dee2aaSAndroid Build Coastguard Worker auto drawEdge = [&edges, vertices, canvas](uint16_t e0, uint16_t e1) {
607*c8dee2aaSAndroid Build Coastguard Worker uint32_t edgeID = (std::max(e0, e1) << 16) | std::min(e0, e1);
608*c8dee2aaSAndroid Build Coastguard Worker if (edges.find(edgeID) == edges.end()) {
609*c8dee2aaSAndroid Build Coastguard Worker edges.insert(edgeID);
610*c8dee2aaSAndroid Build Coastguard Worker if (SkScalarNearlyEqual(vertices[e0].fX, vertices[e1].fX) &&
611*c8dee2aaSAndroid Build Coastguard Worker SkScalarNearlyEqual(vertices[e0].fY, vertices[e1].fY)) {
612*c8dee2aaSAndroid Build Coastguard Worker return;
613*c8dee2aaSAndroid Build Coastguard Worker }
614*c8dee2aaSAndroid Build Coastguard Worker canvas->drawLine(vertices[e0], vertices[e1], paint(SK_ColorBLACK, 0.f));
615*c8dee2aaSAndroid Build Coastguard Worker }
616*c8dee2aaSAndroid Build Coastguard Worker };
617*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 2; i < std::size(kIndices); ++i) {
618*c8dee2aaSAndroid Build Coastguard Worker drawEdge(kIndices[i-1], kIndices[i]);
619*c8dee2aaSAndroid Build Coastguard Worker drawEdge(kIndices[i-2], kIndices[i]);
620*c8dee2aaSAndroid Build Coastguard Worker }
621*c8dee2aaSAndroid Build Coastguard Worker }
622*c8dee2aaSAndroid Build Coastguard Worker
623*c8dee2aaSAndroid Build Coastguard Worker // This Sample is responsive to the entire transform of the viewer slide, including the
624*c8dee2aaSAndroid Build Coastguard Worker // transform (rotation, scale, and perspective) selected from the widget. The 3 points below
625*c8dee2aaSAndroid Build Coastguard Worker // define the location and basis of the local coordinate space, relative to the viewer's
626*c8dee2aaSAndroid Build Coastguard Worker // coordinate space. This is used instead of the root canvas coordinate space because it aligns
627*c8dee2aaSAndroid Build Coastguard Worker // with the coordinate space that the click handler operates in.
628*c8dee2aaSAndroid Build Coastguard Worker SkV2 fOrigin;
629*c8dee2aaSAndroid Build Coastguard Worker SkV2 fXAxisPoint;
630*c8dee2aaSAndroid Build Coastguard Worker SkV2 fYAxisPoint;
631*c8dee2aaSAndroid Build Coastguard Worker
632*c8dee2aaSAndroid Build Coastguard Worker float fStrokeWidth;
633*c8dee2aaSAndroid Build Coastguard Worker SkPaint::Join fJoinMode;
634*c8dee2aaSAndroid Build Coastguard Worker PrimitiveMode fMode;
635*c8dee2aaSAndroid Build Coastguard Worker bool fColorize = true;
636*c8dee2aaSAndroid Build Coastguard Worker };
637*c8dee2aaSAndroid Build Coastguard Worker
638*c8dee2aaSAndroid Build Coastguard Worker class GraphitePrimitivesSlide::Click : public ClickHandlerSlide::Click {
639*c8dee2aaSAndroid Build Coastguard Worker public:
Click(SkV2 * point)640*c8dee2aaSAndroid Build Coastguard Worker Click(SkV2* point) : fPoint(point) {}
641*c8dee2aaSAndroid Build Coastguard Worker
drag()642*c8dee2aaSAndroid Build Coastguard Worker void drag() {
643*c8dee2aaSAndroid Build Coastguard Worker SkVector delta = fCurr - fPrev;
644*c8dee2aaSAndroid Build Coastguard Worker *fPoint += {delta.fX, delta.fY};
645*c8dee2aaSAndroid Build Coastguard Worker }
646*c8dee2aaSAndroid Build Coastguard Worker
647*c8dee2aaSAndroid Build Coastguard Worker private:
648*c8dee2aaSAndroid Build Coastguard Worker SkV2* fPoint;
649*c8dee2aaSAndroid Build Coastguard Worker };
650*c8dee2aaSAndroid Build Coastguard Worker
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey)651*c8dee2aaSAndroid Build Coastguard Worker ClickHandlerSlide::Click* GraphitePrimitivesSlide::onFindClickHandler(SkScalar x, SkScalar y,
652*c8dee2aaSAndroid Build Coastguard Worker skui::ModifierKey) {
653*c8dee2aaSAndroid Build Coastguard Worker auto selected = [x,y](const SkV2& p) {
654*c8dee2aaSAndroid Build Coastguard Worker return ((p - SkV2{x,y}).length() < kControlPointRadius);
655*c8dee2aaSAndroid Build Coastguard Worker };
656*c8dee2aaSAndroid Build Coastguard Worker
657*c8dee2aaSAndroid Build Coastguard Worker if (selected(fOrigin)) {
658*c8dee2aaSAndroid Build Coastguard Worker return new Click(&fOrigin);
659*c8dee2aaSAndroid Build Coastguard Worker } else if (selected(fXAxisPoint)) {
660*c8dee2aaSAndroid Build Coastguard Worker return new Click(&fXAxisPoint);
661*c8dee2aaSAndroid Build Coastguard Worker } else if (selected(fYAxisPoint)) {
662*c8dee2aaSAndroid Build Coastguard Worker return new Click(&fYAxisPoint);
663*c8dee2aaSAndroid Build Coastguard Worker } else {
664*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
665*c8dee2aaSAndroid Build Coastguard Worker }
666*c8dee2aaSAndroid Build Coastguard Worker }
667*c8dee2aaSAndroid Build Coastguard Worker
onClick(ClickHandlerSlide::Click * click)668*c8dee2aaSAndroid Build Coastguard Worker bool GraphitePrimitivesSlide::onClick(ClickHandlerSlide::Click* click) {
669*c8dee2aaSAndroid Build Coastguard Worker Click* myClick = (Click*) click;
670*c8dee2aaSAndroid Build Coastguard Worker myClick->drag();
671*c8dee2aaSAndroid Build Coastguard Worker return true;
672*c8dee2aaSAndroid Build Coastguard Worker }
673*c8dee2aaSAndroid Build Coastguard Worker
onChar(SkUnichar code)674*c8dee2aaSAndroid Build Coastguard Worker bool GraphitePrimitivesSlide::onChar(SkUnichar code) {
675*c8dee2aaSAndroid Build Coastguard Worker switch(code) {
676*c8dee2aaSAndroid Build Coastguard Worker case '1':
677*c8dee2aaSAndroid Build Coastguard Worker fMode = PrimitiveMode::kFillRect;
678*c8dee2aaSAndroid Build Coastguard Worker return true;
679*c8dee2aaSAndroid Build Coastguard Worker case '2':
680*c8dee2aaSAndroid Build Coastguard Worker fMode = PrimitiveMode::kFillRRect;
681*c8dee2aaSAndroid Build Coastguard Worker return true;
682*c8dee2aaSAndroid Build Coastguard Worker case '3':
683*c8dee2aaSAndroid Build Coastguard Worker fMode = PrimitiveMode::kStrokeRect;
684*c8dee2aaSAndroid Build Coastguard Worker return true;
685*c8dee2aaSAndroid Build Coastguard Worker case '4':
686*c8dee2aaSAndroid Build Coastguard Worker fMode = PrimitiveMode::kStrokeRRect;
687*c8dee2aaSAndroid Build Coastguard Worker return true;
688*c8dee2aaSAndroid Build Coastguard Worker case '-':
689*c8dee2aaSAndroid Build Coastguard Worker fStrokeWidth = std::max(0.f, fStrokeWidth - 0.4f);
690*c8dee2aaSAndroid Build Coastguard Worker return true;
691*c8dee2aaSAndroid Build Coastguard Worker case '=':
692*c8dee2aaSAndroid Build Coastguard Worker fStrokeWidth = std::min(5 * kBaseScale, fStrokeWidth + 0.4f);
693*c8dee2aaSAndroid Build Coastguard Worker return true;
694*c8dee2aaSAndroid Build Coastguard Worker case 'q':
695*c8dee2aaSAndroid Build Coastguard Worker fJoinMode = SkPaint::kRound_Join;
696*c8dee2aaSAndroid Build Coastguard Worker return true;
697*c8dee2aaSAndroid Build Coastguard Worker case 'w':
698*c8dee2aaSAndroid Build Coastguard Worker fJoinMode = SkPaint::kBevel_Join;
699*c8dee2aaSAndroid Build Coastguard Worker return true;
700*c8dee2aaSAndroid Build Coastguard Worker case 'e':
701*c8dee2aaSAndroid Build Coastguard Worker fJoinMode = SkPaint::kMiter_Join;
702*c8dee2aaSAndroid Build Coastguard Worker return true;
703*c8dee2aaSAndroid Build Coastguard Worker case 'r':
704*c8dee2aaSAndroid Build Coastguard Worker fStrokeWidth = 10.f;
705*c8dee2aaSAndroid Build Coastguard Worker fOrigin = {300.f, 300.f};
706*c8dee2aaSAndroid Build Coastguard Worker fXAxisPoint = {300.f + kBaseScale, 300.f};
707*c8dee2aaSAndroid Build Coastguard Worker fYAxisPoint = {300.f, 300.f + kBaseScale};
708*c8dee2aaSAndroid Build Coastguard Worker return true;
709*c8dee2aaSAndroid Build Coastguard Worker case 'c':
710*c8dee2aaSAndroid Build Coastguard Worker fColorize = !fColorize;
711*c8dee2aaSAndroid Build Coastguard Worker return true;
712*c8dee2aaSAndroid Build Coastguard Worker }
713*c8dee2aaSAndroid Build Coastguard Worker return false;
714*c8dee2aaSAndroid Build Coastguard Worker }
715*c8dee2aaSAndroid Build Coastguard Worker
716*c8dee2aaSAndroid Build Coastguard Worker DEF_SLIDE(return new GraphitePrimitivesSlide();)
717