Flutter Impeller
path_component.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "path_component.h"
6 
7 #include <cmath>
8 #include <utility>
9 
10 #include "flutter/fml/logging.h"
13 
14 namespace impeller {
15 
16 /////////// FanVertexWriter ///////////
17 
18 FanVertexWriter::FanVertexWriter(Point* point_buffer, uint16_t* index_buffer)
19  : point_buffer_(point_buffer), index_buffer_(index_buffer) {}
20 
22 
24  return index_count_;
25 }
26 
28  if (count_ == 0) {
29  return;
30  }
31  index_buffer_[index_count_++] = 0xFFFF;
32 }
33 
35  index_buffer_[index_count_++] = count_;
36  point_buffer_[count_++] = point;
37 }
38 
39 /////////// StripVertexWriter ///////////
40 
42  uint16_t* index_buffer)
43  : point_buffer_(point_buffer), index_buffer_(index_buffer) {}
44 
46 
48  return index_count_;
49 }
50 
52  if (count_ == 0u || contour_start_ == count_ - 1) {
53  // Empty or first contour.
54  return;
55  }
56 
57  size_t start = contour_start_;
58  size_t end = count_ - 1;
59 
60  index_buffer_[index_count_++] = start;
61 
62  size_t a = start + 1;
63  size_t b = end;
64  while (a < b) {
65  index_buffer_[index_count_++] = a;
66  index_buffer_[index_count_++] = b;
67  a++;
68  b--;
69  }
70  if (a == b) {
71  index_buffer_[index_count_++] = a;
72  }
73 
74  contour_start_ = count_;
75  index_buffer_[index_count_++] = 0xFFFF;
76 }
77 
79  point_buffer_[count_++] = point;
80 }
81 
82 /////////// LineStripVertexWriter ////////
83 
85  : points_(points) {}
86 
88 
90  if (offset_ >= points_.size()) {
91  overflow_.push_back(point);
92  } else {
93  points_[offset_++] = point;
94  }
95 }
96 
97 const std::vector<Point>& LineStripVertexWriter::GetOversizedBuffer() const {
98  return overflow_;
99 }
100 
101 std::pair<size_t, size_t> LineStripVertexWriter::GetVertexCount() const {
102  return std::make_pair(offset_, overflow_.size());
103 }
104 
105 /////////// GLESVertexWriter ///////////
106 
107 GLESVertexWriter::GLESVertexWriter(std::vector<Point>& points,
108  std::vector<uint16_t>& indices)
109  : points_(points), indices_(indices) {}
110 
112  if (points_.size() == 0u || contour_start_ == points_.size() - 1) {
113  // Empty or first contour.
114  return;
115  }
116 
117  auto start = contour_start_;
118  auto end = points_.size() - 1;
119  // All filled paths are drawn as if they are closed, but if
120  // there is an explicit close then a lineTo to the origin
121  // is inserted. This point isn't strictly necesary to
122  // correctly render the shape and can be dropped.
123  if (points_[end] == points_[start]) {
124  end--;
125  }
126 
127  // Triangle strip break for subsequent contours
128  if (contour_start_ != 0) {
129  auto back = indices_.back();
130  indices_.push_back(back);
131  indices_.push_back(start);
132  indices_.push_back(start);
133 
134  // If the contour has an odd number of points, insert an extra point when
135  // bridging to the next contour to preserve the correct triangle winding
136  // order.
137  if (previous_contour_odd_points_) {
138  indices_.push_back(start);
139  }
140  } else {
141  indices_.push_back(start);
142  }
143 
144  size_t a = start + 1;
145  size_t b = end;
146  while (a < b) {
147  indices_.push_back(a);
148  indices_.push_back(b);
149  a++;
150  b--;
151  }
152  if (a == b) {
153  indices_.push_back(a);
154  previous_contour_odd_points_ = false;
155  } else {
156  previous_contour_odd_points_ = true;
157  }
158  contour_start_ = points_.size();
159 }
160 
162  points_.push_back(point);
163 }
164 
165 /*
166  * Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases
167  */
168 
169 static inline Scalar LinearSolve(Scalar t, Scalar p0, Scalar p1) {
170  return p0 + t * (p1 - p0);
171 }
172 
173 static inline Scalar QuadraticSolve(Scalar t, Scalar p0, Scalar p1, Scalar p2) {
174  return (1 - t) * (1 - t) * p0 + //
175  2 * (1 - t) * t * p1 + //
176  t * t * p2;
177 }
178 
180  Scalar p0,
181  Scalar p1,
182  Scalar p2) {
183  return 2 * (1 - t) * (p1 - p0) + //
184  2 * t * (p2 - p1);
185 }
186 
187 static inline Scalar ConicSolve(Scalar t,
188  Scalar p0,
189  Scalar p1,
190  Scalar p2,
191  Scalar w) {
192  auto u = (1 - t);
193  auto coefficient_p0 = u * u;
194  auto coefficient_p1 = 2 * t * u * w;
195  auto coefficient_p2 = t * t;
196 
197  return ((p0 * coefficient_p0 + p1 * coefficient_p1 + p2 * coefficient_p2) /
198  (coefficient_p0 + coefficient_p1 + coefficient_p2));
199 }
200 
201 static inline Scalar CubicSolve(Scalar t,
202  Scalar p0,
203  Scalar p1,
204  Scalar p2,
205  Scalar p3) {
206  return (1 - t) * (1 - t) * (1 - t) * p0 + //
207  3 * (1 - t) * (1 - t) * t * p1 + //
208  3 * (1 - t) * t * t * p2 + //
209  t * t * t * p3;
210 }
211 
213  Scalar p0,
214  Scalar p1,
215  Scalar p2,
216  Scalar p3) {
217  return -3 * p0 * (1 - t) * (1 - t) + //
218  p1 * (3 * (1 - t) * (1 - t) - 6 * (1 - t) * t) +
219  p2 * (6 * (1 - t) * t - 3 * t * t) + //
220  3 * p3 * t * t;
221 }
222 
224  return {
225  LinearSolve(time, p1.x, p2.x), // x
226  LinearSolve(time, p1.y, p2.y), // y
227  };
228 }
229 
231  std::vector<Point>& points) const {
232  if (points.size() == 0 || points.back() != p2) {
233  points.push_back(p2);
234  }
235 }
236 
237 std::vector<Point> LinearPathComponent::Extrema() const {
238  return {p1, p2};
239 }
240 
241 std::optional<Vector2> LinearPathComponent::GetStartDirection() const {
242  if (p1 == p2) {
243  return std::nullopt;
244  }
245  return (p1 - p2).Normalize();
246 }
247 
248 std::optional<Vector2> LinearPathComponent::GetEndDirection() const {
249  if (p1 == p2) {
250  return std::nullopt;
251  }
252  return (p2 - p1).Normalize();
253 }
254 
256  return {
257  QuadraticSolve(time, p1.x, cp.x, p2.x), // x
258  QuadraticSolve(time, p1.y, cp.y, p2.y), // y
259  };
260 }
261 
263  return {
264  QuadraticSolveDerivative(time, p1.x, cp.x, p2.x), // x
265  QuadraticSolveDerivative(time, p1.y, cp.y, p2.y), // y
266  };
267 }
268 
270  Scalar scale,
271  VertexWriter& writer) const {
272  Scalar line_count = std::ceilf(ComputeQuadradicSubdivisions(scale, *this));
273  for (size_t i = 1; i < line_count; i += 1) {
274  writer.Write(Solve(i / line_count));
275  }
276  writer.Write(p2);
277 }
278 
280  Scalar scale_factor,
281  std::vector<Point>& points) const {
282  ToLinearPathComponents(scale_factor, [&points](const Point& point) {
283  points.emplace_back(point);
284  });
285 }
286 
288  Scalar scale_factor,
289  const PointProc& proc) const {
290  Scalar line_count =
291  std::ceilf(ComputeQuadradicSubdivisions(scale_factor, *this));
292  for (size_t i = 1; i < line_count; i += 1) {
293  proc(Solve(i / line_count));
294  }
295  proc(p2);
296 }
297 
299  return std::ceilf(ComputeQuadradicSubdivisions(scale, *this)) + 2;
300 }
301 
302 std::vector<Point> QuadraticPathComponent::Extrema() const {
303  CubicPathComponent elevated(*this);
304  return elevated.Extrema();
305 }
306 
307 std::optional<Vector2> QuadraticPathComponent::GetStartDirection() const {
308  if (p1 != cp) {
309  return (p1 - cp).Normalize();
310  }
311  if (p1 != p2) {
312  return (p1 - p2).Normalize();
313  }
314  return std::nullopt;
315 }
316 
317 std::optional<Vector2> QuadraticPathComponent::GetEndDirection() const {
318  if (p2 != cp) {
319  return (p2 - cp).Normalize();
320  }
321  if (p2 != p1) {
322  return (p2 - p1).Normalize();
323  }
324  return std::nullopt;
325 }
326 
328  return {
329  ConicSolve(time, p1.x, cp.x, p2.x, weight.x), // x
330  ConicSolve(time, p1.y, cp.y, p2.y, weight.y), // y
331  };
332 }
333 
335  const PointProc& proc) const {
336  Scalar line_count = std::ceilf(ComputeConicSubdivisions(scale_factor, *this));
337  for (size_t i = 1; i < line_count; i += 1) {
338  proc(Solve(i / line_count));
339  }
340  proc(p2);
341 }
342 
344  Scalar scale_factor,
345  std::vector<Point>& points) const {
346  ToLinearPathComponents(scale_factor, [&points](const Point& point) {
347  if (point != points.back()) {
348  points.emplace_back(point);
349  }
350  });
351 }
352 
354  VertexWriter& writer) const {
355  Scalar line_count = std::ceilf(ComputeConicSubdivisions(scale, *this));
356  for (size_t i = 1; i < line_count; i += 1) {
357  writer.Write(Solve(i / line_count));
358  }
359  writer.Write(p2);
360 }
361 
363  return std::ceilf(ComputeConicSubdivisions(scale, *this)) + 2;
364 }
365 
366 std::vector<Point> ConicPathComponent::Extrema() const {
367  std::vector<Point> points;
368  for (auto quad : ToQuadraticPathComponents()) {
369  auto quad_extrema = quad.Extrema();
370  points.insert(points.end(), quad_extrema.begin(), quad_extrema.end());
371  }
372  return points;
373 }
374 
375 std::optional<Vector2> ConicPathComponent::GetStartDirection() const {
376  if (p1 != cp) {
377  return (p1 - cp).Normalize();
378  }
379  if (p1 != p2) {
380  return (p1 - p2).Normalize();
381  }
382  return std::nullopt;
383 }
384 
385 std::optional<Vector2> ConicPathComponent::GetEndDirection() const {
386  if (p2 != cp) {
387  return (p2 - cp).Normalize();
388  }
389  if (p2 != p1) {
390  return (p2 - p1).Normalize();
391  }
392  return std::nullopt;
393 }
394 
396  std::array<Point, 5>& points) const {
397  FML_DCHECK(weight.IsFinite() && weight.x > 0 && weight.y > 0);
398 
399  // Observe that scale will always be smaller than 1 because weight > 0.
400  const Scalar scale = 1.0f / (1.0f + weight.x);
401 
402  // The subdivided control points below are the sums of the following three
403  // terms. Because the terms are multiplied by something <1, and the resulting
404  // control points lie within the control points of the original then the
405  // terms and the sums below will not overflow. Note that weight * scale
406  // approaches 1 as weight becomes very large.
407  Point tp1 = p1 * scale;
408  Point tcp = cp * (weight.x * scale);
409  Point tp2 = p2 * scale;
410 
411  // Calculate the subdivided control points
412  Point sub_cp1 = tp1 + tcp;
413  Point sub_cp2 = tcp + tp2;
414 
415  // The middle point shared by the 2 sub-divisions, the interpolation of
416  // the original curve at its halfway point.
417  Point sub_mid = (tp1 + tcp + tcp + tp2) * 0.5f;
418 
419  FML_DCHECK(sub_cp1.IsFinite() && sub_mid.IsFinite() && sub_cp2.IsFinite());
420 
421  points[0] = p1;
422  points[1] = sub_cp1;
423  points[2] = sub_mid;
424  points[3] = sub_cp2;
425  points[4] = p2;
426 
427  // Update w.
428  // Currently this method only subdivides a single time directly to 2
429  // quadratics, but if we eventually want to keep the weights for further
430  // subdivision, this was the code that did it in Skia:
431  // sub_w1 = sub_w2 = SkScalarSqrt(SK_ScalarHalf + w * SK_ScalarHalf)
432 }
433 
434 std::array<QuadraticPathComponent, 2>
436  std::array<Point, 5> points;
438 
439  return {
440  QuadraticPathComponent(points[0], points[1], points[2]),
441  QuadraticPathComponent(points[2], points[3], points[4]),
442  };
443 }
444 
446  return {
447  CubicSolve(time, p1.x, cp1.x, cp2.x, p2.x), // x
448  CubicSolve(time, p1.y, cp1.y, cp2.y, p2.y), // y
449  };
450 }
451 
453  return {
454  CubicSolveDerivative(time, p1.x, cp1.x, cp2.x, p2.x), // x
455  CubicSolveDerivative(time, p1.y, cp1.y, cp2.y, p2.y), // y
456  };
457 }
458 
460  Scalar scale,
461  std::vector<Point>& points) const {
463  scale, [&points](const Point& point) { points.emplace_back(point); });
464 }
465 
467  VertexWriter& writer) const {
468  Scalar line_count = std::ceilf(ComputeCubicSubdivisions(scale, *this));
469  for (size_t i = 1; i < line_count; i++) {
470  writer.Write(Solve(i / line_count));
471  }
472  writer.Write(p2);
473 }
474 
476  return std::ceilf(ComputeCubicSubdivisions(scale, *this)) + 2;
477 }
478 
479 inline QuadraticPathComponent CubicPathComponent::Lower() const {
480  return QuadraticPathComponent(3.0 * (cp1 - p1), 3.0 * (cp2 - cp1),
481  3.0 * (p2 - cp2));
482 }
483 
485  auto p0 = Solve(t0);
486  auto p3 = Solve(t1);
487  auto d = Lower();
488  auto scale = (t1 - t0) * (1.0 / 3.0);
489  auto p1 = p0 + scale * d.Solve(t0);
490  auto p2 = p3 - scale * d.Solve(t1);
491  return CubicPathComponent(p0, p1, p2, p3);
492 }
493 
495  const PointProc& proc) const {
496  Scalar line_count = std::ceilf(ComputeCubicSubdivisions(scale, *this));
497  for (size_t i = 1; i < line_count; i++) {
498  proc(Solve(i / line_count));
499  }
500  proc(p2);
501 }
502 
503 static inline bool NearEqual(Scalar a, Scalar b, Scalar epsilon) {
504  return (a > (b - epsilon)) && (a < (b + epsilon));
505 }
506 
507 static inline bool NearZero(Scalar a) {
508  return NearEqual(a, 0.0, 1e-12);
509 }
510 
511 static void CubicPathBoundingPopulateValues(std::vector<Scalar>& values,
512  Scalar p1,
513  Scalar p2,
514  Scalar p3,
515  Scalar p4) {
516  const Scalar a = 3.0 * (-p1 + 3.0 * p2 - 3.0 * p3 + p4);
517  const Scalar b = 6.0 * (p1 - 2.0 * p2 + p3);
518  const Scalar c = 3.0 * (p2 - p1);
519 
520  /*
521  * Boundary conditions.
522  */
523  if (NearZero(a)) {
524  if (NearZero(b)) {
525  return;
526  }
527 
528  Scalar t = -c / b;
529  if (t >= 0.0 && t <= 1.0) {
530  values.emplace_back(t);
531  }
532  return;
533  }
534 
535  Scalar b2Minus4AC = (b * b) - (4.0 * a * c);
536 
537  if (b2Minus4AC < 0.0) {
538  return;
539  }
540 
541  Scalar rootB2Minus4AC = ::sqrt(b2Minus4AC);
542 
543  /* From Numerical Recipes in C.
544  *
545  * q = -1/2 (b + sign(b) sqrt[b^2 - 4ac])
546  * x1 = q / a
547  * x2 = c / q
548  */
549  Scalar q = (b < 0) ? -(b - rootB2Minus4AC) / 2 : -(b + rootB2Minus4AC) / 2;
550 
551  {
552  Scalar t = q / a;
553  if (t >= 0.0 && t <= 1.0) {
554  values.emplace_back(t);
555  }
556  }
557 
558  {
559  Scalar t = c / q;
560  if (t >= 0.0 && t <= 1.0) {
561  values.emplace_back(t);
562  }
563  }
564 }
565 
566 std::vector<Point> CubicPathComponent::Extrema() const {
567  /*
568  * As described in: https://pomax.github.io/bezierinfo/#extremities
569  */
570  std::vector<Scalar> values;
571 
574 
575  std::vector<Point> points = {p1, p2};
576 
577  for (const auto& value : values) {
578  points.emplace_back(Solve(value));
579  }
580 
581  return points;
582 }
583 
584 std::optional<Vector2> CubicPathComponent::GetStartDirection() const {
585  if (p1 != cp1) {
586  return (p1 - cp1).Normalize();
587  }
588  if (p1 != cp2) {
589  return (p1 - cp2).Normalize();
590  }
591  if (p1 != p2) {
592  return (p1 - p2).Normalize();
593  }
594  return std::nullopt;
595 }
596 
597 std::optional<Vector2> CubicPathComponent::GetEndDirection() const {
598  if (p2 != cp2) {
599  return (p2 - cp2).Normalize();
600  }
601  if (p2 != cp1) {
602  return (p2 - cp1).Normalize();
603  }
604  if (p2 != p1) {
605  return (p2 - p1).Normalize();
606  }
607  return std::nullopt;
608 }
609 
610 } // namespace impeller
void EndContour() override
size_t GetIndexCount() const
void Write(Point point) override
FanVertexWriter(Point *point_buffer, uint16_t *index_buffer)
GLESVertexWriter(std::vector< Point > &points, std::vector< uint16_t > &indices)
void Write(Point point) override
std::pair< size_t, size_t > GetVertexCount() const
LineStripVertexWriter(std::vector< Point > &points)
void Write(Point point) override
const std::vector< Point > & GetOversizedBuffer() const
void Write(Point point) override
StripVertexWriter(Point *point_buffer, uint16_t *index_buffer)
An interface for generating a multi contour polyline as a triangle strip.
virtual void Write(Point point)=0
int32_t value
static void CubicPathBoundingPopulateValues(std::vector< Scalar > &values, Scalar p1, Scalar p2, Scalar p3, Scalar p4)
float Scalar
Definition: scalar.h:18
static bool NearZero(Scalar a)
Scalar ComputeConicSubdivisions(Scalar scale_factor, Point p0, Point p1, Point p2, Scalar w)
static Scalar LinearSolve(Scalar t, Scalar p0, Scalar p1)
static Scalar CubicSolve(Scalar t, Scalar p0, Scalar p1, Scalar p2, Scalar p3)
static Scalar CubicSolveDerivative(Scalar t, Scalar p0, Scalar p1, Scalar p2, Scalar p3)
static Scalar ConicSolve(Scalar t, Scalar p0, Scalar p1, Scalar p2, Scalar w)
static Scalar QuadraticSolve(Scalar t, Scalar p0, Scalar p1, Scalar p2)
static Scalar QuadraticSolveDerivative(Scalar t, Scalar p0, Scalar p1, Scalar p2)
Scalar ComputeQuadradicSubdivisions(Scalar scale_factor, Point p0, Point p1, Point p2)
Scalar ComputeCubicSubdivisions(Scalar scale_factor, Point p0, Point p1, Point p2, Point p3)
static bool NearEqual(Scalar a, Scalar b, Scalar epsilon)
const Scalar scale
std::optional< Vector2 > GetEndDirection() const
std::array< QuadraticPathComponent, 2 > ToQuadraticPathComponents() const
size_t CountLinearPathComponents(Scalar scale) const
Point Solve(Scalar time) const
void ToLinearPathComponents(Scalar scale_factor, const PointProc &proc) const
std::vector< Point > Extrema() const
std::optional< Vector2 > GetStartDirection() const
void SubdivideToQuadraticPoints(std::array< Point, 5 > &points) const
std::function< void(const Point &point)> PointProc
void AppendPolylinePoints(Scalar scale_factor, std::vector< Point > &points) const
void ToLinearPathComponents(Scalar scale, const PointProc &proc) const
size_t CountLinearPathComponents(Scalar scale) const
void AppendPolylinePoints(Scalar scale, std::vector< Point > &points) const
std::function< void(const Point &point)> PointProc
CubicPathComponent Subsegment(Scalar t0, Scalar t1) const
Point Solve(Scalar time) const
std::optional< Vector2 > GetStartDirection() const
std::vector< Point > Extrema() const
std::optional< Vector2 > GetEndDirection() const
Point SolveDerivative(Scalar time) const
std::optional< Vector2 > GetEndDirection() const
std::optional< Vector2 > GetStartDirection() const
std::vector< Point > Extrema() const
Point Solve(Scalar time) const
void AppendPolylinePoints(std::vector< Point > &points) const
size_t CountLinearPathComponents(Scalar scale) const
std::optional< Vector2 > GetEndDirection() const
void AppendPolylinePoints(Scalar scale_factor, std::vector< Point > &points) const
std::function< void(const Point &point)> PointProc
std::vector< Point > Extrema() const
Point SolveDerivative(Scalar time) const
std::optional< Vector2 > GetStartDirection() const
void ToLinearPathComponents(Scalar scale_factor, const PointProc &proc) const
Point Solve(Scalar time) const
IsFinite() const
Definition: point.h:243