10 : kind_(
SmoothShape::EMPTY), extents_(0, 0, -1, -1) {}
14 extents_(std::
move(extents)),
18 : kind_(SmoothShape::ROUND_RECT),
19 extents_(std::move(extents)),
20 round_rect_(std::move(round_rect)) {}
23 : kind_(SmoothShape::ARC),
24 extents_(std::move(extents)),
25 arc_(std::move(arc)) {}
28 : kind_(SmoothShape::TRIANGLE),
29 extents_(std::move(extents)),
30 triangle_(std::move(triangle)) {}
34 : kind_(SmoothShape::PIXEL),
36 pixel_(std::move(pixel)) {}
48 float dr =
sqrtf((a.
x - b.
x) * (a.
x - b.
x) + (a.
y - b.
y) * (a.
y - b.
y));
52 }
else if (br +
dr <= ar) {
57 if (a.
x == b.
x && a.
y == b.
y) {
93 if (width <= 0.0f || height <= 0.0f)
return SmoothShape();
106 Color interior_color) {
107 if (radius < 0) radius = 0;
109 if (x1 < x0) std::swap(x0, x1);
110 if (y1 < y0) std::swap(y0, y1);
130 1.0f, std::max<float>(0.0f, w * h - (4.0f -
M_PI) * ro * ro));
133 (4.0f -
M_PI) * ri * ri));
145 float d =
sqrtf(0.5f) * ri;
162 std::move(inner_mid),
163 std::move(inner_wide),
164 std::move(inner_tall)};
170 float radius,
Color color,
Color interior_color) {
175 float radius,
Color color) {
192 Color interior_color) {
202inline constexpr int Quadrant(
float x,
float y) {
206 return (x <= 0 && y > 0) ? 0
207 : (y <= 0 && x < 0) ? 1
208 : (x >= 0 && y < 0) ? 2
215 float angle_start,
float angle_end,
219 if (radius <= 0 ||
thickness <= 0 || angle_end == angle_start) {
222 if (angle_end < angle_start) {
223 std::swap(angle_end, angle_start);
225 if (angle_end - angle_start >= 2 *
M_PI) {
230 while (angle_start >=
M_PI) {
231 angle_start -= 2 *
M_PI;
232 angle_end -= 2 *
M_PI;
241 float ri = std::max(0.0f, ro -
thickness);
243 float rc = (ro + ri) * 0.5f;
244 float rm = (ro - ri) * 0.5f;
303 bool qt0 = (angle_start <= -0.5f *
M_PI && angle_end >= 0) ||
304 (angle_end >= 2.0f *
M_PI);
306 (angle_end >= 2.5f *
M_PI);
307 bool qt2 = (angle_start <= -1.0f *
M_PI && angle_end >= -0.5f *
M_PI) ||
308 (angle_end >= 1.5 *
M_PI);
309 bool qt3 = (angle_start <= 0.5f *
M_PI && angle_end >=
M_PI) ||
310 (angle_end >= 3.0f *
M_PI);
337 int16_t xMin, yMin, xMax, yMax;
343 yMin =
floorf(
center.y + std::min(start_y_rc, end_y_rc) - rm);
353 xMin =
floorf(
center.x + std::min(start_x_rc, end_x_rc) - rm);
362 yMax =
ceilf(
center.y + std::max(start_y_rc, end_y_rc) + rm);
372 xMax =
ceilf(
center.x + std::max(start_x_rc, end_x_rc) + rm);
385 Box(xMin, yMin, xMax, yMax),
413 angle_end - angle_start <=
M_PI,
423 float angle_end,
Color color) {
429 float angle_start,
float angle_end,
Color color,
432 color, color::Transparent, color::Transparent,
437 float angle_end,
Color color) {
439 angle_end,
color, color::Transparent,
447 Color interior_color,
463 Box(xMin, yMin, xMax, yMax),
473struct WedgeDrawSpec {
484inline uint8_t GetWedgeShapeAlpha(
const WedgeDrawSpec& spec,
float xpax,
487 float hn = (xpax * spec.bax + ypay * spec.bay);
491 float h = hn < 0.0f ? 0.0f : hn > spec.hd ? 1.0f : hn / spec.hd;
493 float dx = xpax - spec.bax * h;
494 float dy = ypay - spec.bay * h;
495 float l_sq = dx * dx + dy * dy;
497 float adj_dist = spec.r - h * spec.dr;
498 float adj_dist_sq = adj_dist * adj_dist;
500 if (adj_dist_sq < l_sq)
return 0;
501 if (!spec.round_endings) {
505 float d1 = (hn / spec.hd) * spec.sqrt_hd;
506 float d2 = (1.0f - hn / spec.hd) * spec.sqrt_hd;
509 if (d1 < -0.5f)
return 0;
510 float d = adj_dist - sqrtf(l_sq);
511 return uint8_t(std::min(d, (d1 + 0.5f)) * spec.max_alpha);
514 if (d2 < -0.5f)
return 0;
515 float d = adj_dist - sqrtf(l_sq);
516 return uint8_t(std::min(d, (d2 + 0.5f)) * spec.max_alpha);
520 if (adj_dist < 1.0f) {
521 float l = sqrtf(l_sq);
523 if (l + adj_dist < 1.0f) {
525 d = 2 * adj_dist - 1.0f;
530 return (uint8_t)(d * spec.max_alpha);
534 if (adj_dist_sq - 2 * adj_dist + 1 > l_sq)
return spec.max_alpha;
537 float l = sqrtf(l_sq);
538 float d = adj_dist - l;
539 return (uint8_t)(d * spec.max_alpha);
542void DrawWedge(
const SmoothShape::Wedge& wedge,
const Surface& s,
546 float ax = wedge.ax + dx;
547 float ay = wedge.ay + dy;
548 float bx = wedge.bx + dx;
549 float by = wedge.by + dy;
555 .r = wedge.ar + 0.5f,
556 .dr = wedge.ar - wedge.br,
557 .bax = wedge.bx - wedge.ax,
558 .bay = wedge.by - wedge.ay,
560 .sqrt_hd = sqrtf(bay_dsq),
561 .max_alpha = wedge.color.a(),
562 .round_endings = wedge.round_endings,
566 int32_t ys = wedge.ay;
567 if ((ax - wedge.ar) > (bx - wedge.br)) ys = wedge.by;
571 bool can_minimize_scan =
575 if (ys < box.yMin()) ys = box.yMin();
576 int32_t xs = box.xMin();
577 Color preblended =
AlphaBlend(s.bgcolor(), wedge.color);
579 BufferedPixelWriter
writer(s.out(), s.blending_mode());
583 for (int32_t yp = ys; yp <= box.yMax(); yp++) {
586 for (int32_t xp = xs; xp <= box.xMax(); xp++) {
587 if (endX && alpha == 0 && can_minimize_scan)
break;
590 alpha = GetWedgeShapeAlpha(spec, xpax, ypay);
591 if (alpha == 0 && can_minimize_scan)
continue;
595 if (can_minimize_scan) {
609 if (ys > box.yMax()) ys = box.yMax();
614 for (int32_t yp = ys - 1; yp >= box.yMin(); yp--) {
617 for (int32_t xp = xs; xp <= box.xMax(); xp++) {
618 if (endX && alpha == 0 && can_minimize_scan)
622 alpha = GetWedgeShapeAlpha(spec, xpax, ypay);
623 if (alpha == 0 && can_minimize_scan)
continue;
627 if (can_minimize_scan) {
642void ReadWedgeColors(
const SmoothShape::Wedge& wedge,
const int16_t* x,
643 const int16_t* y, uint32_t count, Color* result) {
647 float bax = wedge.bx - wedge.ax;
648 float bay = wedge.by - wedge.ay;
652 .r = wedge.ar + 0.5f,
653 .dr = wedge.ar - wedge.br,
654 .bax = wedge.bx - wedge.ax,
655 .bay = wedge.by - wedge.ay,
657 .sqrt_hd = sqrtf(bay_dsq),
658 .max_alpha = wedge.color.a(),
659 .round_endings = wedge.round_endings,
661 while (count-- > 0) {
662 *result++ = wedge.color.withA(
663 GetWedgeShapeAlpha(spec, *x++ - wedge.ax, *y++ - wedge.ay));
669inline Color GetSmoothRoundRectPixelColor(
const SmoothShape::RoundRect& rect,
670 int16_t x, int16_t y) {
676 float ref_x = std::min(std::max((
float)x, rect.x0), rect.x1);
677 float ref_y = std::min(std::max((
float)y, rect.y0), rect.y1);
678 float dx = x - ref_x;
679 float dy = y - ref_y;
681 Color interior = rect.interior_color;
686 float d_squared = dx * dx + dy * dy;
689 Color outline = rect.outline_color;
691 if (d_squared <= rect.ri_sq_adj - ri && ri >= 0.5f) {
695 if (d_squared >= rect.ro_sq_adj + ro) {
697 return color::Transparent;
699 bool fully_within_outer = d_squared <= rect.ro_sq_adj - ro;
700 bool fully_outside_inner = ro == ri || d_squared >= rect.ri_sq_adj + ri;
701 if (fully_within_outer && fully_outside_inner) {
711 float d = sqrtf(d_squared);
712 if (fully_outside_inner) {
714 return outline.withA((uint8_t)(outline.a() * (ro - d + 0.5f)));
716 if (fully_within_outer) {
718 float opacity = 1.0f - (ri - d + 0.5f);
719 if (ri < 0.5f && (roundf(rect.x0 - ri) == roundf(rect.x1 + ri) ||
720 roundf(rect.y0 - ri) == roundf(rect.y1 + ri))) {
722 opacity = std::min(1.0f, opacity * 2.0f);
725 outline.withA((uint8_t)(outline.a() * opacity)));
729 interior, outline.withA((uint8_t)(outline.a() *
730 std::max(0.0f, (ro - d + 0.5f) -
739 OUTLINE_INACTIVE = 4,
742inline float CalcDistSqRect(
float x0,
float y0,
float x1,
float y1,
float xt,
744 float dx = (xt <= x0 ? xt - x0 : xt >= x1 ? xt - x1 : 0.0f);
745 float dy = (yt <= y0 ? yt - y0 : yt >= y1 ? yt - y1 : 0.0f);
746 return dx * dx + dy * dy;
750inline RectColor DetermineRectColorForRoundRect(
751 const SmoothShape::RoundRect& rect,
const Box& box) {
752 if (rect.inner_mid.contains(box) || rect.inner_wide.contains(box) ||
753 rect.inner_tall.contains(box)) {
756 float xMin = box.xMin() - 0.5f;
757 float yMin = box.yMin() - 0.5f;
758 float xMax = box.xMax() + 0.5f;
759 float yMax = box.yMax() + 0.5f;
760 float dtl = CalcDistSqRect(rect.x0, rect.y0, rect.x1, rect.y1, xMin, yMin);
761 float dtr = CalcDistSqRect(rect.x0, rect.y0, rect.x1, rect.y1, xMax, yMin);
762 float dbl = CalcDistSqRect(rect.x0, rect.y0, rect.x1, rect.y1, xMin, yMax);
763 float dbr = CalcDistSqRect(rect.x0, rect.y0, rect.x1, rect.y1, xMax, yMax);
765 float r_min_sq = rect.ri_sq_adj - rect.ri;
767 if (dtl < r_min_sq && dtr < r_min_sq && dbl < r_min_sq && dbr < r_min_sq) {
771 float r_max_sq = rect.ro_sq_adj + rect.ro;
774 if (xMax < rect.x0) {
775 if (yMax < rect.y0) {
776 if (dbr >= r_max_sq) {
779 }
else if (yMin > rect.y1) {
780 if (dtr >= r_max_sq) {
784 }
else if (xMin > rect.x1) {
785 if (yMax < rect.y0) {
786 if (dbl >= r_max_sq) {
789 }
else if (yMin > rect.y1) {
790 if (dtl >= r_max_sq) {
797 if (xMax <= rect.x1) {
798 if (yMax <= rect.y1) {
799 float r_ring_max_sq = rect.ro_sq_adj - rect.ro;
800 float r_ring_min_sq = rect.ri_sq_adj + rect.ri;
801 if (dtl < r_ring_max_sq && dtl > r_ring_min_sq && dbr < r_ring_max_sq &&
802 dbr > r_ring_min_sq) {
803 return OUTLINE_ACTIVE;
805 }
else if (yMin >= rect.y0) {
806 float r_ring_max_sq = rect.ro_sq_adj - rect.ro;
807 float r_ring_min_sq = rect.ri_sq_adj + rect.ri;
808 if (dtr < r_ring_max_sq && dtr > r_ring_min_sq && dbl < r_ring_max_sq &&
809 dbl > r_ring_min_sq) {
810 return OUTLINE_ACTIVE;
813 }
else if (xMin >= rect.x0) {
814 if (yMax <= rect.y1) {
815 float r_ring_max_sq = rect.ro_sq_adj - rect.ro;
816 float r_ring_min_sq = rect.ri_sq_adj + rect.ri;
817 if (dtr < r_ring_max_sq && dtr > r_ring_min_sq && dbl < r_ring_max_sq &&
818 dbl > r_ring_min_sq) {
819 return OUTLINE_ACTIVE;
821 }
else if (yMin >= rect.y0) {
822 float r_ring_max_sq = rect.ro_sq_adj - rect.ro;
823 float r_ring_min_sq = rect.ri_sq_adj + rect.ri;
824 if (dtl < r_ring_max_sq && dtl > r_ring_min_sq && dbr < r_ring_max_sq &&
825 dbr > r_ring_min_sq) {
826 return OUTLINE_ACTIVE;
834bool ReadColorRectOfRoundRect(
const SmoothShape::RoundRect& rect, int16_t xMin,
835 int16_t yMin, int16_t xMax, int16_t yMax,
837 Box box(xMin, yMin, xMax, yMax);
838 switch (DetermineRectColorForRoundRect(rect, box)) {
840 *result = color::Transparent;
844 *result = rect.interior_color;
847 case OUTLINE_ACTIVE: {
848 *result = rect.outline_color;
855 for (int16_t y = yMin; y <= yMax; ++y) {
856 for (int16_t x = xMin; x <= xMax; ++x) {
857 *
out++ = GetSmoothRoundRectPixelColor(rect, x, y);
870void ReadRoundRectColors(
const SmoothShape::RoundRect& rect,
const int16_t* x,
871 const int16_t* y, uint32_t count, Color* result) {
872 while (count-- > 0) {
873 if (rect.inner_mid.contains(*x, *y) || rect.inner_wide.contains(*x, *y) ||
874 rect.inner_tall.contains(*x, *y)) {
875 *result = rect.interior_color;
877 *result = GetSmoothRoundRectPixelColor(rect, *x, *y);
885struct RoundRectDrawSpec {
895inline void FillSubrectOfRoundRect(
const SmoothShape::RoundRect& rect,
896 const RoundRectDrawSpec& spec,
898 Color interior = rect.interior_color;
899 Color outline = rect.outline_color;
900 switch (DetermineRectColorForRoundRect(rect, box)) {
903 spec.out->fillRect(spec.blending_mode, box, spec.bgcolor);
909 interior != color::Transparent) {
910 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_interior);
914 case OUTLINE_ACTIVE: {
916 outline != color::Transparent) {
917 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_outline);
927 BufferedPixelWriter
writer(*spec.out, spec.blending_mode);
928 for (int16_t y = box.yMin(); y <= box.yMax(); ++y) {
929 for (int16_t x = box.xMin(); x <= box.xMax(); ++x) {
930 Color c = GetSmoothRoundRectPixelColor(rect, x, y);
931 if (c == color::Transparent)
continue;
933 c == interior ? spec.pre_blended_interior
941 for (int16_t y = box.yMin(); y <= box.yMax(); ++y) {
942 for (int16_t x = box.xMin(); x <= box.xMax(); ++x) {
943 Color c = GetSmoothRoundRectPixelColor(rect, x, y);
944 color[cnt++] = c.
a() == 0 ? spec.bgcolor
945 : c == interior ? spec.pre_blended_interior
946 : c == outline ? spec.pre_blended_outline
950 spec.out->setAddress(box, spec.blending_mode);
951 spec.out->write(color, cnt);
955void FillSubrectangle(
const SmoothShape::RoundRect& rect,
956 const RoundRectDrawSpec& spec,
const Box& box) {
957 const int16_t xMinOuter = (box.xMin() / 8) * 8;
958 const int16_t yMinOuter = (box.yMin() / 8) * 8;
959 const int16_t xMaxOuter = (box.xMax() / 8) * 8 + 7;
960 const int16_t yMaxOuter = (box.yMax() / 8) * 8 + 7;
961 for (int16_t y = yMinOuter; y < yMaxOuter; y += 8) {
962 for (int16_t x = xMinOuter; x < xMaxOuter; x += 8) {
963 FillSubrectOfRoundRect(
965 Box(std::max(x, box.xMin()), std::max(y, box.yMin()),
966 std::min((int16_t)(x + 7), box.xMax()),
967 std::min((int16_t)(y + 7), box.yMax())));
972void DrawRoundRect(SmoothShape::RoundRect rect,
const Surface& s,
974 RoundRectDrawSpec spec{
976 .fill_mode = s.fill_mode(),
977 .blending_mode = s.blending_mode(),
978 .bgcolor = s.bgcolor(),
980 AlphaBlend(s.bgcolor(), rect.interior_color), rect.outline_color),
981 .pre_blended_interior =
AlphaBlend(s.bgcolor(), rect.interior_color),
983 if (s.dx() != 0 || s.dy() != 0) {
988 rect.inner_wide = rect.inner_wide.translate(s.dx(), s.dy());
989 rect.inner_mid = rect.inner_mid.translate(s.dx(), s.dy());
990 rect.inner_tall = rect.inner_tall.translate(s.dx(), s.dy());
993 uint32_t pixel_count = box.area();
994 if (pixel_count <= 64) {
995 FillSubrectOfRoundRect(rect, spec, box);
999 if (rect.inner_mid.width() <= 16) {
1000 FillSubrectangle(rect, spec, box);
1002 const Box& inner = rect.inner_mid;
1003 FillSubrectangle(rect, spec,
1004 Box(box.xMin(), box.yMin(), box.xMax(), inner.yMin() - 1));
1007 Box(box.xMin(), inner.yMin(), inner.xMin() - 1, inner.yMax()));
1009 rect.interior_color != color::Transparent) {
1010 s.out().fillRect(s.blending_mode(), inner, spec.pre_blended_interior);
1014 Box(inner.xMax() + 1, inner.yMin(), box.xMax(), inner.yMax()));
1015 FillSubrectangle(rect, spec,
1016 Box(box.xMin(), inner.yMax() + 1, box.xMax(), box.yMax()));
1032Color GetSmoothArcPixelColor(
const SmoothShape::Arc& spec, int16_t x,
1034 float dx = x - spec.xc;
1035 float dy = y - spec.yc;
1036 if (spec.inner_mid.contains(x, y)) {
1037 return spec.interior_color;
1039 float d_squared = dx * dx + dy * dy;
1040 if (spec.ri >= 0.5f && d_squared <= spec.ri_sq_adj - spec.ri) {
1042 return spec.interior_color;
1044 if (d_squared >= spec.ro_sq_adj + spec.ro) {
1046 return color::Transparent;
1055 int qx = (dx < 0.5) << 0 | (dx > -0.5) << 1;
1056 int quadrant = (((dy < 0.5) * 3) & qx) | (((dy > -0.5)) * 3 & qx) << 2;
1058 if ((quadrant & spec.quadrants_) == quadrant) {
1060 color = spec.outline_active_color;
1061 }
else if ((quadrant & (spec.quadrants_ >> 4)) == quadrant) {
1063 color = spec.outline_inactive_color;
1065 bool within_range =
false;
1066 float n1 = spec.start_y_slope * dx - spec.start_x_slope * dy;
1067 float n2 = spec.end_x_slope * dy - spec.end_y_slope * dx;
1068 if (spec.range_angle_sharp) {
1069 within_range = (n1 <= -0.5 && n2 <= -0.5);
1071 within_range = (n1 <= -0.5 || n2 <= -0.5);
1074 color = spec.outline_active_color;
1078 if (spec.round_endings) {
1079 float dxs = dx - spec.start_x_rc;
1080 float dys = dy - spec.start_y_rc;
1081 float dxe = dx - spec.end_x_rc;
1082 float dye = dy - spec.end_y_rc;
1085 float smaller_dist_sq =
1086 std::min(dxs * dxs + dys * dys, dxe * dxe + dye * dye);
1087 if (smaller_dist_sq > spec.rm_sq_adj + spec.rm) {
1088 color = spec.outline_inactive_color;
1089 }
else if (smaller_dist_sq < spec.rm_sq_adj - spec.rm) {
1090 color = spec.outline_active_color;
1094 float d = sqrt(smaller_dist_sq);
1096 spec.outline_active_color.withA((
1097 uint8_t)(0.5f + spec.outline_active_color.a() *
1098 (spec.rm - d + 0.5f))));
1101 if (spec.range_angle_sharp) {
1102 bool outside_range = (n1 >= 0.5f || n2 >= 0.5f);
1103 if (outside_range) {
1104 color = spec.outline_inactive_color;
1107 if (n1 > -0.5f && n1 < 0.5f &&
1108 spec.start_x_slope * dx + spec.start_y_slope * dy > 0) {
1111 alpha *= (1 - (n1 + 0.5f));
1113 if (n2 < 0.5f && n2 > -0.5f &&
1114 spec.end_x_slope * dx + spec.end_y_slope * dy >= 0) {
1117 alpha *= (1 - (n2 + 0.5f));
1120 spec.outline_inactive_color,
1121 spec.outline_active_color.withA(
1122 (uint8_t)(0.5f + spec.outline_active_color.a() * alpha)));
1127 bool inside_range = (n1 <= -0.5f || n2 <= -0.5f);
1129 color = spec.outline_active_color;
1132 if (n1 > -0.5f && n1 < 0.5f &&
1133 spec.start_y_slope * dy + spec.start_x_slope * dx >= 0) {
1136 alpha *= (n1 + 0.5f);
1138 if (n2 < 0.5f && n2 > -0.5f &&
1139 spec.end_x_slope * dx + spec.end_y_slope * dy > 0) {
1142 alpha *= (n2 + 0.5f);
1144 alpha = 1.0f - alpha;
1146 spec.outline_inactive_color,
1147 spec.outline_active_color.withA(
1148 (uint8_t)(0.5f + spec.outline_active_color.a() * alpha)));
1156 bool fully_within_outer = d_squared <= spec.ro_sq_adj - spec.ro;
1157 bool fully_outside_inner = spec.ro == spec.ri ||
1158 d_squared >= spec.ri_sq_adj + spec.ri ||
1161 if (fully_within_outer && fully_outside_inner) {
1165 float d = sqrtf(d_squared);
1166 if (fully_outside_inner) {
1169 if (fully_within_outer) {
1171 spec.interior_color,
1175 spec.interior_color,
1177 (uint8_t)(
color.
a() * std::max(0.0f, (spec.ro - d + 0.5f) -
1178 (spec.ri - d + 0.5f)))));
1181inline float CalcDistSq(
float x1,
float y1, int16_t x2, int16_t y2) {
1184 return dx * dx + dy * dy;
1187inline bool IsRectWithinAngle(
float start_x_slope,
float start_y_slope,
1188 float end_x_slope,
float end_y_slope,
bool sharp,
1189 float cx,
float cy,
const Box& box) {
1190 float dxl = box.xMin() - cx;
1191 float dxr = box.xMax() - cx;
1192 float dyt = box.yMin() - cy;
1193 float dyb = box.yMax() - cy;
1195 return (start_y_slope * dxl - start_x_slope * dyt <= -0.5f &&
1196 start_y_slope * dxl - start_x_slope * dyb <= -0.5f &&
1197 start_y_slope * dxr - start_x_slope * dyt <= -0.5f &&
1198 start_y_slope * dxr - start_x_slope * dyb <= -0.5f) &&
1199 (end_x_slope * dyt - end_y_slope * dxl <= -0.5f &&
1200 end_x_slope * dyb - end_y_slope * dxl <= -0.5f &&
1201 end_x_slope * dyt - end_y_slope * dxr <= -0.5f &&
1202 end_x_slope * dyb - end_y_slope * dxr <= -0.5f);
1204 return (start_y_slope * dxl - start_x_slope * dyt <= -0.5f &&
1205 start_y_slope * dxl - start_x_slope * dyb <= -0.5f &&
1206 start_y_slope * dxr - start_x_slope * dyt <= -0.5f &&
1207 start_y_slope * dxr - start_x_slope * dyb <= -0.5f) ||
1208 (end_x_slope * dyt - end_y_slope * dxl <= -0.5f &&
1209 end_x_slope * dyb - end_y_slope * dxl <= -0.5f &&
1210 end_x_slope * dyt - end_y_slope * dxr <= -0.5f &&
1211 end_x_slope * dyb - end_y_slope * dxr <= -0.5f);
1215inline bool IsPointWithinCircle(
float cx,
float cy,
float r_sq,
float x,
1219 return dx * dx + dy * dy <= r_sq;
1222inline bool IsRectWithinCircle(
float cx,
float cy,
float r_sq,
const Box& box) {
1223 return IsPointWithinCircle(cx, cy, r_sq, box.xMin(), box.yMin()) &&
1224 IsPointWithinCircle(cx, cy, r_sq, box.xMin(), box.yMax()) &&
1225 IsPointWithinCircle(cx, cy, r_sq, box.xMax(), box.yMin()) &&
1226 IsPointWithinCircle(cx, cy, r_sq, box.xMax(), box.yMax());
1230inline RectColor DetermineRectColorForArc(
const SmoothShape::Arc& arc,
1232 if (arc.inner_mid.contains(box)) {
1235 int16_t xMin = box.xMin();
1236 int16_t yMin = box.yMin();
1237 int16_t xMax = box.xMax();
1238 int16_t yMax = box.yMax();
1239 float dtl = CalcDistSq(arc.xc, arc.yc, xMin, yMin);
1240 float dtr = CalcDistSq(arc.xc, arc.yc, xMax, yMin);
1241 float dbl = CalcDistSq(arc.xc, arc.yc, xMin, yMax);
1242 float dbr = CalcDistSq(arc.xc, arc.yc, xMax, yMax);
1244 float r_min_sq = arc.ri_sq_adj - arc.ri;
1246 if (dtl < r_min_sq && dtr < r_min_sq && dbl < r_min_sq && dbr < r_min_sq) {
1250 float r_max_sq = arc.ro_sq_adj + arc.ro;
1253 if (xMax < arc.xc) {
1254 if (yMax < arc.yc) {
1255 if (dbr >= r_max_sq) {
1258 }
else if (yMin > arc.yc) {
1259 if (dtr >= r_max_sq) {
1263 }
else if (xMin > arc.xc) {
1264 if (yMax < arc.yc) {
1265 if (dbl >= r_max_sq) {
1268 }
else if (yMin > arc.yc) {
1269 if (dtl >= r_max_sq) {
1274 if (arc.round_endings) {
1275 if (arc.nonempty_cutoff) {
1281 if (xMax <= arc.xc) {
1282 if (yMax <= arc.yc) {
1283 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1284 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1285 if (dtl > r_ring_max_sq || dtl < r_ring_min_sq || dbr > r_ring_max_sq ||
1286 dbr < r_ring_min_sq) {
1290 if ((arc.quadrants_ & 1) && xMax <= arc.xc - 0.5f &&
1291 yMax <= arc.yc - 0.5f) {
1292 return OUTLINE_ACTIVE;
1294 if ((arc.quadrants_ & 0x10) && xMax <= arc.xc - 0.5f &&
1295 yMax <= arc.yc - 0.5f) {
1296 return OUTLINE_INACTIVE;
1298 }
else if (yMin >= arc.yc) {
1299 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1300 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1301 if (dtr > r_ring_max_sq || dtr < r_ring_min_sq || dbl > r_ring_max_sq ||
1302 dbl < r_ring_min_sq) {
1306 if (arc.quadrants_ & 4 && xMax <= arc.xc - 0.5f &&
1307 yMin >= arc.yc + 0.5f) {
1308 return OUTLINE_ACTIVE;
1310 if (arc.quadrants_ & 0x40 && xMax <= arc.xc - 0.5f &&
1311 yMin >= arc.yc + 0.5f) {
1312 return OUTLINE_INACTIVE;
1314 }
else if (xMax <= arc.xc - arc.ri - 0.5f) {
1315 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1316 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1317 if (dtl > r_ring_max_sq || dtl < r_ring_min_sq || dbl > r_ring_max_sq ||
1318 dbl < r_ring_min_sq) {
1321 if ((arc.quadrants_ & 0b0101) == 0b0101) {
1322 return OUTLINE_ACTIVE;
1328 }
else if (xMin >= arc.xc) {
1329 if (yMax <= arc.yc) {
1330 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1331 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1332 if (dtr > r_ring_max_sq || dtr < r_ring_min_sq || dbl > r_ring_max_sq ||
1333 dbl < r_ring_min_sq) {
1337 if (arc.quadrants_ & 2 && xMin >= arc.xc + 0.5f &&
1338 yMax <= arc.yc - 0.5f) {
1339 return OUTLINE_ACTIVE;
1341 if (arc.quadrants_ & 0x20 && xMin >= arc.xc + 0.5f &&
1342 yMax <= arc.yc - 0.5f) {
1343 return OUTLINE_INACTIVE;
1345 }
else if (yMin >= arc.yc) {
1346 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1347 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1348 if (dtl > r_ring_max_sq || dtl < r_ring_min_sq || dbr > r_ring_max_sq ||
1349 dbr < r_ring_min_sq) {
1353 if (arc.quadrants_ & 8 && xMin >= arc.xc + 0.5f &&
1354 yMin >= arc.yc + 0.5f) {
1355 return OUTLINE_ACTIVE;
1357 if (arc.quadrants_ & 0x80 && xMin >= arc.xc + 0.5f &&
1358 yMin >= arc.yc + 0.5f) {
1359 return OUTLINE_INACTIVE;
1361 }
else if (xMin >= arc.xc + arc.ri + 0.5f) {
1362 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1363 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1364 if (dtr > r_ring_max_sq || dtr < r_ring_min_sq || dbr > r_ring_max_sq ||
1365 dbr < r_ring_min_sq) {
1369 if ((arc.quadrants_ & 0b1010) == 0b1010) {
1370 return OUTLINE_ACTIVE;
1375 }
else if (yMax <= arc.yc - arc.ri - 0.5f) {
1376 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1377 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1378 if (dtl > r_ring_max_sq || dtl < r_ring_min_sq || dtr > r_ring_max_sq ||
1379 dtr < r_ring_min_sq) {
1383 if ((arc.quadrants_ & 0b0011) == 0b0011) {
1384 return OUTLINE_ACTIVE;
1386 }
else if (yMin >= arc.yc + arc.ri + 0.5f) {
1387 float r_ring_max_sq = arc.ro_sq_adj - arc.ro;
1388 float r_ring_min_sq = arc.ri_sq_adj + arc.ri;
1389 if (dbl > r_ring_max_sq || dbl < r_ring_min_sq || dbr > r_ring_max_sq ||
1390 dbr < r_ring_min_sq) {
1394 if ((arc.quadrants_ & 0b1100) == 0b1100) {
1395 return OUTLINE_ACTIVE;
1404 if (IsRectWithinAngle(arc.start_x_slope, arc.start_y_slope, arc.end_x_slope,
1405 arc.end_y_slope, arc.range_angle_sharp, arc.xc, arc.yc,
1407 return OUTLINE_ACTIVE;
1411 if (arc.nonempty_cutoff &&
1412 IsRectWithinAngle(arc.end_cutoff_x_slope, arc.end_cutoff_y_slope,
1413 arc.start_cutoff_x_slope, arc.start_cutoff_y_slope,
1414 !arc.cutoff_angle_sharp, arc.xc, arc.yc, box)) {
1415 return OUTLINE_INACTIVE;
1420 if (arc.round_endings) {
1421 if (IsRectWithinCircle(arc.xc + arc.start_x_rc, arc.yc + arc.start_y_rc,
1422 arc.rm_sq_adj - arc.rm, box) ||
1423 IsRectWithinCircle(arc.xc + arc.end_x_rc, arc.yc + arc.end_y_rc,
1424 arc.rm_sq_adj - arc.rm, box)) {
1425 return OUTLINE_ACTIVE;
1434void FillSubrectOfArc(
const SmoothShape::Arc& arc,
const ArcDrawSpec& spec,
1436 Color interior = arc.interior_color;
1437 Color outline_active = arc.outline_active_color;
1438 Color outline_inactive = arc.outline_inactive_color;
1439 switch (DetermineRectColorForArc(arc, box)) {
1442 spec.out->fillRect(spec.blending_mode, box, spec.bgcolor);
1448 interior != color::Transparent) {
1449 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_interior);
1453 case OUTLINE_ACTIVE: {
1455 outline_active != color::Transparent) {
1456 spec.out->fillRect(spec.blending_mode, box,
1457 spec.pre_blended_outline_active);
1461 case OUTLINE_INACTIVE: {
1463 outline_inactive != color::Transparent) {
1464 spec.out->fillRect(spec.blending_mode, box,
1465 spec.pre_blended_outline_inactive);
1475 BufferedPixelWriter
writer(*spec.out, spec.blending_mode);
1476 for (int16_t y = box.yMin(); y <= box.yMax(); ++y) {
1477 for (int16_t x = box.xMin(); x <= box.xMax(); ++x) {
1478 Color c = GetSmoothArcPixelColor(arc, x, y);
1479 if (c == color::Transparent)
continue;
1482 c == interior ? spec.pre_blended_interior
1491 for (int16_t y = box.yMin(); y <= box.yMax(); ++y) {
1492 for (int16_t x = box.xMin(); x <= box.xMax(); ++x) {
1493 Color c = GetSmoothArcPixelColor(arc, x, y);
1494 color[cnt++] = c.
a() == 0 ? spec.bgcolor
1495 : c == interior ? spec.pre_blended_interior
1496 : c == outline_active ? spec.pre_blended_outline_active
1497 : c == outline_inactive
1498 ? spec.pre_blended_outline_inactive
1502 spec.out->setAddress(box, spec.blending_mode);
1503 spec.out->write(color, cnt);
1507void DrawArc(SmoothShape::Arc arc,
const Surface& s,
const Box& box) {
1510 .fill_mode = s.fill_mode(),
1511 .blending_mode = s.blending_mode(),
1512 .bgcolor = s.bgcolor(),
1513 .pre_blended_outline_active =
1515 arc.outline_active_color),
1516 .pre_blended_outline_inactive =
1518 arc.outline_inactive_color),
1519 .pre_blended_interior =
AlphaBlend(s.bgcolor(), arc.interior_color),
1521 if (s.dx() != 0 || s.dy() != 0) {
1526 arc.inner_mid = arc.inner_mid.translate(s.dx(), s.dy());
1528 int16_t xMin = box.xMin();
1529 int16_t xMax = box.xMax();
1530 int16_t yMin = box.yMin();
1531 int16_t yMax = box.yMax();
1533 uint32_t pixel_count = box.area();
1534 if (pixel_count <= 64) {
1535 FillSubrectOfArc(arc, spec, box);
1539 const int16_t xMinOuter = (xMin / 8) * 8;
1540 const int16_t yMinOuter = (yMin / 8) * 8;
1541 const int16_t xMaxOuter = (xMax / 8) * 8 + 7;
1542 const int16_t yMaxOuter = (yMax / 8) * 8 + 7;
1543 for (int16_t y = yMinOuter; y < yMaxOuter; y += 8) {
1544 for (int16_t x = xMinOuter; x < xMaxOuter; x += 8) {
1545 FillSubrectOfArc(arc, spec,
1546 Box(std::max(x, box.xMin()), std::max(y, box.yMin()),
1547 std::min((int16_t)(x + 7), box.xMax()),
1548 std::min((int16_t)(y + 7), box.yMax())));
1553bool ReadColorRectOfArc(
const SmoothShape::Arc& arc, int16_t xMin, int16_t yMin,
1554 int16_t xMax, int16_t yMax, Color* result) {
1555 Box box(xMin, yMin, xMax, yMax);
1556 switch (DetermineRectColorForArc(arc, box)) {
1558 *result = color::Transparent;
1562 *result = arc.interior_color;
1565 case OUTLINE_ACTIVE: {
1566 *result = arc.outline_active_color;
1569 case OUTLINE_INACTIVE: {
1570 *result = arc.outline_inactive_color;
1576 Color*
out = result;
1577 for (int16_t y = yMin; y <= yMax; ++y) {
1578 for (int16_t x = xMin; x <= xMax; ++x) {
1579 *
out++ = GetSmoothArcPixelColor(arc, x, y);
1583 Color c = result[0];
1584 uint32_t pixel_count = box.area();
1585 for (uint32_t i = 1; i < pixel_count; i++) {
1586 if (result[i] != c)
return false;
1591void ReadArcColors(
const SmoothShape::Arc& arc,
const int16_t* x,
1592 const int16_t* y, uint32_t count, Color* result) {
1593 while (count-- > 0) {
1594 *result++ = GetSmoothArcPixelColor(arc, *x++, *y++);
1600struct TriangleDrawSpec {
1608Color GetSmoothTrianglePixelColor(
const SmoothShape::Triangle& t, int16_t x,
1610 float n1 = t.dy12 * (x - t.x1) - t.dx12 * (y - t.y1);
1611 float n2 = t.dy23 * (x - t.x2) - t.dx23 * (y - t.y2);
1612 float n3 = t.dy31 * (x - t.x3) - t.dx31 * (y - t.y3);
1613 if (n1 <= -0.5f && n2 <= -0.5f && n3 <= -0.5f) {
1617 if (n1 >= 0.5f || n2 >= 0.5f || n3 >= 0.5f) {
1619 return color::Transparent;
1624 return t.color.withA(roundf(t.color.a() * std::min(1.0f, 0.5f - n1) *
1625 std::min(1.0f, 0.5f - n2) *
1626 std::min(1.0f, 0.5f - n3)));
1629inline bool IsPointWithinTriangle(
const SmoothShape::Triangle& t,
float x,
1631 float n1 = t.dy12 * (x - t.x1) - t.dx12 * (y - t.y1);
1632 if (n1 > -0.5f)
return false;
1633 float n2 = t.dy23 * (x - t.x2) - t.dx23 * (y - t.y2);
1634 if (n2 > -0.5f)
return false;
1635 float n3 = t.dy31 * (x - t.x3) - t.dx31 * (y - t.y3);
1636 if (n3 > -0.5f)
return false;
1640inline bool IsRectWithinTriangle(
const SmoothShape::Triangle& t,
1642 return IsPointWithinTriangle(t, box.xMin(), box.yMin()) &&
1643 IsPointWithinTriangle(t, box.xMin(), box.yMax()) &&
1644 IsPointWithinTriangle(t, box.xMax(), box.yMin()) &&
1645 IsPointWithinTriangle(t, box.xMax(), box.yMax());
1648inline bool IsRectOutsideLine(
float x0,
float y0,
float dx,
float dy,
1650 float n1 = dy * (box.xMin() - x0) - dx * (box.yMin() - y0);
1651 if (n1 <= 0.5f)
return false;
1652 float n2 = dy * (box.xMin() - x0) - dx * (box.yMax() - y0);
1653 if (n2 <= 0.5f)
return false;
1654 float n3 = dy * (box.xMax() - x0) - dx * (box.yMin() - y0);
1655 if (n3 <= 0.5f)
return false;
1656 float n4 = dy * (box.xMax() - x0) - dx * (box.yMax() - y0);
1657 if (n4 <= 0.5f)
return false;
1661inline bool IsRectOutsideTriangle(
const SmoothShape::Triangle& t,
1665 return IsRectOutsideLine(t.x1, t.y1, t.dx12, t.dy12, box) ||
1666 IsRectOutsideLine(t.x2, t.y2, t.dx23, t.dy23, box) ||
1667 IsRectOutsideLine(t.x3, t.y3, t.dx31, t.dy31, box);
1671inline RectColor DetermineRectColorForTriangle(
1672 const SmoothShape::Triangle& triangle,
const Box& box) {
1674 if (IsRectWithinTriangle(triangle, box)) {
1677 if (IsRectOutsideTriangle(triangle, box)) {
1684void FillSubrectOfTriangle(
const SmoothShape::Triangle& triangle,
1685 const TriangleDrawSpec& spec,
const Box& box) {
1686 Color interior = triangle.color;
1687 switch (DetermineRectColorForTriangle(triangle, box)) {
1690 spec.out->fillRect(spec.blending_mode, box, spec.bgcolor);
1696 interior != color::Transparent) {
1697 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_interior);
1707 BufferedPixelWriter
writer(*spec.out, spec.blending_mode);
1708 for (int16_t y = box.yMin(); y <= box.yMax(); ++y) {
1709 for (int16_t x = box.xMin(); x <= box.xMax(); ++x) {
1710 Color c = GetSmoothTrianglePixelColor(triangle, x, y);
1711 if (c == color::Transparent)
continue;
1713 c == interior ? spec.pre_blended_interior
1720 for (int16_t y = box.yMin(); y <= box.yMax(); ++y) {
1721 for (int16_t x = box.xMin(); x <= box.xMax(); ++x) {
1722 Color c = GetSmoothTrianglePixelColor(triangle, x, y);
1723 color[cnt++] = c.
a() == 0 ? spec.bgcolor
1724 : c == interior ? spec.pre_blended_interior
1728 spec.out->setAddress(box, spec.blending_mode);
1729 spec.out->write(color, cnt);
1733void DrawTriangle(SmoothShape::Triangle triangle,
const Surface& s,
1735 TriangleDrawSpec spec{
1737 .fill_mode = s.fill_mode(),
1738 .blending_mode = s.blending_mode(),
1739 .bgcolor = s.bgcolor(),
1740 .pre_blended_interior =
AlphaBlend(s.bgcolor(), triangle.color),
1742 if (s.dx() != 0 || s.dy() != 0) {
1743 triangle.x1 += s.dx();
1744 triangle.y1 += s.dx();
1745 triangle.x2 += s.dx();
1746 triangle.y2 += s.dx();
1747 triangle.x3 += s.dx();
1748 triangle.y3 += s.dx();
1750 int16_t xMin = box.xMin();
1751 int16_t xMax = box.xMax();
1752 int16_t yMin = box.yMin();
1753 int16_t yMax = box.yMax();
1755 uint32_t pixel_count = box.area();
1756 if (pixel_count <= 64) {
1757 FillSubrectOfTriangle(triangle, spec, box);
1761 const int16_t xMinOuter = (xMin / 8) * 8;
1762 const int16_t yMinOuter = (yMin / 8) * 8;
1763 const int16_t xMaxOuter = (xMax / 8) * 8 + 7;
1764 const int16_t yMaxOuter = (yMax / 8) * 8 + 7;
1765 for (int16_t y = yMinOuter; y < yMaxOuter; y += 8) {
1766 for (int16_t x = xMinOuter; x < xMaxOuter; x += 8) {
1767 FillSubrectOfTriangle(
1769 Box(std::max(x, box.xMin()), std::max(y, box.yMin()),
1770 std::min((int16_t)(x + 7), box.xMax()),
1771 std::min((int16_t)(y + 7), box.yMax())));
1776bool ReadColorRectOfTriangle(
const SmoothShape::Triangle& triangle,
1777 int16_t xMin, int16_t yMin, int16_t xMax,
1778 int16_t yMax, Color* result) {
1779 Box box(xMin, yMin, xMax, yMax);
1780 switch (DetermineRectColorForTriangle(triangle, box)) {
1782 *result = color::Transparent;
1786 *result = triangle.color;
1792 Color*
out = result;
1793 for (int16_t y = yMin; y <= yMax; ++y) {
1794 for (int16_t x = xMin; x <= xMax; ++x) {
1795 *
out++ = GetSmoothTrianglePixelColor(triangle, x, y);
1807void ReadTriangleColors(
const SmoothShape::Triangle& triangle,
const int16_t* x,
1808 const int16_t* y, uint32_t count, Color* result) {
1809 while (count-- > 0) {
1810 *result++ = GetSmoothTrianglePixelColor(triangle, *x++, *y++);
1814void DrawPixel(SmoothShape::Pixel pixel,
const Surface& s,
const Box& box) {
1815 int16_t x = box.xMin();
1816 int16_t y = box.yMin();
1817 s.out().fillPixels(s.blending_mode(),
AlphaBlend(s.bgcolor(), pixel.color),
1823void SmoothShape::drawTo(
const Surface& s)
const {
1830 DrawWedge(
wedge_, s, box);
1838 DrawArc(
arc_, s, box);
1846 DrawPixel(
pixel_, s, box);
1874 while (count-- > 0) {
1880 while (count-- > 0) {
1881 *
result++ = color::Transparent;
1909 *
result = color::Transparent;
1913 *
result = color::Transparent;
BufferedRectWriter & writer
Axis-aligned integer rectangle.
int16_t xMin() const
Minimum x (inclusive).
int16_t xMax() const
Maximum x (inclusive).
Box translate(int16_t x_offset, int16_t y_offset) const
Return a translated copy of this box.
int16_t yMax() const
Maximum y (inclusive).
static Box Intersect(const Box &a, const Box &b)
Return the intersection of two boxes (may be empty).
int16_t yMin() const
Minimum y (inclusive).
void writePixel(int16_t x, int16_t y, Color color)
ARGB8888 color stored as a 32-bit unsigned integer.
constexpr uint8_t a() const
Alpha channel.
constexpr Color withA(uint8_t a) const
Return a copy with the specified alpha channel.
virtual bool readColorRect(int16_t xMin, int16_t yMin, int16_t xMax, int16_t yMax, Color *result) const
Read colors for a rectangle.
Smooth (anti-aliased) shape rasterizable.
void readColors(const int16_t *x, const int16_t *y, uint32_t count, Color *result) const override
Read colors for the given points.
bool readColorRect(int16_t xMin, int16_t yMin, int16_t xMax, int16_t yMax, Color *result) const override
Read colors for a rectangle.
Defines 140 opaque HTML named colors.
SmoothShape SmoothThickArcImpl(FpPoint center, float radius, float thickness, float angle_start, float angle_end, Color active_color, Color inactive_color, Color interior_color, EndingStyle ending_style, bool trim_to_active)
SmoothShape SmoothFilledCircle(FpPoint center, float radius, Color color)
Create a filled circle.
BlendingMode
Porter-Duff style blending modes.
Color AlphaBlend(Color bgc, Color fgc)
SmoothShape SmoothThickArc(FpPoint center, float radius, float thickness, float angle_start, float angle_end, Color color, EndingStyle ending_style)
Create an arc with thickness.
SmoothShape SmoothFilledTriangle(FpPoint a, FpPoint b, FpPoint c, Color color)
Create a filled triangle.
SmoothShape SmoothRotatedFilledRect(FpPoint center, float width, float height, float angle, Color color)
Create a rotated filled rectangle.
SmoothShape SmoothThickCircle(FpPoint center, float radius, float thickness, Color color, Color interior_color)
Create a circle with thickness (ring).
SmoothShape SmoothRoundRect(float x0, float y0, float x1, float y1, float radius, Color color, Color interior_color)
Create an outlined round-rect.
SmoothShape SmoothCircle(FpPoint center, float radius, Color color, Color interior_color)
Create a circle (optionally with interior color).
EndingStyle
Line ending style for smooth shapes.
SmoothShape SmoothThickArcWithBackground(FpPoint center, float radius, float thickness, float angle_start, float angle_end, Color active_color, Color inactive_color, Color interior_color, EndingStyle ending_style)
Create an arc with background and interior colors.
SmoothShape SmoothWedgedLine(FpPoint a, float width_a, FpPoint b, float width_b, Color color, EndingStyle ending_style)
Create a wedged line with different start/end widths.
FillMode
Specifies whether a Drawable should fill its entire extents box, including fully transparent pixels.
@ kVisible
Fully transparent pixels do not need to be filled.
@ kExtents
Fill the entire extents box (possibly with fully transparent pixels).
SmoothShape SmoothLine(FpPoint a, FpPoint b, Color color)
Create a 1-pixel-wide anti-aliased line.
SmoothShape SmoothPie(FpPoint center, float radius, float angle_start, float angle_end, Color color)
Create a pie slice.
SmoothShape SmoothFilledRoundRect(float x0, float y0, float x1, float y1, float radius, Color color)
Create a filled round-rect.
SmoothShape SmoothThickRoundRect(float x0, float y0, float x1, float y1, float radius, float thickness, Color color, Color interior_color)
Create an outlined round-rect with thickness.
SmoothShape SmoothArc(FpPoint center, float radius, float angle_start, float angle_end, Color color)
Create a 1-pixel-wide arc.
SmoothShape SmoothThickLine(FpPoint a, FpPoint b, float width, Color color, EndingStyle ending_style)
Create a line with width and ending style.
Color pre_blended_interior
Color pre_blended_outline
Color pre_blended_outline_active
BlendingMode blending_mode
Color pre_blended_outline_inactive