roo_display
API Documentation for roo_display
Loading...
Searching...
No Matches
smooth.cpp
Go to the documentation of this file.
2
3#include <math.h>
4
6
7namespace roo_display {
8
10 : kind_(SmoothShape::EMPTY), extents_(0, 0, -1, -1) {}
11
13 : kind_(SmoothShape::WEDGE),
14 extents_(std::move(extents)),
15 wedge_(std::move(wedge)) {}
16
17SmoothShape::SmoothShape(Box extents, RoundRect round_rect)
18 : kind_(SmoothShape::ROUND_RECT),
19 extents_(std::move(extents)),
20 round_rect_(std::move(round_rect)) {}
21
22SmoothShape::SmoothShape(Box extents, Arc arc)
23 : kind_(SmoothShape::ARC),
24 extents_(std::move(extents)),
25 arc_(std::move(arc)) {}
26
27SmoothShape::SmoothShape(Box extents, Triangle triangle)
28 : kind_(SmoothShape::TRIANGLE),
29 extents_(std::move(extents)),
30 triangle_(std::move(triangle)) {}
31
32// Used for tiny shapes that fit within one pixel.
33SmoothShape::SmoothShape(int16_t x, int16_t y, Pixel pixel)
34 : kind_(SmoothShape::PIXEL),
35 extents_(x, y, x, y),
36 pixel_(std::move(pixel)) {}
37
40 if (width_a < 0.0f) width_a = 0.0f;
41 if (width_b < 0.0f) width_b = 0.0f;
42 if (width_a == 0 && width_b == 0) {
43 return SmoothShape();
44 }
45 float ar = width_a / 2.0f;
46 float br = width_b / 2.0f;
48 float dr = sqrtf((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
49 if (ar + dr <= br) {
50 // 'a' fits in 'b'.
51 return SmoothFilledCircle({b.x, b.y}, br, color);
52 } else if (br + dr <= ar) {
53 // 'b' fits in 'a'.
54 return SmoothFilledCircle({a.x, a.y}, ar, color);
55 }
56 }
57 if (a.x == b.x && a.y == b.y) {
58 // Must be ENDING_FLAT. Nothing to draw.
59 return SmoothShape();
60 }
61 // if (a.y == b.y && ar == br && ar >= 1.0f) {
62 // // Horizontal line.
63 // if (ending_style == ENDING_ROUNDED) {
64 // return SmoothFilledRoundRect(a.x - ar, a.y - ar, b.x + br, b.y + br,
65 // ar, color);
66 // } else {
67 // // return SmoothFilledRect(a.x, a.y - ar, b.x, b.y + br, color);
68 // }
69 // }
70
71 SmoothShape::Wedge wedge{a.x, a.y, b.x, b.y,
72 ar, br, color, (ending_style == ENDING_ROUNDED)};
73 ar -= 0.5f;
74 br -= 0.5f;
75 int16_t x0 = (int32_t)floorf(fminf(a.x - ar, b.x - br));
76 int16_t y0 = (int32_t)floorf(fminf(a.y - ar, b.y - br));
77 int16_t x1 = (int32_t)ceilf(fmaxf(a.x + ar, b.x + br));
78 int16_t y1 = (int32_t)ceilf(fmaxf(a.y + ar, b.y + br));
79 return SmoothShape(Box(x0, y0, x1, y1), std::move(wedge));
80}
81
84 return SmoothWedgedLine(a, width, b, width, color, ending_style);
85}
86
88 return SmoothThickLine(a, b, 1.0f, color, ENDING_ROUNDED);
89}
90
92 float angle, Color color) {
93 if (width <= 0.0f || height <= 0.0f) return SmoothShape();
94 // We will use width as thickness.
95 // Before rotation, and adjusted by the center point, the line endpoints have
96 // coordinates (0, height/2) and (0, -height/2).
97 float dx = sinf(angle) * (height * 0.5f);
98 float dy = cosf(angle) * (height * 0.5f);
99 return SmoothThickLine({center.x + dx, center.y + dy},
100 {center.x - dx, center.y - dy}, width, color,
102}
103
104SmoothShape SmoothThickRoundRect(float x0, float y0, float x1, float y1,
105 float radius, float thickness, Color color,
106 Color interior_color) {
107 if (radius < 0) radius = 0;
108 if (thickness < 0) thickness = 0;
109 if (x1 < x0) std::swap(x0, x1);
110 if (y1 < y0) std::swap(y0, y1);
111 x0 -= thickness * 0.5f;
112 y0 -= thickness * 0.5f;
113 x1 += thickness * 0.5f;
114 y1 += thickness * 0.5f;
115 radius += thickness * 0.5f;
116 float w = x1 - x0;
117 float h = y1 - y0;
118 float max_radius = ((w < h) ? w : h) / 2; // 1/2 minor axis.
119 if (radius > max_radius) radius = max_radius;
120 float ro = radius;
121 if (ro < 0) ro = 0;
122 float ri = ro - thickness;
123 if (ri < 0) ri = 0;
124 Box extents((int16_t)floorf(x0 + 0.5f), (int16_t)floorf(y0 + 0.5f),
125 (int16_t)ceilf(x1 - 0.5f), (int16_t)ceilf(y1 - 0.5f));
126 if (extents.xMin() == extents.xMax() && extents.yMin() == extents.yMax()) {
127 // Special case: the rect fits into a single pixel. Let's just calculate the
128 // color and get it over with.
129 float outer_area = std::min<float>(
130 1.0f, std::max<float>(0.0f, w * h - (4.0f - M_PI) * ro * ro));
131 float inner_area = std::min<float>(
132 1.0f, std::max<float>(0.0f, (w - thickness) * (h - thickness) -
133 (4.0f - M_PI) * ri * ri));
135 interior_color = interior_color.withA(interior_color.a() * inner_area);
136 Color pixel_color = AlphaBlend(color, interior_color);
137 if (pixel_color.a() == 0) return SmoothShape();
138 return SmoothShape(extents.xMin(), extents.yMin(),
139 SmoothShape::Pixel{color});
140 }
141 x0 += ro;
142 y0 += ro;
143 x1 -= ro;
144 y1 -= ro;
145 float d = sqrtf(0.5f) * ri;
146 Box inner_mid((int16_t)ceilf(x0 - d + 0.5f), (int16_t)ceilf(y0 - d + 0.5f),
147 (int16_t)floorf(x1 + d - 0.5f), (int16_t)floorf(y1 + d - 0.5f));
148 Box inner_wide((int16_t)ceilf(x0 - ri + 0.5f), (int16_t)ceilf(y0 + 0.5f),
149 (int16_t)floorf(x1 + ri - 0.5f), (int16_t)floorf(y1 - 0.5f));
150 Box inner_tall((int16_t)ceilf(x0 + 0.5f), (int16_t)ceilf(y0 - ri + 0.5f),
151 (int16_t)floorf(x1 - 0.5f), (int16_t)floorf(y1 + ri - 0.5f));
153 y0,
154 x1,
155 y1,
156 ro,
157 ri,
158 ro * ro + 0.25f,
159 ri * ri + 0.25f,
160 color,
161 interior_color,
162 std::move(inner_mid),
163 std::move(inner_wide),
164 std::move(inner_tall)};
165
166 return SmoothShape(std::move(extents), std::move(spec));
167}
168
169SmoothShape SmoothRoundRect(float x0, float y0, float x1, float y1,
170 float radius, Color color, Color interior_color) {
171 return SmoothThickRoundRect(x0, y0, x1, y1, radius, 1, color, interior_color);
172}
173
174SmoothShape SmoothFilledRoundRect(float x0, float y0, float x1, float y1,
175 float radius, Color color) {
176 return SmoothThickRoundRect(x0, y0, x1, y1, radius, 0, color, color);
177}
178
179// SmoothShape SmoothFilledRect(float x0, float y0, float x1, float y1,
180// Color color) {
181// return SmoothFilledRoundRect(x0, y0, x1, y1, 0.5, color);
182// }
183
185 Color color, Color interior_color) {
186 return SmoothThickRoundRect(center.x - radius, center.y - radius,
187 center.x + radius, center.y + radius, radius,
188 thickness, color, interior_color);
189}
190
192 Color interior_color) {
193 return SmoothThickCircle(center, radius, 1, color, interior_color);
194}
195
197 return SmoothThickCircle(center, radius, 0, color, color);
198}
199
200namespace {
201
202inline constexpr int Quadrant(float x, float y) {
203 // 1 | 0
204 // --+--
205 // 2 | 3
206 return (x <= 0 && y > 0) ? 0
207 : (y <= 0 && x < 0) ? 1
208 : (x >= 0 && y < 0) ? 2
209 : 3;
210}
211
212} // namespace
213
215 float angle_start, float angle_end,
217 Color interior_color, EndingStyle ending_style,
218 bool trim_to_active) {
219 if (radius <= 0 || thickness <= 0 || angle_end == angle_start) {
220 return SmoothShape();
221 }
222 if (angle_end < angle_start) {
223 std::swap(angle_end, angle_start);
224 }
225 if (angle_end - angle_start >= 2 * M_PI) {
227 interior_color);
228 }
229 radius += thickness * 0.5f;
230 while (angle_start >= M_PI) {
231 angle_start -= 2 * M_PI;
232 angle_end -= 2 * M_PI;
233 }
234
235 float start_sin = sinf(angle_start);
236 float start_cos = cosf(angle_start);
237 float end_sin = sinf(angle_end);
238 float end_cos = cosf(angle_end);
239
240 float ro = radius;
241 float ri = std::max(0.0f, ro - thickness);
242 thickness = ro - ri;
243 float rc = (ro + ri) * 0.5f;
244 float rm = (ro - ri) * 0.5f;
245
246 float start_x_ro = ro * start_sin;
247 float start_y_ro = -ro * start_cos;
248 float start_x_rc = rc * start_sin;
249 float start_y_rc = -rc * start_cos;
250 float start_x_ri = ri * start_sin;
251 float start_y_ri = -ri * start_cos;
252
253 float end_x_ro = ro * end_sin;
254 float end_y_ro = -ro * end_cos;
255 float end_x_rc = rc * end_sin;
256 float end_y_rc = -rc * end_cos;
257 float end_x_ri = ri * end_sin;
258 float end_y_ri = -ri * end_cos;
259
260 // int start_quadrant = Quadrant(start_x_ro, start_y_ro);
261 // int end_quadrant = Quadrant(end_x_ro, end_y_ro);
262
263 float d = sqrtf(0.5f) * (radius - thickness);
264 Box inner_mid((int16_t)ceilf(center.x - d + 0.5f),
265 (int16_t)ceilf(center.y - d + 0.5f),
266 (int16_t)floorf(center.x + d - 0.5f),
267 (int16_t)floorf(center.y + d - 0.5f));
268
270 float cutoff_angle;
271 float cutoff_start_sin;
272 float cutoff_start_cos;
273 float cutoff_end_sin;
274 float cutoff_end_cos;
276 cutoff_angle = asinf(rm / rc);
278 (angle_end - angle_start + 2.0f * cutoff_angle) < 2.0f * M_PI;
279 cutoff_start_sin = sinf(angle_start - cutoff_angle);
280 cutoff_start_cos = cosf(angle_start - cutoff_angle);
281 cutoff_end_sin = sinf(angle_end + cutoff_angle);
282 cutoff_end_cos = cosf(angle_end + cutoff_angle);
283 } else {
284 has_nonempty_cutoff = true;
285 cutoff_angle = 0.0f;
290 }
291
292 // qt0-qt3, when true, mean that the arc goes through the entire given
293 // quadrant, in which case the bounds are determined simply by the external
294 // radius. qf0-qf3 are defined similarly, except they indicate that the arc
295 // goes through none of the quadrant.
296 //
297 // Quadrants are defined as follows:
298 //
299 // 0 | 1
300 // --+--
301 // 2 | 3
302
303 bool qt0 = (angle_start <= -0.5f * M_PI && angle_end >= 0) ||
304 (angle_end >= 2.0f * M_PI);
305 bool qt1 = (angle_start <= 0 && angle_end >= 0.5f * 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);
311
312 bool qf0, qf1, qf2, qf3;
314 qf0 = qf1 = qf2 = qf3 = false;
315 } else {
316 float cutoff_angle_start = angle_start - cutoff_angle;
317 float cutoff_angle_end = angle_end + cutoff_angle;
318 if (cutoff_angle_start < -M_PI) {
320 cutoff_angle_end += 2 * M_PI;
321 }
322 qf0 = !qt0 && ((cutoff_angle_end < -0.5f * M_PI) ||
323 (cutoff_angle_start > 0 && cutoff_angle_end < 1.5f * M_PI));
324 qf1 =
325 !qt1 &&
326 ((cutoff_angle_end < 0.0f * M_PI) ||
327 (cutoff_angle_start > 0.5f * M_PI && cutoff_angle_end < 2.0f * M_PI));
328 qf2 = !qt2 &&
329 (cutoff_angle_start > -0.5f * M_PI && cutoff_angle_end < 1.0f * M_PI);
330 qf3 =
331 !qt3 &&
332 (cutoff_angle_end < 0.5f * M_PI ||
333 (cutoff_angle_start > 1.0f * M_PI && cutoff_angle_end < 2.5f * M_PI));
334 }
335
336 // Figure out the extents.
337 int16_t xMin, yMin, xMax, yMax;
338 if (trim_to_active) {
339 if (qt0 || qt1 || (angle_start <= 0 && angle_end >= 0)) {
340 // Arc passes through zero (touches the top).
341 yMin = (int16_t)floorf(center.y - radius);
342 } else if (ending_style == ENDING_ROUNDED) {
343 yMin = floorf(center.y + std::min(start_y_rc, end_y_rc) - rm);
344 } else {
345 yMin = floorf(center.y + std::min(std::min(start_y_ro, start_y_ri),
346 std::min(end_y_ro, end_y_ri)));
347 }
348 if (qt0 || qt2 ||
350 // Arc passes through -M_PI/2 (touches the left).
351 xMin = (int16_t)floorf(center.x - radius);
352 } else if (ending_style == ENDING_ROUNDED) {
353 xMin = floorf(center.x + std::min(start_x_rc, end_x_rc) - rm);
354 } else {
355 xMin = floorf(center.x + std::min(std::min(start_x_ro, start_x_ri),
356 std::min(end_x_ro, end_x_ri)));
357 }
359 // Arc passes through M_PI (touches the bottom).
360 yMax = (int16_t)ceilf(center.y + radius);
361 } else if (ending_style == ENDING_ROUNDED) {
362 yMax = ceilf(center.y + std::max(start_y_rc, end_y_rc) + rm);
363 } else {
364 yMax = ceilf(center.y + std::max(std::max(start_y_ro, start_y_ri),
365 std::max(end_y_ro, end_y_ri)));
366 }
367 if (qt3 || qt1 ||
369 // Arc passes through M_PI/2 (touches the right).
370 xMax = (int16_t)ceilf(center.x + radius);
371 } else if (ending_style == ENDING_ROUNDED) {
372 xMax = ceilf(center.x + std::max(start_x_rc, end_x_rc) + rm);
373 } else {
374 xMax = ceilf(center.x + std::max(std::max(start_x_ro, start_x_ri),
375 std::max(end_x_ro, end_x_ri)));
376 }
377 } else {
378 xMin = (int16_t)floorf(center.x - radius);
379 yMin = (int16_t)floorf(center.y - radius);
380 xMax = (int16_t)ceilf(center.x + radius);
381 yMax = (int16_t)ceilf(center.y + radius);
382 }
383
384 return SmoothShape(
385 Box(xMin, yMin, xMax, yMax),
387 center.y,
388 ro,
389 ri,
390 rm,
391 ro * ro + 0.25f,
392 ri * ri + 0.25f,
393 rm * rm + 0.25f,
394 angle_start,
395 angle_end,
398 interior_color,
399 inner_mid,
400 start_sin,
401 -start_cos,
402 start_x_rc,
403 start_y_rc,
404 end_sin,
405 -end_cos,
406 end_x_rc,
407 end_y_rc,
413 angle_end - angle_start <= M_PI,
415 angle_end - angle_start + 2.0f * cutoff_angle < M_PI,
416 (uint8_t)(((uint8_t)qt0 << 0) | ((uint8_t)qt1 << 1) |
417 ((uint8_t)qt2 << 2) | ((uint8_t)qt3 << 3) |
418 ((uint8_t)qf0 << 4) | ((uint8_t)qf1 << 5) |
419 ((uint8_t)qf2 << 6) | ((uint8_t)qf3 << 7))});
420}
421
422SmoothShape SmoothArc(FpPoint center, float radius, float angle_start,
423 float angle_end, Color color) {
424 return SmoothThickArc(center, radius, 1.0f, angle_start, angle_end, color,
426}
427
429 float angle_start, float angle_end, Color color,
431 return SmoothThickArcImpl(center, radius, thickness, angle_start, angle_end,
432 color, color::Transparent, color::Transparent,
433 ending_style, true);
434}
435
436SmoothShape SmoothPie(FpPoint center, float radius, float angle_start,
437 float angle_end, Color color) {
438 return SmoothThickArcImpl(center, radius * 0.5f, radius, angle_start,
439 angle_end, color, color::Transparent,
440 color::Transparent, ENDING_FLAT, true);
441}
442
444 float thickness, float angle_start,
445 float angle_end, Color active_color,
447 Color interior_color,
449 return SmoothThickArcImpl(center, radius, thickness, angle_start, angle_end,
450 active_color, inactive_color, interior_color,
451 ending_style, false);
452}
453
455 int16_t xMin = floorf(std::min(std::min(a.x, b.x), c.x));
456 int16_t yMin = floorf(std::min(std::min(a.y, b.y), c.y));
457 int16_t xMax = ceilf(std::max(std::max(a.x, b.x), c.x));
458 int16_t yMax = ceilf(std::max(std::max(a.y, b.y), c.y));
459 float d12 = sqrtf((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
460 float d23 = sqrtf((c.x - b.x) * (c.x - b.x) + (c.y - b.y) * (c.y - b.y));
461 float d31 = sqrtf((a.x - c.x) * (a.x - c.x) + (a.y - c.y) * (a.y - c.y));
462 return SmoothShape(
463 Box(xMin, yMin, xMax, yMax),
464 SmoothShape::Triangle{a.x, a.y, (b.x - a.x) / d12, (b.y - a.y) / d12, b.x,
465 b.y, (c.x - b.x) / d23, (c.y - b.y) / d23, c.x, c.y,
466 (a.x - c.x) / d31, (a.y - c.y) / d31, color});
467};
468
469// Helper functions for wedge.
470
471namespace {
472
473struct WedgeDrawSpec {
474 float r;
475 float dr;
476 float bax;
477 float bay;
478 float hd;
479 float sqrt_hd;
480 uint8_t max_alpha;
482};
483
484inline uint8_t GetWedgeShapeAlpha(const WedgeDrawSpec& spec, float xpax,
485 float ypay) {
486 // hn (h-numerator) is zero at 'a' and equal to spec.hd at 'b'.
487 float hn = (xpax * spec.bax + ypay * spec.bay);
488 // h is hn/hd trimmed to [0, 1]. It represents the position of the point along
489 // the line, with 0 at 'a' and 1 at 'b', that is closest to the point under
490 // test. We will call that point 'P'.
491 float h = hn < 0.0f ? 0.0f : hn > spec.hd ? 1.0f : hn / spec.hd;
492 // (dx, dy) is a distance vector (from point under test to 'P').
493 float dx = xpax - spec.bax * h;
494 float dy = ypay - spec.bay * h;
495 float l_sq = dx * dx + dy * dy;
496 // adj_dist says how far the (widened) boundary of our shape is from 'P'.
497 float adj_dist = spec.r - h * spec.dr;
498 float adj_dist_sq = adj_dist * adj_dist;
499 // Check if point under test is outside the (widened) boundary.
500 if (adj_dist_sq < l_sq) return 0;
501 if (!spec.round_endings) {
502 // We now additionally require that the non-trimmed 'h' is not outside [0,
503 // 1] by more than a pixel distance (and if it is within a pixel distance,
504 // we anti-alias it).
505 float d1 = (hn / spec.hd) * spec.sqrt_hd;
506 float d2 = (1.0f - hn / spec.hd) * spec.sqrt_hd;
507
508 if (d1 < .5f) {
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);
512 }
513 if (d2 < .5f) {
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);
517 }
518 }
519 // Handle sub-pixel widths.
520 if (adj_dist < 1.0f) {
521 float l = sqrtf(l_sq);
522 float d;
523 if (l + adj_dist < 1.0f) {
524 // Line fully within the pixel. Use line thickness as alpha.
525 d = 2 * adj_dist - 1.0f;
526 } else {
527 // Line on the boundary of the pixel. Use the regular formula.
528 d = adj_dist - l;
529 }
530 return (uint8_t)(d * spec.max_alpha);
531 }
532 // Equivalent to sqrt(l_sq) < adj_dist - 1 (the point is deep inside the
533 // (widened) boundary), but without sqrt.
534 if (adj_dist_sq - 2 * adj_dist + 1 > l_sq) return spec.max_alpha;
535 // d is the distance of tested point, inside the (widened) boundary, from the
536 // (widened) boundary. We now know it is in the [0-1] range.
537 float l = sqrtf(l_sq);
538 float d = adj_dist - l;
539 return (uint8_t)(d * spec.max_alpha);
540}
541
542void DrawWedge(const SmoothShape::Wedge& wedge, const Surface& s,
543 const Box& box) {
544 int16_t dx = s.dx();
545 int16_t dy = s.dy();
546 float ax = wedge.ax + dx;
547 float ay = wedge.ay + dy;
548 float bx = wedge.bx + dx;
549 float by = wedge.by + dy;
550 uint8_t max_alpha = wedge.color.a();
551 float bax = bx - ax;
552 float bay = by - ay;
553 float bay_dsq = bax * bax + bay * bay;
554 WedgeDrawSpec spec{
555 .r = wedge.ar + 0.5f,
556 .dr = wedge.ar - wedge.br,
557 .bax = wedge.bx - wedge.ax,
558 .bay = wedge.by - wedge.ay,
559 .hd = bay_dsq,
560 .sqrt_hd = sqrtf(bay_dsq),
561 .max_alpha = wedge.color.a(),
562 .round_endings = wedge.round_endings,
563 };
564
565 // Establish x start and y start.
566 int32_t ys = wedge.ay;
567 if ((ax - wedge.ar) > (bx - wedge.br)) ys = wedge.by;
568
569 uint8_t alpha = max_alpha;
570 float xpax, ypay;
571 bool can_minimize_scan =
572 spec.round_endings && s.fill_mode() != FillMode::kExtents;
573
574 ys += dy;
575 if (ys < box.yMin()) ys = box.yMin();
576 int32_t xs = box.xMin();
577 Color preblended = AlphaBlend(s.bgcolor(), wedge.color);
578
579 BufferedPixelWriter writer(s.out(), s.blending_mode());
580
581 // Scan bounding box from ys down, calculate pixel intensity from distance
582 // to line.
583 for (int32_t yp = ys; yp <= box.yMax(); yp++) {
584 bool endX = false; // Flag to skip pixels
585 ypay = yp - ay;
586 for (int32_t xp = xs; xp <= box.xMax(); xp++) {
587 if (endX && alpha == 0 && can_minimize_scan) break; // Skip right side.
588 xpax = xp - ax;
589 // alpha = getAlpha(ar, xpax, ypay, bax, bay, rdt, max_alpha);
590 alpha = GetWedgeShapeAlpha(spec, xpax, ypay);
591 if (alpha == 0 && can_minimize_scan) continue;
592 // Track the edge to minimize calculations.
593 if (!endX) {
594 endX = true;
595 if (can_minimize_scan) {
596 xs = xp;
597 }
598 }
599 if (alpha != 0 || s.fill_mode() == FillMode::kExtents) {
601 xp, yp,
602 alpha == max_alpha
603 ? preblended
604 : AlphaBlend(s.bgcolor(), wedge.color.withA(alpha)));
605 }
606 }
607 }
608
609 if (ys > box.yMax()) ys = box.yMax();
610 // Reset x start to left side of box.
611 xs = box.xMin();
612 // Scan bounding box from ys-1 up, calculate pixel intensity from distance
613 // to line.
614 for (int32_t yp = ys - 1; yp >= box.yMin(); yp--) {
615 bool endX = false; // Flag to skip pixels
616 ypay = yp - ay;
617 for (int32_t xp = xs; xp <= box.xMax(); xp++) {
618 if (endX && alpha == 0 && can_minimize_scan)
619 break; // Skip right side of drawn line.
620 xpax = xp - ax;
621 // alpha = getAlpha(ar, xpax, ypay, bax, bay, rdt, max_alpha);
622 alpha = GetWedgeShapeAlpha(spec, xpax, ypay);
623 if (alpha == 0 && can_minimize_scan) continue;
624 // Track line boundary.
625 if (!endX) {
626 endX = true;
627 if (can_minimize_scan) {
628 xs = xp;
629 }
630 }
631 if (alpha != 0 || s.fill_mode() == FillMode::kExtents) {
633 xp, yp,
634 alpha == max_alpha
635 ? preblended
636 : AlphaBlend(s.bgcolor(), wedge.color.withA(alpha)));
637 }
638 }
639 }
640}
641
642void ReadWedgeColors(const SmoothShape::Wedge& wedge, const int16_t* x,
643 const int16_t* y, uint32_t count, Color* result) {
644 // This default rasterizable implementation seems to be ~50% slower than
645 // drawTo (but it allows to use wedges as backgrounds or overlays, e.g.
646 // indicator needles).
647 float bax = wedge.bx - wedge.ax;
648 float bay = wedge.by - wedge.ay;
649 float bay_dsq = bax * bax + bay * bay;
650 WedgeDrawSpec spec{
651 // Widen the boundary to simplify calculations.
652 .r = wedge.ar + 0.5f,
653 .dr = wedge.ar - wedge.br,
654 .bax = wedge.bx - wedge.ax,
655 .bay = wedge.by - wedge.ay,
656 .hd = bay_dsq,
657 .sqrt_hd = sqrtf(bay_dsq),
658 .max_alpha = wedge.color.a(),
659 .round_endings = wedge.round_endings,
660 };
661 while (count-- > 0) {
662 *result++ = wedge.color.withA(
663 GetWedgeShapeAlpha(spec, *x++ - wedge.ax, *y++ - wedge.ay));
664 }
665}
666
667// Helper functions for round rect.
668
669inline Color GetSmoothRoundRectPixelColor(const SmoothShape::RoundRect& rect,
670 int16_t x, int16_t y) {
671 // // This only applies to a handful of pixels and seems to slow things down.
672 // if (rect.inner_mid.contains(x, y) || rect.inner_wide.contains(x, y) ||
673 // rect.inner_tall.contains(x, y)) {
674 // return rect.interior_color;
675 // }
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;
680
681 Color interior = rect.interior_color;
682 // // This only applies to a handful of pixels and seems to slow things down.
683 // if (abs(dx) + abs(dy) < spec.ri - 0.5) {
684 // return spec.interior;
685 // }
686 float d_squared = dx * dx + dy * dy;
687 float ro = rect.ro;
688 float ri = rect.ri;
689 Color outline = rect.outline_color;
690
691 if (d_squared <= rect.ri_sq_adj - ri && ri >= 0.5f) {
692 // Point fully within the interior.
693 return interior;
694 }
695 if (d_squared >= rect.ro_sq_adj + ro) {
696 // Point fully outside the round rectangle.
697 return color::Transparent;
698 }
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) {
702 // Point fully within the outline band.
703 return outline;
704 }
705 // if (dx == 0 && dy == 0 && rect.inner_mid.contains(x, y)) {
706 // return interior;
707 // }
708 // Point is on either of the boundaries; need anti-aliasing.
709 // Note: replacing with integer sqrt (iterative, loop-unrolled, 24-bit) slows
710 // things down. At 64-bit not loop-unrolled, does so dramatically.
711 float d = sqrtf(d_squared);
712 if (fully_outside_inner) {
713 // On the outer boundary.
714 return outline.withA((uint8_t)(outline.a() * (ro - d + 0.5f)));
715 }
716 if (fully_within_outer) {
717 // On the inner boundary.
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))) {
721 // Pesky corner case: the interior is hair-thin. Approximate.
722 opacity = std::min(1.0f, opacity * 2.0f);
723 }
724 return AlphaBlend(interior,
725 outline.withA((uint8_t)(outline.a() * opacity)));
726 }
727 // On both bounderies (e.g. the band is very thin).
728 return AlphaBlend(
729 interior, outline.withA((uint8_t)(outline.a() *
730 std::max(0.0f, (ro - d + 0.5f) -
731 (ri - d + 0.5f)))));
732}
733
734enum RectColor {
735 NON_UNIFORM = 0,
736 TRANSPARENT = 1,
737 INTERIOR = 2,
738 OUTLINE_ACTIVE = 3,
739 OUTLINE_INACTIVE = 4, // Used by arcs.
740};
741
742inline float CalcDistSqRect(float x0, float y0, float x1, float y1, float xt,
743 float yt) {
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;
747}
748
749// Called for rectangles with area <= 64 pixels.
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)) {
754 return INTERIOR;
755 }
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);
764
765 float r_min_sq = rect.ri_sq_adj - rect.ri;
766 // Check if the rect falls entirely inside the interior boundary.
767 if (dtl < r_min_sq && dtr < r_min_sq && dbl < r_min_sq && dbr < r_min_sq) {
768 return INTERIOR;
769 }
770
771 float r_max_sq = rect.ro_sq_adj + rect.ro;
772 // Check if the rect falls entirely outside the boundary (in one of the 4
773 // corners).
774 if (xMax < rect.x0) {
775 if (yMax < rect.y0) {
776 if (dbr >= r_max_sq) {
777 return TRANSPARENT;
778 }
779 } else if (yMin > rect.y1) {
780 if (dtr >= r_max_sq) {
781 return TRANSPARENT;
782 }
783 }
784 } else if (xMin > rect.x1) {
785 if (yMax < rect.y0) {
786 if (dbl >= r_max_sq) {
787 return TRANSPARENT;
788 }
789 } else if (yMin > rect.y1) {
790 if (dtl >= r_max_sq) {
791 return TRANSPARENT;
792 }
793 }
794 }
795 // If all corners are in the same quadrant, and all corners are within the
796 // ring, then the rect is also within the ring.
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;
804 }
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;
811 }
812 }
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;
820 }
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;
827 }
828 }
829 }
830 // Slow case; evaluate every pixel from the rectangle.
831 return NON_UNIFORM;
832}
833
834bool ReadColorRectOfRoundRect(const SmoothShape::RoundRect& rect, int16_t xMin,
835 int16_t yMin, int16_t xMax, int16_t yMax,
836 Color* result) {
837 Box box(xMin, yMin, xMax, yMax);
838 switch (DetermineRectColorForRoundRect(rect, box)) {
839 case TRANSPARENT: {
840 *result = color::Transparent;
841 return true;
842 }
843 case INTERIOR: {
844 *result = rect.interior_color;
845 return true;
846 }
847 case OUTLINE_ACTIVE: {
848 *result = rect.outline_color;
849 return true;
850 }
851 default:
852 break;
853 }
854 Color* out = result;
855 for (int16_t y = yMin; y <= yMax; ++y) {
856 for (int16_t x = xMin; x <= xMax; ++x) {
857 *out++ = GetSmoothRoundRectPixelColor(rect, x, y);
858 }
859 }
860 // This is now very unlikely to be true, or we would have caught it above.
861 // uint32_t pixel_count = (xMax - xMin + 1) * (yMax - yMin + 1);
862 // Color c = result[0];
863 // for (uint32_t i = 1; i < pixel_count; i++) {
864 // if (result[i] != c) return false;
865 // }
866 // return true;
867 return false;
868}
869
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;
876 } else {
877 *result = GetSmoothRoundRectPixelColor(rect, *x, *y);
878 }
879 ++x;
880 ++y;
881 ++result;
882 }
883}
884
885struct RoundRectDrawSpec {
886 DisplayOutput* out;
889 Color bgcolor;
892};
893
894// Called for rectangles with area <= 64 pixels.
895inline void FillSubrectOfRoundRect(const SmoothShape::RoundRect& rect,
896 const RoundRectDrawSpec& spec,
897 const Box& box) {
898 Color interior = rect.interior_color;
899 Color outline = rect.outline_color;
900 switch (DetermineRectColorForRoundRect(rect, box)) {
901 case TRANSPARENT: {
902 if (spec.fill_mode == FillMode::kExtents) {
903 spec.out->fillRect(spec.blending_mode, box, spec.bgcolor);
904 }
905 return;
906 }
907 case INTERIOR: {
908 if (spec.fill_mode == FillMode::kExtents ||
909 interior != color::Transparent) {
910 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_interior);
911 }
912 return;
913 }
914 case OUTLINE_ACTIVE: {
915 if (spec.fill_mode == FillMode::kExtents ||
916 outline != color::Transparent) {
917 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_outline);
918 return;
919 }
920 }
921 default:
922 break;
923 }
924
925 // Slow case; evaluate every pixel from the rectangle.
926 if (spec.fill_mode == FillMode::kVisible) {
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;
932 writer.writePixel(x, y,
933 c == interior ? spec.pre_blended_interior
934 : c == outline ? spec.pre_blended_outline
935 : AlphaBlend(spec.bgcolor, c));
936 }
937 }
938 } else {
939 Color color[64];
940 int cnt = 0;
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
947 : AlphaBlend(spec.bgcolor, c);
948 }
949 }
950 spec.out->setAddress(box, spec.blending_mode);
951 spec.out->write(color, cnt);
952 }
953}
954
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(
964 rect, spec,
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())));
968 }
969 }
970}
971
972void DrawRoundRect(SmoothShape::RoundRect rect, const Surface& s,
973 const Box& box) {
974 RoundRectDrawSpec spec{
975 .out = &s.out(),
976 .fill_mode = s.fill_mode(),
977 .blending_mode = s.blending_mode(),
978 .bgcolor = s.bgcolor(),
979 .pre_blended_outline = AlphaBlend(
980 AlphaBlend(s.bgcolor(), rect.interior_color), rect.outline_color),
981 .pre_blended_interior = AlphaBlend(s.bgcolor(), rect.interior_color),
982 };
983 if (s.dx() != 0 || s.dy() != 0) {
984 rect.x0 += s.dx();
985 rect.y0 += s.dy();
986 rect.x1 += s.dx();
987 rect.y1 += s.dy();
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());
991 }
992 {
993 uint32_t pixel_count = box.area();
994 if (pixel_count <= 64) {
995 FillSubrectOfRoundRect(rect, spec, box);
996 return;
997 }
998 }
999 if (rect.inner_mid.width() <= 16) {
1000 FillSubrectangle(rect, spec, box);
1001 } else {
1002 const Box& inner = rect.inner_mid;
1003 FillSubrectangle(rect, spec,
1004 Box(box.xMin(), box.yMin(), box.xMax(), inner.yMin() - 1));
1005 FillSubrectangle(
1006 rect, spec,
1007 Box(box.xMin(), inner.yMin(), inner.xMin() - 1, inner.yMax()));
1008 if (s.fill_mode() == FillMode::kExtents ||
1009 rect.interior_color != color::Transparent) {
1010 s.out().fillRect(s.blending_mode(), inner, spec.pre_blended_interior);
1011 }
1012 FillSubrectangle(
1013 rect, spec,
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()));
1017 }
1018}
1019
1020// Arc.
1021
1022struct ArcDrawSpec {
1023 DisplayOutput* out;
1026 Color bgcolor;
1030};
1031
1032Color GetSmoothArcPixelColor(const SmoothShape::Arc& spec, int16_t x,
1033 int16_t y) {
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;
1038 }
1039 float d_squared = dx * dx + dy * dy;
1040 if (spec.ri >= 0.5f && d_squared <= spec.ri_sq_adj - spec.ri) {
1041 // Pixel fully within the 'inner' ring.
1042 return spec.interior_color;
1043 }
1044 if (d_squared >= spec.ro_sq_adj + spec.ro) {
1045 // Pixel fully outside the 'outer' ring.
1046 return color::Transparent;
1047 }
1048 // We now know that the pixel is somewhere inside the ring.
1049 Color color;
1050
1051 // 0 | 1
1052 // --+--
1053 // 2 | 3
1054
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;
1057
1058 if ((quadrant & spec.quadrants_) == quadrant) {
1059 // The entire quadrant is within range.
1060 color = spec.outline_active_color;
1061 } else if ((quadrant & (spec.quadrants_ >> 4)) == quadrant) {
1062 // The entire quadrant is outside range.
1063 color = spec.outline_inactive_color;
1064 } else {
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);
1070 } else {
1071 within_range = (n1 <= -0.5 || n2 <= -0.5);
1072 }
1073 if (within_range) {
1074 color = spec.outline_active_color;
1075 } else {
1076 // Not entirely within the angle range. May be close to boundary; may be
1077 // far from it. The result depends on ending style.
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;
1083 // The endings may overlap, so we need to check the min distance from
1084 // both endpoints.
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;
1091 } else {
1092 // Round endings boundary - we need to calculate the alpha-blended
1093 // color.
1094 float d = sqrt(smaller_dist_sq);
1095 color = AlphaBlend(spec.outline_inactive_color,
1096 spec.outline_active_color.withA((
1097 uint8_t)(0.5f + spec.outline_active_color.a() *
1098 (spec.rm - d + 0.5f))));
1099 }
1100 } else {
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;
1105 } else {
1106 float alpha = 1.0f;
1107 if (n1 > -0.5f && n1 < 0.5f &&
1108 spec.start_x_slope * dx + spec.start_y_slope * dy > 0) {
1109 // To the inner side of the start angle sub-plane (shifted by
1110 // 0.5), and pointing towards the start angle (cos < 90).
1111 alpha *= (1 - (n1 + 0.5f));
1112 }
1113 if (n2 < 0.5f && n2 > -0.5f &&
1114 spec.end_x_slope * dx + spec.end_y_slope * dy >= 0) {
1115 // To the inner side of the end angle sub-plane (shifted by 0.5),
1116 // and pointing towards the end angle (cos < 90).
1117 alpha *= (1 - (n2 + 0.5f));
1118 }
1119 color = AlphaBlend(
1120 spec.outline_inactive_color,
1121 spec.outline_active_color.withA(
1122 (uint8_t)(0.5f + spec.outline_active_color.a() * alpha)));
1123 }
1124 } else {
1125 // Equivalent to the 'sharp' case with colors (active vs active)
1126 // flipped.
1127 bool inside_range = (n1 <= -0.5f || n2 <= -0.5f);
1128 if (inside_range) {
1129 color = spec.outline_active_color;
1130 } else {
1131 float alpha = 1.0f;
1132 if (n1 > -0.5f && n1 < 0.5f &&
1133 spec.start_y_slope * dy + spec.start_x_slope * dx >= 0) {
1134 // To the inner side of the start angle sub-plane (shifted by
1135 // 0.5), and pointing towards the start angle (cos < 90).
1136 alpha *= (n1 + 0.5f);
1137 }
1138 if (n2 < 0.5f && n2 > -0.5f &&
1139 spec.end_x_slope * dx + spec.end_y_slope * dy > 0) {
1140 // To the inner side of the end angle sub-plane (shifted by 0.5),
1141 // and pointing towards the end angle (cos < 90).
1142 alpha *= (n2 + 0.5f);
1143 }
1144 alpha = 1.0f - alpha;
1145 color = AlphaBlend(
1146 spec.outline_inactive_color,
1147 spec.outline_active_color.withA(
1148 (uint8_t)(0.5f + spec.outline_active_color.a() * alpha)));
1149 }
1150 }
1151 }
1152 }
1153 }
1154
1155 // Now we need to apply blending at the edges of the outer and inner rings.
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 ||
1159 spec.ri == 0;
1160
1161 if (fully_within_outer && fully_outside_inner) {
1162 // Fully inside the ring, far enough from the boundary.
1163 return color;
1164 }
1165 float d = sqrtf(d_squared);
1166 if (fully_outside_inner) {
1167 return color.withA((uint8_t)(color.a() * (spec.ro - d + 0.5f)));
1168 }
1169 if (fully_within_outer) {
1170 return AlphaBlend(
1171 spec.interior_color,
1172 color.withA((uint8_t)(color.a() * (1.0f - (spec.ri - d + 0.5f)))));
1173 }
1174 return AlphaBlend(
1175 spec.interior_color,
1176 color.withA(
1177 (uint8_t)(color.a() * std::max(0.0f, (spec.ro - d + 0.5f) -
1178 (spec.ri - d + 0.5f)))));
1179}
1180
1181inline float CalcDistSq(float x1, float y1, int16_t x2, int16_t y2) {
1182 float dx = x1 - x2;
1183 float dy = y1 - y2;
1184 return dx * dx + dy * dy;
1185}
1186
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;
1194 if (sharp) {
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);
1203 } else {
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);
1212 }
1213}
1214
1215inline bool IsPointWithinCircle(float cx, float cy, float r_sq, float x,
1216 float y) {
1217 float dx = x - cx;
1218 float dy = y - cy;
1219 return dx * dx + dy * dy <= r_sq;
1220}
1221
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());
1227}
1228
1229// Called for arcs with area <= 64 pixels.
1230inline RectColor DetermineRectColorForArc(const SmoothShape::Arc& arc,
1231 const Box& box) {
1232 if (arc.inner_mid.contains(box)) {
1233 return INTERIOR;
1234 }
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);
1243
1244 float r_min_sq = arc.ri_sq_adj - arc.ri;
1245 // Check if the rect falls entirely inside the interior boundary.
1246 if (dtl < r_min_sq && dtr < r_min_sq && dbl < r_min_sq && dbr < r_min_sq) {
1247 return INTERIOR;
1248 }
1249
1250 float r_max_sq = arc.ro_sq_adj + arc.ro;
1251 // Check if the rect falls entirely outside the boundary (in one of the 4
1252 // corners).
1253 if (xMax < arc.xc) {
1254 if (yMax < arc.yc) {
1255 if (dbr >= r_max_sq) {
1256 return TRANSPARENT;
1257 }
1258 } else if (yMin > arc.yc) {
1259 if (dtr >= r_max_sq) {
1260 return TRANSPARENT;
1261 }
1262 }
1263 } else if (xMin > arc.xc) {
1264 if (yMax < arc.yc) {
1265 if (dbl >= r_max_sq) {
1266 return TRANSPARENT;
1267 }
1268 } else if (yMin > arc.yc) {
1269 if (dtl >= r_max_sq) {
1270 return TRANSPARENT;
1271 }
1272 }
1273 }
1274 if (arc.round_endings) {
1275 if (arc.nonempty_cutoff) {
1276 // Check if all rect corners are in the cut-out area.
1277 }
1278 }
1279 // If all corners are in the same quadrant, and all corners are within the
1280 // ring, then the rect is also within the ring.
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) {
1287 return NON_UNIFORM;
1288 }
1289 // Fast-path for the case when the arc contains the entire quadrant.
1290 if ((arc.quadrants_ & 1) && xMax <= arc.xc - 0.5f &&
1291 yMax <= arc.yc - 0.5f) {
1292 return OUTLINE_ACTIVE;
1293 }
1294 if ((arc.quadrants_ & 0x10) && xMax <= arc.xc - 0.5f &&
1295 yMax <= arc.yc - 0.5f) {
1296 return OUTLINE_INACTIVE;
1297 }
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) {
1303 return NON_UNIFORM;
1304 }
1305 // Fast-path for the case when the arc contains the entire quadrant.
1306 if (arc.quadrants_ & 4 && xMax <= arc.xc - 0.5f &&
1307 yMin >= arc.yc + 0.5f) {
1308 return OUTLINE_ACTIVE;
1309 }
1310 if (arc.quadrants_ & 0x40 && xMax <= arc.xc - 0.5f &&
1311 yMin >= arc.yc + 0.5f) {
1312 return OUTLINE_INACTIVE;
1313 }
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) {
1319 return NON_UNIFORM;
1320 // Fast-path for the case when the arc contains the entire half-circle.
1321 if ((arc.quadrants_ & 0b0101) == 0b0101) {
1322 return OUTLINE_ACTIVE;
1323 }
1324 }
1325 } else {
1326 return NON_UNIFORM;
1327 }
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) {
1334 return NON_UNIFORM;
1335 }
1336 // Fast-path for the case when the arc contains the entire quadrant.
1337 if (arc.quadrants_ & 2 && xMin >= arc.xc + 0.5f &&
1338 yMax <= arc.yc - 0.5f) {
1339 return OUTLINE_ACTIVE;
1340 }
1341 if (arc.quadrants_ & 0x20 && xMin >= arc.xc + 0.5f &&
1342 yMax <= arc.yc - 0.5f) {
1343 return OUTLINE_INACTIVE;
1344 }
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) {
1350 return NON_UNIFORM;
1351 }
1352 // Fast-path for the case when the arc contains the entire quadrant.
1353 if (arc.quadrants_ & 8 && xMin >= arc.xc + 0.5f &&
1354 yMin >= arc.yc + 0.5f) {
1355 return OUTLINE_ACTIVE;
1356 }
1357 if (arc.quadrants_ & 0x80 && xMin >= arc.xc + 0.5f &&
1358 yMin >= arc.yc + 0.5f) {
1359 return OUTLINE_INACTIVE;
1360 }
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) {
1366 return NON_UNIFORM;
1367 }
1368 // Fast-path for the case when the arc contains the entire half-circle.
1369 if ((arc.quadrants_ & 0b1010) == 0b1010) {
1370 return OUTLINE_ACTIVE;
1371 }
1372 } else {
1373 return NON_UNIFORM;
1374 }
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) {
1380 return NON_UNIFORM;
1381 }
1382 // Fast-path for the case when the arc contains the entire half-circle.
1383 if ((arc.quadrants_ & 0b0011) == 0b0011) {
1384 return OUTLINE_ACTIVE;
1385 }
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) {
1391 return NON_UNIFORM;
1392 }
1393 // // Fast-path for the case when the arc contains the entire half-circle.
1394 if ((arc.quadrants_ & 0b1100) == 0b1100) {
1395 return OUTLINE_ACTIVE;
1396 }
1397 } else {
1398 return NON_UNIFORM;
1399 }
1400 // The rectangle is entirely inside the ring. Let's check if it is actually
1401 // single-color.
1402 //
1403 // First, let's see if the rect is entirely within the 'active' angle.
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,
1406 box)) {
1407 return OUTLINE_ACTIVE;
1408 }
1409
1410 // Now, let's see if the rect is perhaps entirely inside the 'inactive' angle.
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;
1416 }
1417
1418 // Finally, check if maybe the rect is entirely within one of the round
1419 // endings.
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;
1426 }
1427 }
1428
1429 // Slow case; evaluate every pixel from the rectangle.
1430 return NON_UNIFORM;
1431}
1432
1433// Called for arcs with area <= 64 pixels.
1434void FillSubrectOfArc(const SmoothShape::Arc& arc, const ArcDrawSpec& spec,
1435 const Box& box) {
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)) {
1440 case TRANSPARENT: {
1441 if (spec.fill_mode == FillMode::kExtents) {
1442 spec.out->fillRect(spec.blending_mode, box, spec.bgcolor);
1443 }
1444 return;
1445 }
1446 case INTERIOR: {
1447 if (spec.fill_mode == FillMode::kExtents ||
1448 interior != color::Transparent) {
1449 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_interior);
1450 }
1451 return;
1452 }
1453 case OUTLINE_ACTIVE: {
1454 if (spec.fill_mode == FillMode::kExtents ||
1455 outline_active != color::Transparent) {
1456 spec.out->fillRect(spec.blending_mode, box,
1457 spec.pre_blended_outline_active);
1458 }
1459 return;
1460 }
1461 case OUTLINE_INACTIVE: {
1462 if (spec.fill_mode == FillMode::kExtents ||
1463 outline_inactive != color::Transparent) {
1464 spec.out->fillRect(spec.blending_mode, box,
1465 spec.pre_blended_outline_inactive);
1466 }
1467 return;
1468 }
1469 default:
1470 break;
1471 }
1472
1473 // Slow case; evaluate every pixel from the rectangle.
1474 if (spec.fill_mode == FillMode::kVisible) {
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;
1481 x, y,
1482 c == interior ? spec.pre_blended_interior
1483 : c == outline_active ? spec.pre_blended_outline_active
1484 : c == outline_inactive ? spec.pre_blended_outline_inactive
1485 : AlphaBlend(spec.bgcolor, c));
1486 }
1487 }
1488 } else {
1489 Color color[64];
1490 int cnt = 0;
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
1499 : AlphaBlend(spec.bgcolor, c);
1500 }
1501 }
1502 spec.out->setAddress(box, spec.blending_mode);
1503 spec.out->write(color, cnt);
1504 }
1505}
1506
1507void DrawArc(SmoothShape::Arc arc, const Surface& s, const Box& box) {
1508 ArcDrawSpec spec{
1509 .out = &s.out(),
1510 .fill_mode = s.fill_mode(),
1511 .blending_mode = s.blending_mode(),
1512 .bgcolor = s.bgcolor(),
1513 .pre_blended_outline_active =
1514 AlphaBlend(AlphaBlend(s.bgcolor(), arc.interior_color),
1515 arc.outline_active_color),
1516 .pre_blended_outline_inactive =
1517 AlphaBlend(AlphaBlend(s.bgcolor(), arc.interior_color),
1518 arc.outline_inactive_color),
1519 .pre_blended_interior = AlphaBlend(s.bgcolor(), arc.interior_color),
1520 };
1521 if (s.dx() != 0 || s.dy() != 0) {
1522 arc.xc += s.dx();
1523 arc.yc += s.dy();
1524 // Not adjusting {start,end}_{x,y}_rc, because those are relative to (xc,
1525 // yc).
1526 arc.inner_mid = arc.inner_mid.translate(s.dx(), s.dy());
1527 }
1528 int16_t xMin = box.xMin();
1529 int16_t xMax = box.xMax();
1530 int16_t yMin = box.yMin();
1531 int16_t yMax = box.yMax();
1532 {
1533 uint32_t pixel_count = box.area();
1534 if (pixel_count <= 64) {
1535 FillSubrectOfArc(arc, spec, box);
1536 return;
1537 }
1538 }
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())));
1549 }
1550 }
1551}
1552
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)) {
1557 case TRANSPARENT: {
1558 *result = color::Transparent;
1559 return true;
1560 }
1561 case INTERIOR: {
1562 *result = arc.interior_color;
1563 return true;
1564 }
1565 case OUTLINE_ACTIVE: {
1566 *result = arc.outline_active_color;
1567 return true;
1568 }
1569 case OUTLINE_INACTIVE: {
1570 *result = arc.outline_inactive_color;
1571 return true;
1572 }
1573 default:
1574 break;
1575 }
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);
1580 }
1581 }
1582 // // This is now very unlikely to be true, or we would have caught it above.
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;
1587 }
1588 return true;
1589}
1590
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++);
1595 }
1596}
1597
1598// Triangle.
1599
1600struct TriangleDrawSpec {
1601 DisplayOutput* out;
1604 Color bgcolor;
1606};
1607
1608Color GetSmoothTrianglePixelColor(const SmoothShape::Triangle& t, int16_t x,
1609 int16_t y) {
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) {
1614 // Fully inside the triangle.
1615 return t.color;
1616 }
1617 if (n1 >= 0.5f || n2 >= 0.5f || n3 >= 0.5f) {
1618 // Fully outside the triangle.
1619 return color::Transparent;
1620 }
1621 // Somewhere near the boundery of the triangle; perhaps near more than one.
1622 // NOTE: this formula isn't accurate for very thin triangles, producing
1623 // somewhat jittery lines. For now, prefer wedge in such cases.
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)));
1627}
1628
1629inline bool IsPointWithinTriangle(const SmoothShape::Triangle& t, float x,
1630 float y) {
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;
1637 return true;
1638}
1639
1640inline bool IsRectWithinTriangle(const SmoothShape::Triangle& t,
1641 const Box& box) {
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());
1646}
1647
1648inline bool IsRectOutsideLine(float x0, float y0, float dx, float dy,
1649 const Box& box) {
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;
1658 return true;
1659}
1660
1661inline bool IsRectOutsideTriangle(const SmoothShape::Triangle& t,
1662 const Box& box) {
1663 // It is not enough to check that all points are outside triangle; we must
1664 // check that they fall on the same side.
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);
1668}
1669
1670// Called for rects with area <= 64 pixels.
1671inline RectColor DetermineRectColorForTriangle(
1672 const SmoothShape::Triangle& triangle, const Box& box) {
1673 // First, let's see if the rect is entirely within the triangle.
1674 if (IsRectWithinTriangle(triangle, box)) {
1675 return INTERIOR;
1676 }
1677 if (IsRectOutsideTriangle(triangle, box)) {
1678 return TRANSPARENT;
1679 }
1680 return NON_UNIFORM;
1681}
1682
1683// Called for arcs with area <= 64 pixels.
1684void FillSubrectOfTriangle(const SmoothShape::Triangle& triangle,
1685 const TriangleDrawSpec& spec, const Box& box) {
1686 Color interior = triangle.color;
1687 switch (DetermineRectColorForTriangle(triangle, box)) {
1688 case TRANSPARENT: {
1689 if (spec.fill_mode == FillMode::kExtents) {
1690 spec.out->fillRect(spec.blending_mode, box, spec.bgcolor);
1691 }
1692 return;
1693 }
1694 case INTERIOR: {
1695 if (spec.fill_mode == FillMode::kExtents ||
1696 interior != color::Transparent) {
1697 spec.out->fillRect(spec.blending_mode, box, spec.pre_blended_interior);
1698 }
1699 return;
1700 }
1701 default:
1702 break;
1703 }
1704
1705 // Slow case; evaluate every pixel from the rectangle.
1706 if (spec.fill_mode == FillMode::kVisible) {
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;
1712 writer.writePixel(x, y,
1713 c == interior ? spec.pre_blended_interior
1714 : AlphaBlend(spec.bgcolor, c));
1715 }
1716 }
1717 } else {
1718 Color color[64];
1719 int cnt = 0;
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
1725 : AlphaBlend(spec.bgcolor, c);
1726 }
1727 }
1728 spec.out->setAddress(box, spec.blending_mode);
1729 spec.out->write(color, cnt);
1730 }
1731}
1732
1733void DrawTriangle(SmoothShape::Triangle triangle, const Surface& s,
1734 const Box& box) {
1735 TriangleDrawSpec spec{
1736 .out = &s.out(),
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),
1741 };
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();
1749 }
1750 int16_t xMin = box.xMin();
1751 int16_t xMax = box.xMax();
1752 int16_t yMin = box.yMin();
1753 int16_t yMax = box.yMax();
1754 {
1755 uint32_t pixel_count = box.area();
1756 if (pixel_count <= 64) {
1757 FillSubrectOfTriangle(triangle, spec, box);
1758 return;
1759 }
1760 }
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(
1768 triangle, spec,
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())));
1772 }
1773 }
1774}
1775
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)) {
1781 case TRANSPARENT: {
1782 *result = color::Transparent;
1783 return true;
1784 }
1785 case INTERIOR: {
1786 *result = triangle.color;
1787 return true;
1788 }
1789 default:
1790 break;
1791 }
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);
1796 }
1797 }
1798 // // // This is now very unlikely to be true, or we would have caught it
1799 // above. Color c = result[0]; uint32_t pixel_count = box.area(); for
1800 // (uint32_t i = 1; i < pixel_count; i++) {
1801 // if (result[i] != c) return false;
1802 // }
1803 // return true;
1804 return false;
1805}
1806
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++);
1811 }
1812}
1813
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),
1818 &x, &y, 1);
1819}
1820
1821} // namespace
1822
1823void SmoothShape::drawTo(const Surface& s) const {
1824 Box box = Box::Intersect(extents_.translate(s.dx(), s.dy()), s.clip_box());
1825 if (box.empty()) {
1826 return;
1827 }
1828 switch (kind_) {
1829 case WEDGE: {
1830 DrawWedge(wedge_, s, box);
1831 break;
1832 }
1833 case ROUND_RECT: {
1834 DrawRoundRect(round_rect_, s, box);
1835 break;
1836 }
1837 case ARC: {
1838 DrawArc(arc_, s, box);
1839 break;
1840 }
1841 case TRIANGLE: {
1842 DrawTriangle(triangle_, s, box);
1843 break;
1844 }
1845 case PIXEL: {
1846 DrawPixel(pixel_, s, box);
1847 }
1848 case EMPTY: {
1849 return;
1850 }
1851 }
1852}
1853
1854void SmoothShape::readColors(const int16_t* x, const int16_t* y, uint32_t count,
1855 Color* result) const {
1856 switch (kind_) {
1857 case WEDGE: {
1858 ReadWedgeColors(wedge_, x, y, count, result);
1859 break;
1860 }
1861 case ROUND_RECT: {
1862 ReadRoundRectColors(round_rect_, x, y, count, result);
1863 break;
1864 }
1865 case ARC: {
1866 ReadArcColors(arc_, x, y, count, result);
1867 break;
1868 }
1869 case TRIANGLE: {
1870 ReadTriangleColors(triangle_, x, y, count, result);
1871 break;
1872 }
1873 case PIXEL: {
1874 while (count-- > 0) {
1875 *result = pixel_.color;
1876 }
1877 break;
1878 }
1879 case EMPTY: {
1880 while (count-- > 0) {
1881 *result++ = color::Transparent;
1882 }
1883 break;
1884 }
1885 }
1886}
1887
1889 int16_t yMax, Color* result) const {
1890 switch (kind_) {
1891 case WEDGE: {
1892 return Rasterizable::readColorRect(xMin, yMin, xMax, yMax, result);
1893 }
1894 case ROUND_RECT: {
1895 return ReadColorRectOfRoundRect(round_rect_, xMin, yMin, xMax, yMax,
1896 result);
1897 }
1898 case ARC: {
1899 return ReadColorRectOfArc(arc_, xMin, yMin, xMax, yMax, result);
1900 }
1901 case TRIANGLE: {
1902 return ReadColorRectOfTriangle(triangle_, xMin, yMin, xMax, yMax, result);
1903 }
1904 case PIXEL: {
1905 *result = pixel_.color;
1906 return true;
1907 }
1908 case EMPTY: {
1909 *result = color::Transparent;
1910 return true;
1911 }
1912 }
1913 *result = color::Transparent;
1914 return true;
1915}
1916
1917} // namespace roo_display
BufferedRectWriter & writer
Axis-aligned integer rectangle.
Definition box.h:12
int16_t xMin() const
Minimum x (inclusive).
Definition box.h:65
int16_t xMax() const
Maximum x (inclusive).
Definition box.h:71
Box translate(int16_t x_offset, int16_t y_offset) const
Return a translated copy of this box.
Definition box.h:127
int16_t yMax() const
Maximum y (inclusive).
Definition box.h:74
static Box Intersect(const Box &a, const Box &b)
Return the intersection of two boxes (may be empty).
Definition box.h:25
int16_t yMin() const
Minimum y (inclusive).
Definition box.h:68
void writePixel(int16_t x, int16_t y, Color color)
ARGB8888 color stored as a 32-bit unsigned integer.
Definition color.h:16
constexpr uint8_t a() const
Alpha channel.
Definition color.h:36
constexpr Color withA(uint8_t a) const
Return a copy with the specified alpha channel.
Definition color.h:69
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.
Definition smooth.h:91
void readColors(const int16_t *x, const int16_t *y, uint32_t count, Color *result) const override
Read colors for the given points.
Definition smooth.cpp:1854
bool readColorRect(int16_t xMin, int16_t yMin, int16_t xMax, int16_t yMax, Color *result) const override
Read colors for a rectangle.
Definition smooth.cpp:1888
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)
Definition smooth.cpp:214
SmoothShape SmoothFilledCircle(FpPoint center, float radius, Color color)
Create a filled circle.
Definition smooth.cpp:196
BlendingMode
Porter-Duff style blending modes.
Definition blending.h:17
Color AlphaBlend(Color bgc, Color fgc)
Definition blending.h:598
SmoothShape SmoothThickArc(FpPoint center, float radius, float thickness, float angle_start, float angle_end, Color color, EndingStyle ending_style)
Create an arc with thickness.
Definition smooth.cpp:428
SmoothShape SmoothFilledTriangle(FpPoint a, FpPoint b, FpPoint c, Color color)
Create a filled triangle.
Definition smooth.cpp:454
SmoothShape SmoothRotatedFilledRect(FpPoint center, float width, float height, float angle, Color color)
Create a rotated filled rectangle.
Definition smooth.cpp:91
SmoothShape SmoothThickCircle(FpPoint center, float radius, float thickness, Color color, Color interior_color)
Create a circle with thickness (ring).
Definition smooth.cpp:184
SmoothShape SmoothRoundRect(float x0, float y0, float x1, float y1, float radius, Color color, Color interior_color)
Create an outlined round-rect.
Definition smooth.cpp:169
SmoothShape SmoothCircle(FpPoint center, float radius, Color color, Color interior_color)
Create a circle (optionally with interior color).
Definition smooth.cpp:191
EndingStyle
Line ending style for smooth shapes.
Definition smooth.h:10
@ ENDING_FLAT
Definition smooth.h:12
@ ENDING_ROUNDED
Definition smooth.h:11
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.
Definition smooth.cpp:443
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.
Definition smooth.cpp:38
FillMode
Specifies whether a Drawable should fill its entire extents box, including fully transparent pixels.
Definition drawable.h:15
@ 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.
Definition smooth.cpp:87
SmoothShape SmoothPie(FpPoint center, float radius, float angle_start, float angle_end, Color color)
Create a pie slice.
Definition smooth.cpp:436
SmoothShape SmoothFilledRoundRect(float x0, float y0, float x1, float y1, float radius, Color color)
Create a filled round-rect.
Definition smooth.cpp:174
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.
Definition smooth.cpp:104
SmoothShape SmoothArc(FpPoint center, float radius, float angle_start, float angle_end, Color color)
Create a 1-pixel-wide arc.
Definition smooth.cpp:422
SmoothShape SmoothThickLine(FpPoint a, FpPoint b, float width, Color color, EndingStyle ending_style)
Create a line with width and ending style.
Definition smooth.cpp:82
float bay
Definition smooth.cpp:477
float hd
Definition smooth.cpp:478
DisplayOutput * out
Definition smooth.cpp:886
Color bgcolor
Definition smooth.cpp:889
FillMode fill_mode
Definition smooth.cpp:887
Color pre_blended_interior
Definition smooth.cpp:891
float r
Definition smooth.cpp:474
Color pre_blended_outline
Definition smooth.cpp:890
Color pre_blended_outline_active
Definition smooth.cpp:1027
float bax
Definition smooth.cpp:476
float sqrt_hd
Definition smooth.cpp:479
BlendingMode blending_mode
Definition smooth.cpp:888
Color pre_blended_outline_inactive
Definition smooth.cpp:1028
uint8_t max_alpha
Definition smooth.cpp:480
float dr
Definition smooth.cpp:475
bool round_endings
Definition smooth.cpp:481
Floating-point point.
Definition point.h:12