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