Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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,
22 paint.setColor(color);
23 paint.setAntiAlias(true);
24 if (strokeWidth >= 0.f) {
26 paint.setStrokeWidth(strokeWidth);
27 paint.setStrokeJoin(join);
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,
257 SkPaint::Join join) {
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,
295 SkPaint::Join join) {
296 SkV4 devCenter = m.map(rrect.getBounds().centerX(), rrect.getBounds().centerY(), 0.f, 1.f);
297
298 float localAARadius = std::max({
299 local_aa_radius(m, {rrect.getBounds().fRight, rrect.getBounds().fBottom}),
300 local_aa_radius(m, {rrect.getBounds().fRight, rrect.getBounds().fTop}),
301 local_aa_radius(m, {rrect.getBounds().fLeft, rrect.getBounds().fTop}),
302 local_aa_radius(m, {rrect.getBounds().fLeft, rrect.getBounds().fBottom})
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
322 maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerRight_Corner).fX ||
323 maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperLeft_Corner).fX ||
324 maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperRight_Corner).fX ||
325 maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerLeft_Corner).fY || // Y corner cross
326 maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerRight_Corner).fY ||
327 maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperLeft_Corner).fY ||
328 maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperRight_Corner).fY) {
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,
343 rrect.getBounds().fBottom},
346 devCenter, centerWeight, localAARadius, strokeRadius, join);
347 compute_corner(devPts + kTR, m, kTRBasis,
348 {rrect.getBounds().fRight,
349 rrect.getBounds().fTop},
352 devCenter, centerWeight, localAARadius,strokeRadius, join);
353 compute_corner(devPts + kTL, m, kTLBasis,
354 {rrect.getBounds().fLeft,
355 rrect.getBounds().fTop},
358 devCenter, centerWeight, localAARadius,strokeRadius, join);
359 compute_corner(devPts + kBL, m, kBLBasis,
360 {rrect.getBounds().fLeft,
361 rrect.getBounds().fBottom},
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,
589 std::size(kEdgeIndices));
590 drawMeshSubset(SK_ColorDKGRAY,
592 std::size(kInteriorIndices));
593 drawMeshSubset(SK_ColorMAGENTA,
595 std::size(kInnerCornerIndices));
596 drawMeshSubset(SK_ColorCYAN,
598 std::size(kOuterCornerIndices));
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() {
643 SkVector delta = fCurr - fPrev;
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
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 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[]
SkColor4f color
float e1
float e0
#define SkAssertResult(cond)
Definition SkAssert.h:123
#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 void normalize(int n, double *gauss)
static int sign(SkScalar x)
Definition SkPath.cpp:2141
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
Click * onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override
bool onChar(SkUnichar) override
void restore()
Definition SkCanvas.cpp:465
void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint &paint)
SkM44 getLocalToDevice() const
void drawRRect(const SkRRect &rrect, const SkPaint &paint)
void resetMatrix()
int save()
Definition SkCanvas.cpp:451
void concat(const SkMatrix &matrix)
void drawVertices(const SkVertices *vertices, SkBlendMode mode, const SkPaint &paint)
void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint &paint)
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 setColor(SkColor color)
Definition SkPaint.cpp:119
@ kStroke_Style
set to stroke geometry
Definition SkPaint.h:194
@ 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 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[])
@ kTriangleStrip_VertexMode
Definition SkVertices.h:32
SkString fName
Definition Slide.h:54
const Paint & paint
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]
size_t length
double y
double x
SkRRect rrect
Definition SkRecords.h:232
SkMesh mesh
Definition SkRecords.h:345
ModifierKey
Definition ModifierKey.h:9
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
float fY
y-axis value
constexpr float x() const
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