tiny-skia-cpp
A C++20 2D rendering library (port of tiny-skia)
Loading...
Searching...
No Matches
Path.h
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <cmath>
8#include <cstddef>
9#include <cstdint>
10#include <optional>
11#include <span>
12#include <utility>
13#include <variant>
14#include <vector>
15
16#include "tiny_skia/Edge.h"
17#include "tiny_skia/Geom.h"
18#include "tiny_skia/Transform.h"
19
20namespace tiny_skia {
21
23enum class PathVerb : std::uint8_t {
24 Move,
25 Close,
26 Line,
27 Quad,
28 Cubic,
29};
30
32enum class LineCap : std::uint8_t {
33 Butt,
34 Round,
35 Square,
36};
37
38// Forward declarations for stroke/dash.
39class PathBuilder;
40struct Stroke;
41struct StrokeDash;
42
45struct PathSegment {
46 enum class Kind : std::uint8_t { MoveTo, LineTo, QuadTo, CubicTo, Close };
47 Kind kind;
48 Point pts[3] = {}; // up to 3 points depending on kind
49};
50
56class Path {
57 public:
58 Path() = default;
59 Path(std::vector<PathVerb> verbs, std::vector<Point> points)
60 : verbs_(std::move(verbs)), points_(std::move(points)) {
61 recomputeBounds();
62 }
63
65 static Path fromRect(const Rect& rect) {
66 std::vector<PathVerb> verbs = {PathVerb::Move, PathVerb::Line, PathVerb::Line, PathVerb::Line,
67 PathVerb::Close};
68 std::vector<Point> points = {Point{rect.left(), rect.top()}, Point{rect.right(), rect.top()},
69 Point{rect.right(), rect.bottom()},
70 Point{rect.left(), rect.bottom()}};
71 return Path(std::move(verbs), std::move(points));
72 }
73
75 [[nodiscard]] static std::optional<Path> fromCircle(float cx, float cy, float r);
76
77 [[nodiscard]] std::size_t size() const { return verbs_.size(); }
78 [[nodiscard]] bool empty() const { return verbs_.empty(); }
79
80 [[nodiscard]] std::span<const PathVerb> verbs() const { return verbs_; }
81 [[nodiscard]] std::span<const Point> points() const { return points_; }
82
84 void addVerb(PathVerb verb) { verbs_.push_back(verb); }
85
87 void addPoint(Point point) {
88 if (bounds_.has_value()) {
89 const auto current = bounds_.value();
90 bounds_ =
91 Rect::fromLTRB(std::min(current.left(), point.x), std::min(current.top(), point.y),
92 std::max(current.right(), point.x), std::max(current.bottom(), point.y));
93 } else {
94 bounds_ = Rect::fromLTRB(point.x, point.y, point.x, point.y);
95 }
96 points_.push_back(point);
97 }
98
102 [[nodiscard]] bool isConvex() const {
103 if (points_.size() < 3) return false;
104
105 // Must be a single closed contour.
106 int moveCount = 0;
107 bool hasClosed = false;
108 for (auto v : verbs_) {
109 if (v == PathVerb::Move) moveCount++;
110 if (v == PathVerb::Close) hasClosed = true;
111 }
112 if (moveCount != 1 || !hasClosed) return false;
113
114 const auto n = points_.size();
115
116 // Check cross products of consecutive direction vectors on the control polygon.
117 int sign = 0; // 0 = unknown, 1 = positive, -1 = negative
118 for (std::size_t i = 0; i < n; i++) {
119 const auto& p0 = points_[i];
120 const auto& p1 = points_[(i + 1) % n];
121 const auto& p2 = points_[(i + 2) % n];
122
123 float dx1 = p1.x - p0.x;
124 float dy1 = p1.y - p0.y;
125 float dx2 = p2.x - p1.x;
126 float dy2 = p2.y - p1.y;
127
128 float cross = dx1 * dy2 - dy1 * dx2;
129 if (cross > 0.0f) {
130 if (sign < 0) return false;
131 sign = 1;
132 } else if (cross < 0.0f) {
133 if (sign > 0) return false;
134 sign = -1;
135 }
136 }
137 if (sign == 0) return false;
138
139 // Check no non-adjacent edges intersect (simple polygon check).
140 // Stroked path outlines can be self-intersecting single contours that pass the
141 // cross-product check above; this O(n²) check catches them.
142 for (std::size_t i = 0; i < n; i++) {
143 const auto& a = points_[i];
144 const auto& b = points_[(i + 1) % n];
145 for (std::size_t j = i + 2; j < n; j++) {
146 if (i == 0 && j == n - 1) continue; // Adjacent via wrap-around.
147 const auto& c = points_[j];
148 const auto& d = points_[(j + 1) % n];
149 // Check proper intersection using orientation tests.
150 float d1 = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
151 float d2 = (b.x - a.x) * (d.y - a.y) - (b.y - a.y) * (d.x - a.x);
152 float d3 = (d.x - c.x) * (a.y - c.y) - (d.y - c.y) * (a.x - c.x);
153 float d4 = (d.x - c.x) * (b.y - c.y) - (d.y - c.y) * (b.x - c.x);
154 if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
155 ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
156 return false;
157 }
158 }
159 }
160 return true;
161 }
162
164 [[nodiscard]] Rect bounds() const {
165 return bounds_.value_or(Rect::fromLTRB(0.0f, 0.0f, 0.0f, 0.0f).value());
166 }
167
169 [[nodiscard]] std::optional<Path> transform(const Transform& ts) const {
170 auto pts = points_;
171 ts.mapPoints(pts);
172 for (const auto& p : pts) {
173 if (!std::isfinite(p.x) || !std::isfinite(p.y)) {
174 return std::nullopt;
175 }
176 }
177 return Path(verbs_, std::move(pts));
178 }
179
181 [[nodiscard]] std::optional<Rect> computeTightBounds() const;
182
184 [[nodiscard]] PathBuilder clear();
185
187 [[nodiscard]] std::optional<Path> stroke(const Stroke& stroke, float resScale) const;
188
190 [[nodiscard]] std::optional<Path> dash(const StrokeDash& dash, float resScale) const;
191
192 private:
193 void recomputeBounds() {
194 if (points_.empty()) {
195 bounds_.reset();
196 return;
197 }
198
199 auto left = points_[0].x;
200 auto top = points_[0].y;
201 auto right = points_[0].x;
202 auto bottom = points_[0].y;
203
204 for (const auto& point : points_) {
205 left = std::min(left, point.x);
206 right = std::max(right, point.x);
207 top = std::min(top, point.y);
208 bottom = std::max(bottom, point.y);
209 }
210
211 bounds_ = Rect::fromLTRB(left, top, right, bottom);
212 }
213
214 std::vector<PathVerb> verbs_;
215 std::vector<Point> points_;
216 std::optional<Rect> bounds_;
217};
218
220enum class FillRule : std::uint8_t {
221 Winding = 0,
222 EvenOdd = 1,
223};
224
227class PathSegmentsIter {
228 public:
229 explicit PathSegmentsIter(const Path& path) : path_(&path) {}
230
231 void setAutoClose(bool flag) { isAutoClose_ = flag; }
232
233 std::optional<PathSegment> next();
234
235 [[nodiscard]] Point lastPoint() const { return lastPoint_; }
236 [[nodiscard]] Point lastMoveTo() const { return lastMoveTo_; }
237
238 [[nodiscard]] PathVerb currVerb() const { return path_->verbs()[verbIndex_ - 1]; }
239
240 [[nodiscard]] std::optional<PathVerb> nextVerb() const {
241 if (verbIndex_ < path_->verbs().size()) {
242 return path_->verbs()[verbIndex_];
243 }
244 return std::nullopt;
245 }
246
247 [[nodiscard]] bool hasValidTangent() const;
248
249 private:
250 PathSegment autoClose();
251
252 const Path* path_;
253 std::size_t verbIndex_ = 0;
254 std::size_t pointsIndex_ = 0;
255 bool isAutoClose_ = false;
256 Point lastMoveTo_ = Point::zero();
257 Point lastPoint_ = Point::zero();
258};
259
260} // namespace tiny_skia
Geometric primitives: Rect, IntRect, ScreenIntRect, IntSize.
PathVerb
Path segment verb (moveTo, lineTo, quadTo, cubicTo, close).
Definition Path.h:23
FillRule
Fill rule for path filling.
Definition Path.h:220
@ Winding
Non-zero winding rule.
@ EvenOdd
Even-odd (parity) rule.
LineCap
Line cap style for stroke endpoints.
Definition Path.h:32
@ Butt
Flat cap, no extension.
@ Round
Semicircle cap.
@ Square
Extends by half the stroke width.
2D affine transformation matrix.
Incrementally builds a Path from move/line/quad/cubic/close operations.
Definition PathBuilder.h:20
Immutable vector path — a sequence of lines, quadratics, and cubics.
Definition Path.h:56
Rect bounds() const
Axis-aligned bounding box (control-point bounds).
Definition Path.h:164
std::optional< Path > transform(const Transform &ts) const
Returns a transformed copy. Nullopt if the transform produces non-finite values.
Definition Path.h:169
std::optional< Path > dash(const StrokeDash &dash, float resScale) const
Applies a dash pattern, returning a new dashed path.
std::optional< Rect > computeTightBounds() const
Computes tight bounds by finding curve extrema (more precise than bounds()).
std::optional< Path > stroke(const Stroke &stroke, float resScale) const
Generates a filled path representing the stroke outline.
static Path fromRect(const Rect &rect)
Creates a rectangular path.
Definition Path.h:65
static std::optional< Path > fromCircle(float cx, float cy, float r)
Creates a circular path. Returns nullopt for non-positive radius.
PathBuilder clear()
Clears the path and returns a PathBuilder reusing the allocations.
Floating-point rectangle (left, top, right, bottom). All components must be finite,...
Definition Geom.h:119
static std::optional< Rect > fromLTRB(float left, float top, float right, float bottom)
Creates from edges. Returns nullopt for non-finite, empty, or inverted rects.
2D affine transformation: [sx kx tx; ky sy ty; 0 0 1].
Definition Transform.h:17
void mapPoints(std::span< Point > points) const
Transforms an array of points in-place.
2D point / vector with float components.
Definition Point.h:14
Dash pattern for stroked paths.
Definition Stroke.h:24
Stroke properties for Painter::strokePath.
Definition Stroke.h:36