HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
graphic_path.hpp
1// Copyright Take Vos 2019-2021.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "bezier_point.hpp" // export
8#include "bezier_curve.hpp" // export
9#include "bezier.hpp" // export
10#include "../utility/utility.hpp"
11#include "../geometry/module.hpp"
12#include "../image/module.hpp"
13#include "../macros.hpp"
14#include <vector>
15
16hi_export_module(hikogui.graphic_path);
17
18namespace hi { inline namespace v1 {
19
26hi_export struct graphic_path {
30
34
38
42 {
43 points.clear();
44 contourEndPoints.clear();
45 layerEndContours.clear();
46 }
47
54
61
65 {
66 if (!hasLayers()) {
67 return true;
68 }
69
70 hilet& firstColor = layerEndContours.front().second;
71
72 for (hilet & [ endContour, color ] : layerEndContours) {
73 if (color != firstColor) {
74 return false;
75 }
76 }
77 return true;
78 }
79
83 {
84 if (ssize(points) == 0) {
85 return aarectangle{0.0, 0.0, 0.0, 0.0};
86 }
87
88 auto r = aarectangle{points.front().p, points.front().p};
89
90 for (hilet& point : points) {
91 r |= point.p;
92 }
93
94 return r;
95 }
96
102 {
103 if (!hasLayers()) {
104 return;
105 }
106
107 if (!allLayersHaveSameColor()) {
108 return;
109 }
110
111 layerEndContours.clear();
112 }
113
116 [[nodiscard]] std::vector<bezier_point>::const_iterator beginContour(ssize_t contourNr) const noexcept
117 {
118 return points.begin() + (contourNr == 0 ? 0 : contourEndPoints.at(contourNr - 1) + 1);
119 }
120
121 /* Return and end-iterator beyond the end point of a contour.
122 */
123 [[nodiscard]] std::vector<bezier_point>::const_iterator endContour(ssize_t contourNr) const noexcept
124 {
125 return points.begin() + contourEndPoints.at(contourNr) + 1;
126 }
127
128 /* Return the first contour index of a layer.
129 */
130 [[nodiscard]] ssize_t beginLayer(ssize_t layerNr) const noexcept
131 {
132 return layerNr == 0 ? 0 : layerEndContours.at(layerNr - 1).first + 1;
133 }
134
135 /* Return beyond the last contour index of a layer.
136 */
137 [[nodiscard]] ssize_t endLayer(ssize_t layerNr) const noexcept
138 {
139 return layerEndContours.at(layerNr).first + 1;
140 }
141
142 [[nodiscard]] std::vector<bezier_point> getbezier_pointsOfContour(ssize_t contourNr) const noexcept
143 {
144 hilet begin = points.begin() + (contourNr == 0 ? 0 : contourEndPoints.at(contourNr - 1) + 1);
145 hilet end = points.begin() + contourEndPoints.at(contourNr) + 1;
146 return std::vector(begin, end);
147 }
148
149 [[nodiscard]] std::vector<bezier_curve> getBeziersOfContour(ssize_t contourNr) const noexcept
150 {
151 auto first = beginContour(contourNr);
152 auto last = endContour(contourNr);
153 auto num_points = std::distance(first, last);
154 if (num_points < 3) {
155 // Contours with less than three points do not have volume and are invisible.
156 // Contours with one point are used for anchors when compositing compound glyphs.
157 return {};
158 }
159
161 }
162
164 {
165 hi_assert(!hasLayers());
166
168
169 for (auto contourNr = 0; contourNr < numberOfContours(); contourNr++) {
170 hilet beziers = getBeziersOfContour(contourNr);
171 r.insert(r.end(), beziers.begin(), beziers.end());
172 }
173 return r;
174 }
175
176 [[nodiscard]] std::pair<graphic_path, color> getLayer(ssize_t layerNr) const noexcept
177 {
178 hi_assert(hasLayers());
179
180 auto path = graphic_path{};
181
182 hilet begin = beginLayer(layerNr);
183 hilet end = endLayer(layerNr);
185 path.addContour(beginContour(contourNr), endContour(contourNr));
186 }
187
188 return {path, getColorOfLayer(layerNr)};
189 }
190
191 [[nodiscard]] color getColorOfLayer(ssize_t layerNr) const noexcept
192 {
193 return layerEndContours.at(layerNr).second;
194 }
195
196 void setColorOfLayer(ssize_t layerNr, color fill_color) noexcept
197 {
198 layerEndContours.at(layerNr).second = fill_color;
199 }
200
204 {
205 if (points.size() == 0) {
206 return false;
207 } else if (contourEndPoints.size() == 0) {
208 return true;
209 } else {
210 return contourEndPoints.back() != (ssize(points) - 1);
211 }
212 }
213
218 {
219 if (isContourOpen()) {
220 contourEndPoints.push_back(ssize(points) - 1);
221 }
222 }
223
227 {
228 return numberOfLayers() > 0;
229 }
230
234 {
235 if (points.size() == 0) {
236 return false;
237 } else if (isContourOpen()) {
238 return true;
239 } else if (layerEndContours.size() == 0) {
240 return true;
241 } else {
242 return layerEndContours.back().first != (ssize(contourEndPoints) - 1);
243 }
244 }
245
249 void closeLayer(color fill_color) noexcept
250 {
251 closeContour();
252 if (isLayerOpen()) {
253 layerEndContours.emplace_back(ssize(contourEndPoints) - 1, fill_color);
254 }
255 }
256
261 {
262 if (ssize(layerEndContours) == 0) {
263 return;
264 }
265
266 decltype(layerEndContours) tmp;
267 tmp.reserve(ssize(layerEndContours));
268
269 auto prev_i = layerEndContours.begin();
270 for (auto i = prev_i + 1; i != layerEndContours.end(); ++i) {
271 // Add the last layer of a contiguous color.
272 if (prev_i->second != i->second) {
273 tmp.push_back(*prev_i);
274 }
275
276 prev_i = i;
277 }
278 tmp.push_back(*prev_i);
279
281 }
282
287 {
288 if (isContourOpen()) {
289 return points.back().p;
290 } else {
291 return point2{};
292 }
293 }
294
298 void moveTo(point2 position) noexcept
299 {
300 closeContour();
301 points.emplace_back(position, bezier_point::Type::Anchor);
302 }
303
307 void moveRelativeTo(vector2 direction) noexcept
308 {
309 hi_assert(isContourOpen());
310
312 closeContour();
313 points.emplace_back(lastPosition + direction, bezier_point::Type::Anchor);
314 }
315
316 void lineTo(point2 position) noexcept
317 {
318 hi_assert(isContourOpen());
319
320 points.emplace_back(position, bezier_point::Type::Anchor);
321 }
322
323 void lineRelativeTo(vector2 direction) noexcept
324 {
325 hi_assert(isContourOpen());
326
327 points.emplace_back(currentPosition() + direction, bezier_point::Type::Anchor);
328 }
329
330 void quadraticCurveTo(point2 controlPosition, point2 position) noexcept
331 {
332 hi_assert(isContourOpen());
333
334 points.emplace_back(controlPosition, bezier_point::Type::QuadraticControl);
335 points.emplace_back(position, bezier_point::Type::Anchor);
336 }
337
343 {
344 hi_assert(isContourOpen());
345
346 hilet p = currentPosition();
347 points.emplace_back(p + controlDirection, bezier_point::Type::QuadraticControl);
348 points.emplace_back(p + direction, bezier_point::Type::Anchor);
349 }
350
351 void cubicCurveTo(point2 controlPosition1, point2 controlPosition2, point2 position) noexcept
352 {
353 hi_assert(isContourOpen());
354
355 points.emplace_back(controlPosition1, bezier_point::Type::CubicControl1);
356 points.emplace_back(controlPosition2, bezier_point::Type::CubicControl2);
357 points.emplace_back(position, bezier_point::Type::Anchor);
358 }
359
366 {
367 hi_assert(isContourOpen());
368
369 hilet p = currentPosition();
370 points.emplace_back(p + controlDirection1, bezier_point::Type::CubicControl1);
371 points.emplace_back(p + controlDirection2, bezier_point::Type::CubicControl2);
372 points.emplace_back(p + direction, bezier_point::Type::Anchor);
373 }
374
386 void arcTo(float radius, point2 position) noexcept
387 {
388 hi_assert(isContourOpen());
389
390 hilet r = std::abs(radius);
391 hilet P1 = currentPosition();
392 hilet P2 = position;
393 hilet Pm = midpoint(P1, P2);
394
395 hilet Vm2 = P2 - Pm;
396
397 // Calculate the half angle between vectors P0 - C and P2 - C.
398 hilet alpha = std::asin(hypot(Vm2) / r);
399
400 // Calculate the center point C. As the length of the normal of Vm2 at Pm.
401 hilet C = Pm + normal(Vm2) * std::cos(alpha) * radius;
402
403 // Calculate vectors from center to end points.
404 hilet VC1 = P1 - C;
405 hilet VC2 = P2 - C;
406
407 hilet q1 = squared_hypot(VC1);
408 hilet q2 = q1 + dot(VC1, VC2);
409 hilet k2 = (4.0f / 3.0f) * (std::sqrt(2.0f * q1 * q2) - q2) / cross(VC1, VC2);
410
411 // Calculate the control points.
412 hilet C1 = point2{(C.x() + VC1.x()) - k2 * VC1.y(), (C.y() + VC1.y()) + k2 * VC1.x()};
413 hilet C2 = point2{(C.x() + VC2.x()) + k2 * VC2.y(), (C.y() + VC2.y()) - k2 * VC2.x()};
414
415 cubicCurveTo(C1, C2, P2);
416 }
417
423 void addRectangle(aarectangle rectangle, corner_radii corners = corner_radii{0.0f, 0.0f, 0.0f, 0.0f}) noexcept
424 {
425 hi_assert(!isContourOpen());
426
427 hilet bl_radius = std::abs(corners.left_bottom());
428 hilet br_radius = std::abs(corners.right_bottom());
429 hilet tl_radius = std::abs(corners.left_top());
430 hilet tr_radius = std::abs(corners.right_top());
431
432 hilet blc = get<0>(rectangle);
433 hilet brc = get<1>(rectangle);
434 hilet tlc = get<2>(rectangle);
435 hilet trc = get<3>(rectangle);
436
437 hilet blc1 = blc + vector2{0.0f, bl_radius};
438 hilet blc2 = blc + vector2{bl_radius, 0.0f};
439 hilet brc1 = brc + vector2{-br_radius, 0.0f};
440 hilet brc2 = brc + vector2{0.0f, br_radius};
441 hilet tlc1 = tlc + vector2{tl_radius, 0.0f};
442 hilet tlc2 = tlc + vector2{0.0f, -tl_radius};
443 hilet trc1 = trc + vector2{0.0f, -tr_radius};
444 hilet trc2 = trc + vector2{-tr_radius, 0.0f};
445
446 moveTo(blc1);
447 if (corners.left_bottom() > 0.0) {
449 } else if (corners.left_bottom() < 0.0) {
450 lineTo(blc2);
451 }
452
453 lineTo(brc1);
454 if (corners.right_bottom() > 0.0) {
456 } else if (corners.right_bottom() < 0.0) {
457 lineTo(blc2);
458 }
459
460 lineTo(tlc1);
461 if (corners.left_top() > 0.0) {
463 } else if (corners.left_top() < 0.0) {
464 lineTo(tlc2);
465 }
466
467 lineTo(trc1);
468 if (corners.right_top() > 0.0) {
470 } else if (corners.right_top() < 0.0) {
471 lineTo(trc2);
472 }
473
474 closeContour();
475 }
476
481 void addCircle(point2 position, float radius) noexcept
482 {
483 hi_assert(!isContourOpen());
484
485 moveTo(point2{position.x(), position.y() - radius});
486 arcTo(radius, point2{position.x() + radius, position.y()});
487 arcTo(radius, point2{position.x(), position.y() + radius});
488 arcTo(radius, point2{position.x() - radius, position.y()});
489 arcTo(radius, point2{position.x(), position.y() - radius});
490 closeContour();
491 }
492
497 {
498 hi_assert(!isContourOpen());
499
500 for (hilet& curve : contour) {
501 // Don't emit the first point, the last point of the contour will wrap around.
502 switch (curve.type) {
503 case bezier_curve::Type::Linear:
504 points.emplace_back(curve.P2, bezier_point::Type::Anchor);
505 break;
506 case bezier_curve::Type::Quadratic:
507 points.emplace_back(curve.C1, bezier_point::Type::QuadraticControl);
508 points.emplace_back(curve.P2, bezier_point::Type::Anchor);
509 break;
510 case bezier_curve::Type::Cubic:
511 points.emplace_back(curve.C1, bezier_point::Type::CubicControl1);
512 points.emplace_back(curve.C2, bezier_point::Type::CubicControl2);
513 points.emplace_back(curve.P2, bezier_point::Type::Anchor);
514 break;
515 default:
516 hi_no_default();
517 }
518 }
519
520 closeContour();
521 }
522
527 std::vector<bezier_point>::const_iterator const& begin,
528 std::vector<bezier_point>::const_iterator const& end) noexcept
529 {
530 hi_assert(!isContourOpen());
531 points.insert(points.end(), begin, end);
532 closeContour();
533 }
534
539 {
540 addContour(contour.begin(), contour.end());
541 }
542
545 void addPath(graphic_path const& path, color fill_color) noexcept
546 {
547 *this += path;
548 closeLayer(fill_color);
549 }
550
563
576 float strokeWidth = 1.0f,
578 float tolerance = 0.05f) const noexcept
579 {
580 hi_assert(!hasLayers());
581 hi_assert(!isContourOpen());
582
583 auto r = graphic_path{};
584
585 float starboardOffset = strokeWidth / 2;
587
588 for (int i = 0; i < numberOfContours(); i++) {
589 hilet baseContour = getBeziersOfContour(i);
590
592 r.addContour(starboardContour);
593
595 r.addContour(portContour);
596 }
597
598 return r;
599 }
600
603 [[nodiscard]] graphic_path centerScale(extent2 extent, float padding = 0.0) const noexcept
604 {
605 auto max_size =
606 extent2{std::max(1.0f, extent.width() - (padding * 2.0f)), std::max(1.0f, extent.height() - (padding * 2.0f))};
607
608 auto bbox = boundingBox();
609 if (bbox.width() <= 0.0 || bbox.height() <= 0.0) {
610 return {};
611 }
612
613 hilet scale = std::min(max_size.width() / bbox.width(), max_size.height() / bbox.height());
614 bbox = scale2(scale) * bbox;
615
616 hilet offset = (point2{} - get<0>(bbox)) + (extent - bbox.size()) * 0.5;
617
618 return (translate2(offset) * scale2(scale, scale)) * *this;
619 }
620
621 graphic_path& operator+=(graphic_path const& rhs) noexcept
622 {
623 hi_assert(!isContourOpen());
624 hi_assert(!rhs.isContourOpen());
625
626 // Left hand layer can only be open if the right hand side contains no layers.
627 hi_assert(!rhs.hasLayers() || !isLayerOpen());
628
629 hilet pointOffset = ssize(points);
631
632 layerEndContours.reserve(layerEndContours.size() + rhs.layerEndContours.size());
633 for (hilet & [ x, fill_color ] : rhs.layerEndContours) {
634 layerEndContours.emplace_back(contourOffset + x, fill_color);
635 }
636
637 contourEndPoints.reserve(contourEndPoints.size() + rhs.contourEndPoints.size());
638 for (hilet x : rhs.contourEndPoints) {
639 contourEndPoints.push_back(pointOffset + x);
640 }
641
642 points.insert(points.end(), rhs.points.begin(), rhs.points.end());
643 return *this;
644 }
645
646 [[nodiscard]] friend graphic_path operator+(graphic_path lhs, graphic_path const& rhs) noexcept
647 {
648 return lhs += rhs;
649 }
650
651 friend graphic_path operator*(transformer2 auto const& lhs, graphic_path const& rhs) noexcept
652 {
653 auto rhs_ = rhs;
654 for (auto& point : rhs_.points) {
655 point = lhs * point;
656 }
657 return rhs_;
658 }
659};
660
667hi_export inline void composit(pixmap_span<sfloat_rgba16> dst, hi::color color, graphic_path const& mask) noexcept
668{
669 hi_assert(not mask.hasLayers());
670 hi_assert(not mask.isContourOpen());
671
672 auto mask_image = pixmap<uint8_t>(dst.width(), dst.height());
673 fill(mask_image);
674
675 hilet curves = mask.getBeziers();
676 fill(mask_image, curves);
677
679}
680
686hi_export inline void composit(pixmap_span<sfloat_rgba16> dst, graphic_path const& mask) noexcept
687{
688 hi_assert(mask.hasLayers() and not mask.isLayerOpen());
689
690 for (int layerNr = 0; layerNr < mask.numberOfLayers(); layerNr++) {
691 hilet[layer, fill_color] = mask.getLayer(layerNr);
692
693 composit(dst, fill_color, layer);
694 }
695}
696
701hi_export inline void fill(pixmap_span<sdf_r8> dst, graphic_path const& path) noexcept
702{
703 fill(dst, path.getBeziers());
704}
705
706}} // namespace hi::v1
@ end
Start from the end of the file.
@ begin
Start from the beginning of the file.
line_join_style
The way two lines should be joined.
Definition line_join_style.hpp:19
@ miter
The outer edge of both lines are extended until they meet to form a sharp corner.
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
hi_export void composit(pixmap_span< sfloat_rgba16 > dst, hi::color color, graphic_path const &mask) noexcept
Composit color onto the destination image where the mask is solid.
Definition graphic_path.hpp:667
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition misc.hpp:33
constexpr std::vector< bezier_curve > makeContourFromPoints(std::vector< bezier_point >::const_iterator begin, std::vector< bezier_point >::const_iterator end) noexcept
Make a contour of Bezier curves from a list of points.
Definition bezier_curve.hpp:523
constexpr std::vector< bezier_curve > makeInverseContour(std::vector< bezier_curve > const &contour) noexcept
Inverse a contour.
Definition bezier_curve.hpp:587
constexpr std::vector< bezier_curve > makeParallelContour(std::vector< bezier_curve > const &contour, float offset, hi::line_join_style line_join_style, float tolerance) noexcept
Definition bezier_curve.hpp:609
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
This is a RGBA floating point color.
Definition color.hpp:45
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:29
The 4 radii of the corners of a quad or rectangle.
Definition corner_radii.hpp:19
A high-level geometric extent.
Definition extent2.hpp:29
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:104
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:115
A rectangle / parallelogram in 3D space.
Definition rectangle.hpp:21
Definition scale2.hpp:13
Definition translate2.hpp:14
A high-level geometric vector Part of the high-level vector, point, mat and color types.
Definition vector2.hpp:19
A path is a vector graphics object.
Definition graphic_path.hpp:26
void addContour(std::vector< bezier_point >::const_iterator const &begin, std::vector< bezier_point >::const_iterator const &end) noexcept
Curve with the given bezier curve.
Definition graphic_path.hpp:526
void addRectangle(aarectangle rectangle, corner_radii corners=corner_radii{0.0f, 0.0f, 0.0f, 0.0f}) noexcept
Draw a rectangle.
Definition graphic_path.hpp:423
void clear() noexcept
Clear the path.
Definition graphic_path.hpp:41
void addStroke(graphic_path const &path, color strokeColor, float strokeWidth, hi::line_join_style line_join_style=line_join_style::miter, float tolerance=0.05f) noexcept
Stroke a path and close layer.
Definition graphic_path.hpp:553
std::vector< bezier_point > points
A set of all bezier points describing all bezier curves, contours and layers.
Definition graphic_path.hpp:29
void optimizeLayers() noexcept
Optimize layers.
Definition graphic_path.hpp:260
void closeLayer(color fill_color) noexcept
Close current contour.
Definition graphic_path.hpp:249
std::vector< bezier_point >::const_iterator beginContour(ssize_t contourNr) const noexcept
Return an iterator to the start point of a contour.
Definition graphic_path.hpp:116
void addContour(std::vector< bezier_curve > const &contour) noexcept
Contour with the given bezier curves.
Definition graphic_path.hpp:496
void addContour(std::vector< bezier_point > const &contour) noexcept
Curve with the given bezier curve.
Definition graphic_path.hpp:538
aarectangle boundingBox() const noexcept
Calculate bounding box.
Definition graphic_path.hpp:82
graphic_path toStroke(float strokeWidth=1.0f, line_join_style line_join_style=line_join_style::miter, float tolerance=0.05f) const noexcept
Convert path to stroke-path.
Definition graphic_path.hpp:575
bool allLayersHaveSameColor() const noexcept
Check if all layers have the same color.
Definition graphic_path.hpp:64
ssize_t numberOfLayers() const noexcept
Return the number of closed layers.
Definition graphic_path.hpp:57
void cubicCurveRelativeTo(vector2 controlDirection1, vector2 controlDirection2, vector2 direction) noexcept
Draw curve from the current position to the new direction.
Definition graphic_path.hpp:365
ssize_t numberOfContours() const noexcept
Return the number of closed contours.
Definition graphic_path.hpp:50
std::vector< std::pair< ssize_t, color > > layerEndContours
An color and index into.
Definition graphic_path.hpp:37
void arcTo(float radius, point2 position) noexcept
Draw an circular arc.
Definition graphic_path.hpp:386
void closeContour() noexcept
Close current contour.
Definition graphic_path.hpp:217
void addPath(graphic_path const &path, color fill_color) noexcept
Add path and close layer.
Definition graphic_path.hpp:545
void addCircle(point2 position, float radius) noexcept
Draw a circle.
Definition graphic_path.hpp:481
void moveTo(point2 position) noexcept
Start a new contour at position.
Definition graphic_path.hpp:298
point2 currentPosition() const noexcept
Get the currentPosition of the open contour.
Definition graphic_path.hpp:286
bool hasLayers() const noexcept
This path has layers.
Definition graphic_path.hpp:226
std::vector< ssize_t > contourEndPoints
An index into.
Definition graphic_path.hpp:33
bool isContourOpen() const noexcept
Return true if there is an open contour.
Definition graphic_path.hpp:203
void quadraticCurveRelativeTo(vector2 controlDirection, vector2 direction) noexcept
Draw curve from the current position to the new direction.
Definition graphic_path.hpp:342
bool isLayerOpen() const noexcept
Return true if there is an open layer.
Definition graphic_path.hpp:233
void moveRelativeTo(vector2 direction) noexcept
Start a new contour relative to current position.
Definition graphic_path.hpp:307
graphic_path centerScale(extent2 extent, float padding=0.0) const noexcept
Center and scale a path inside the extent with padding.
Definition graphic_path.hpp:603
void tryRemoveLayers() noexcept
Try to move the layers in a path.
Definition graphic_path.hpp:101
A non-owning 2D pixel-based image.
Definition pixmap_span.hpp:34
Definition units.hpp:18
T asin(T... args)
T cos(T... args)
T distance(T... args)
T end(T... args)
T insert(T... args)
T max(T... args)
T min(T... args)
T sqrt(T... args)
T swap(T... args)