FastLED 3.10.8
Loading...
Searching...
No Matches
primitives.h
Go to the documentation of this file.
1#pragma once
2
3#include "fl/stl/noexcept.h"
4
5#include "fl/gfx/canvas.h" // IWYU pragma: keep
6#include "fl/gfx/draw_mode.h"
7#include "fl/math/distance_lut.h" // IWYU pragma: keep
8#include "fl/math/integer_math.h" // IWYU pragma: keep
10#include "fl/math/math.h"
11#include "fl/math/scale8.h"
12
13namespace fl {
14namespace gfx {
15
21
25template<typename T>
26inline fl::u8 coordToU8(T alpha) FL_NO_EXCEPT {
27 return (fl::u8)fl::clamp((alpha * T(255.0f) + T(0.5f)).to_int(), 0, 255);
28}
29
30// Specialization for float
31template<>
33 return (fl::u8)fl::round(alpha * 255.0f);
34}
35
36// Specialization for double
37template<>
39 return (fl::u8)fl::round(alpha * 255.0);
40}
41
42// Specialization for int
43template<>
45 return (fl::u8)fl::clamp(alpha, 0, 255);
46}
47
48// Specialization for s16x16: uses from_raw, no float at all
49template<>
52 constexpr fl::s16x16 half = fl::s16x16::from_raw(1 << 15);
53 return (fl::u8)fl::clamp((alpha * c255 + half).to_int(), (fl::i32)0, (fl::i32)255);
54}
55
58template<typename T>
59inline T fromInt(int n) FL_NO_EXCEPT { return T(static_cast<float>(n)); }
60
61template<>
65
68template<typename T>
69inline T fromFrac(int p, int q) FL_NO_EXCEPT { return T(static_cast<float>(p) / static_cast<float>(q)); }
70
71template<>
75
78template<typename T>
79inline T halfOf(T val) FL_NO_EXCEPT { return val / fromInt<T>(2); }
80
81template<>
83 return fl::s16x16::from_raw(val.raw() >> 1);
84}
85
86template<>
87inline float halfOf<float>(float val) { return val * 0.5f; }
88
89template<>
90inline double halfOf<double>(double val) { return val * 0.5; }
91
92template<>
93inline int halfOf<int>(int val) FL_NO_EXCEPT { return val >> 1; }
94
97template<typename T>
98inline int toInt(const T& val) FL_NO_EXCEPT {
99 return val.to_int(); // For s16x16 and similar
100}
101
102// Specialization for float
103template<>
104inline int toInt<float>(const float& val) FL_NO_EXCEPT {
105 return static_cast<int>(val);
106}
107
108// Specialization for int (passthrough)
109template<>
110inline int toInt<int>(const int& val) FL_NO_EXCEPT {
111 return val;
112}
113
114// Specialization for double
115template<>
116inline int toInt<double>(const double& val) FL_NO_EXCEPT {
117 return static_cast<int>(val);
118}
119
123template<typename PixelT, bool Overwrite>
124inline void addPixelToBuffer(PixelT* pixels, int width, int height,
125 int x, int y, const PixelT& color) FL_NO_EXCEPT {
126 if (x >= 0 && x < width && y >= 0 && y < height) {
127 if (Overwrite)
128 pixels[y * width + x] = color;
129 else
130 pixels[y * width + x] += color;
131 }
132}
133
134namespace detail {
135
136// void_t for C++11 SFINAE (detects member existence)
137template<typename...> struct voider { typedef void type; };
138
139// Trait: does this Coord type need division instead of reciprocal multiply?
140// Default (float, double, int — no FRAC_BITS member): use multiply
141template<typename Coord, typename = void>
143
144// Fixed-point types with FRAC_BITS < 16: reciprocal too imprecise, use division
145template<typename Coord>
146struct needs_division<Coord,
147 typename voider<fl::bool_constant<(Coord::FRAC_BITS < 16)>>::type>
148 : fl::bool_constant<(Coord::FRAC_BITS < 16)> {};
149
150// Tag-dispatch: high precision → multiply by precomputed reciprocal
151template<typename Coord>
152inline Coord aaRatioDispatch(Coord num, Coord, Coord inv_denom, fl::false_type) FL_NO_EXCEPT {
153 return num * inv_denom;
154}
155
156// Tag-dispatch: low precision → direct division (more accurate)
157template<typename Coord>
158inline Coord aaRatioDispatch(Coord num, Coord denom, Coord, fl::true_type) FL_NO_EXCEPT {
159 return num / denom;
160}
161
162} // namespace detail
163
167template<typename Coord>
168inline Coord aaRatio(Coord num, Coord denom, Coord inv_denom) FL_NO_EXCEPT {
169 return detail::aaRatioDispatch(num, denom, inv_denom,
171}
172
173// ============================================================================
174// Integer-optimized helpers for disc/ring/line drawing
175// ============================================================================
176
177namespace detail {
178
181template<typename Coord>
182inline fl::i32 toFixed8(Coord val) FL_NO_EXCEPT {
183 return static_cast<fl::i32>(val.to_float() * 256.0f);
184}
185
186template<>
187inline fl::i32 toFixed8<float>(float val) FL_NO_EXCEPT {
188 return static_cast<fl::i32>(val * 256.0f);
189}
190
191template<>
192inline fl::i32 toFixed8<double>(double val) FL_NO_EXCEPT {
193 return static_cast<fl::i32>(val * 256.0);
194}
195
196template<>
197inline fl::i32 toFixed8<int>(int val) FL_NO_EXCEPT {
198 return static_cast<fl::i32>(static_cast<fl::u32>(val) << 8);
199}
200
201template<>
203 // Q16.16 → Q8.8: shift right by 8
204 return static_cast<fl::i32>(val.raw() >> 8);
205}
206
211inline void computeBandShift(fl::i32 band, fl::u8 &shift_out, fl::u16 &inv_out) FL_NO_EXCEPT {
212 fl::u8 sh = 0;
213 fl::u32 tmp = static_cast<fl::u32>(band);
214 while (tmp > 255u) { tmp >>= 1; ++sh; }
215 shift_out = sh;
216 // Reciprocal: (255 * 256 + scaled/2) / scaled — rounds to nearest
217 fl::u8 scaled = static_cast<fl::u8>(tmp);
218 inv_out = static_cast<fl::u16>((65280u + (scaled >> 1)) / scaled);
219}
220
224template<typename Coord>
225inline fl::i32 toQ16(Coord val) FL_NO_EXCEPT {
226 return static_cast<fl::i32>(val.to_float() * 65536.0f);
227}
228
229template<>
230inline fl::i32 toQ16<float>(float val) FL_NO_EXCEPT {
231 return static_cast<fl::i32>(val * 65536.0f);
232}
233
234template<>
235inline fl::i32 toQ16<double>(double val) FL_NO_EXCEPT {
236 return static_cast<fl::i32>(val * 65536.0);
237}
238
239template<>
240inline fl::i32 toQ16<int>(int val) FL_NO_EXCEPT {
241 return static_cast<fl::i32>(val) << 16;
242}
243
244template<>
246 return val.raw();
247}
248
249
253template<typename PixelT>
254struct DiscCtx {
255 fl::i32 xdelta0;
256 int xmin, xmax;
257 fl::i32 rin2, rout2;
259 fl::u16 band_inv; // reciprocal: (255 * 256 + scaled/2) / scaled
260 PixelT color;
261};
262
266template<typename PixelT>
267struct RingCtx {
268 fl::i32 xdelta0;
269 int xmin, xmax;
270 fl::i32 ii2, io2, oi2, oo2;
272 fl::u16 inner_inv, outer_inv; // reciprocals: (255 * 256 + scaled/2) / scaled
273 PixelT color;
274};
275
279template<typename PixelT, bool Overwrite>
280inline void renderDiscRow(PixelT* buf, int w, int py,
281 fl::i32 d2_row,
282 const DiscCtx<PixelT>& f) FL_NO_EXCEPT {
283 PixelT* ptr = &buf[py * w + f.xmin];
284 fl::i32 d2 = d2_row;
285 fl::i32 xd = f.xdelta0;
286 int px = f.xmin;
287 // Phase 1: Skip outside pixels (left, d2 decreasing)
288 while (px <= f.xmax && d2 >= f.rout2) {
289 d2 += xd; xd += 131072; ++ptr; ++px;
290 }
291 // Phase 2: Outer AA fringe (left)
292 while (px <= f.xmax && d2 >= f.rin2 && d2 < f.rout2) {
293 fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(f.rout2 - d2) >> f.band_shift);
294 fl::u8 br = static_cast<fl::u8>((diff * f.band_inv) >> 8);
295 PixelT c = f.color; c.nscale8(br);
296 if (Overwrite) *ptr = c; else *ptr += c;
297 d2 += xd; xd += 131072; ++ptr; ++px;
298 }
299 // Phase 3: Full brightness interior (d2 hits minimum, then increases)
300 while (px <= f.xmax && d2 < f.rin2) {
301 if (Overwrite) *ptr = f.color; else *ptr += f.color;
302 d2 += xd; xd += 131072; ++ptr; ++px;
303 }
304 // Phase 4: Outer AA fringe (right, d2 increasing)
305 while (px <= f.xmax && d2 < f.rout2) {
306 fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(f.rout2 - d2) >> f.band_shift);
307 fl::u8 br = static_cast<fl::u8>((diff * f.band_inv) >> 8);
308 PixelT c = f.color; c.nscale8(br);
309 if (Overwrite) *ptr = c; else *ptr += c;
310 d2 += xd; xd += 131072; ++ptr; ++px;
311 }
312}
313
318template<typename PixelT, bool Overwrite>
319inline void renderRingRow(PixelT* buf, int w, int py,
320 fl::i32 d2_row,
322 PixelT* ptr = &buf[py * w + g.xmin];
323 fl::i32 d2 = d2_row;
324 fl::i32 xd = g.xdelta0;
325 int px = g.xmin;
326 // Preamble: left exterior (d2 >= oo2)
327 while (px <= g.xmax && d2 >= g.oo2) {
328 d2 += xd; xd += 131072; ++ptr; ++px;
329 }
330 // Phase 1: outer-left AA fringe (oi2 <= d2 < oo2)
331 while (px <= g.xmax && d2 >= g.oi2 && d2 < g.oo2) {
332 fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(g.oo2 - d2) >> g.outer_shift);
333 fl::u8 br = static_cast<fl::u8>((diff * g.outer_inv) >> 8);
334 PixelT c = g.color; c.nscale8(br);
335 if (Overwrite) *ptr = c; else *ptr += c;
336 d2 += xd; xd += 131072; ++ptr; ++px;
337 }
338 // Phase 2: full-brightness band left (io2 <= d2 < oi2)
339 while (px <= g.xmax && d2 >= g.io2 && d2 < g.oi2) {
340 if (Overwrite) *ptr = g.color; else *ptr += g.color;
341 d2 += xd; xd += 131072; ++ptr; ++px;
342 }
343 // Phase 3: inner-left AA fringe (ii2 <= d2 < io2)
344 while (px <= g.xmax && d2 >= g.ii2 && d2 < g.io2) {
345 fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(d2 - g.ii2) >> g.inner_shift);
346 fl::u8 br = static_cast<fl::u8>((diff * g.inner_inv) >> 8);
347 PixelT c = g.color; c.nscale8(br);
348 if (Overwrite) *ptr = c; else *ptr += c;
349 d2 += xd; xd += 131072; ++ptr; ++px;
350 }
351 // Phase 4: transparent hole (d2 < ii2)
352 while (px <= g.xmax && d2 < g.ii2) {
353 d2 += xd; xd += 131072; ++ptr; ++px;
354 }
355 // Phase 5: inner-right AA fringe (ii2 <= d2 < io2)
356 while (px <= g.xmax && d2 >= g.ii2 && d2 < g.io2) {
357 fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(d2 - g.ii2) >> g.inner_shift);
358 fl::u8 br = static_cast<fl::u8>((diff * g.inner_inv) >> 8);
359 PixelT c = g.color; c.nscale8(br);
360 if (Overwrite) *ptr = c; else *ptr += c;
361 d2 += xd; xd += 131072; ++ptr; ++px;
362 }
363 // Phase 6: full-brightness band right (io2 <= d2 < oi2)
364 while (px <= g.xmax && d2 >= g.io2 && d2 < g.oi2) {
365 if (Overwrite) *ptr = g.color; else *ptr += g.color;
366 d2 += xd; xd += 131072; ++ptr; ++px;
367 }
368 // Phase 7: outer-right AA fringe (oi2 <= d2 < oo2)
369 while (px <= g.xmax && d2 >= g.oi2 && d2 < g.oo2) {
370 fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(g.oo2 - d2) >> g.outer_shift);
371 fl::u8 br = static_cast<fl::u8>((diff * g.outer_inv) >> 8);
372 PixelT c = g.color; c.nscale8(br);
373 if (Overwrite) *ptr = c; else *ptr += c;
374 d2 += xd; xd += 131072; ++ptr; ++px;
375 }
376}
377
382template<typename PixelT>
383struct StrokeCtx {
384 fl::i32 threshold_q;
385 fl::i32 len2_q;
386 fl::i32 dx_q, dy_q;
388 fl::u16 aa_inv;
389 fl::i32 x0_8, y0_8, x1_8, y1_8;
390 fl::i32 r_max2_8;
392 fl::u16 cap_inv;
393 fl::i32 dot_ext_q;
395 PixelT color;
396 int xmin, xmax;
397};
398
402template<typename PixelT, bool Overwrite>
403inline void renderStrokeRow(PixelT* buf, int w, int py,
404 fl::i32 cross_start, fl::i32 dot_start,
406 PixelT* ptr = &buf[py * w + sc.xmin];
407 fl::i32 cross = cross_start;
408 fl::i32 dot = dot_start;
409 int px = sc.xmin;
410 const fl::i32 thr = sc.threshold_q;
411 const fl::i32 neg_thr = -thr;
412
413 // Phase 1: Skip pixels outside the band
414 while (px <= sc.xmax && (cross >= thr || cross < neg_thr)) {
415 cross += sc.dy_q;
416 dot += sc.dx_q;
417 ++ptr; ++px;
418 }
419
420 // Hoist per-row endcap ey values (py is constant within a row)
421 fl::i32 py8 = static_cast<fl::i32>(py) << 8;
422 fl::i32 ey8_y0 = py8 - sc.y0_8;
423 fl::i32 ey8_y1 = py8 - sc.y1_8;
424
425 // Phase 2: Process visible pixels (|cross| < threshold)
426 while (px <= sc.xmax && cross < thr && cross >= neg_thr) {
427 fl::i32 abs_cross = (cross < 0) ? -cross : cross;
428 bool on_segment = (dot >= 0 && dot <= sc.len2_q);
429
430 if (on_segment) {
431 // Integer AA: shift+multiply (no Coord multiply)
432 fl::u16 shifted = static_cast<fl::u16>(
433 static_cast<fl::u32>(abs_cross) >> sc.aa_shift);
434 fl::u8 linear_idx = static_cast<fl::u8>((shifted * sc.aa_inv) >> 8);
435 // Fast approximate x/255 ≈ (x + 1 + (x >> 8)) >> 8
436 fl::u16 sq = static_cast<fl::u16>(linear_idx) * linear_idx;
437 fl::u8 sq_idx = static_cast<fl::u8>((sq + 1 + (sq >> 8)) >> 8);
438 PixelT c = sc.color;
439 c.nscale8(FL_PGM_READ_BYTE_NEAR(&distanceAA_LUT[sq_idx]));
440 if (Overwrite) *ptr = c; else *ptr += c;
441 } else if (sc.cap == LineCap::ROUND) {
442 // Endpoint distance in 8.8 fixed-point
443 fl::i32 ex8;
444 fl::i16 ey_s;
445 if (dot < 0) {
446 ex8 = (static_cast<fl::i32>(px) << 8) - sc.x0_8;
447 ey_s = static_cast<fl::i16>(ey8_y0);
448 } else {
449 ex8 = (static_cast<fl::i32>(px) << 8) - sc.x1_8;
450 ey_s = static_cast<fl::i16>(ey8_y1);
451 }
452 fl::i16 ex_s = static_cast<fl::i16>(ex8);
453 // 16×16→32 multiply (4 MUL on AVR vs ~70 for 32×32)
454 fl::i32 ed2 = static_cast<fl::i32>(ex_s) * ex_s +
455 static_cast<fl::i32>(ey_s) * ey_s;
456 if (ed2 < sc.r_max2_8) {
457 fl::u16 shifted = static_cast<fl::u16>(
458 static_cast<fl::u32>(ed2) >> sc.cap_shift);
459 fl::u8 idx = static_cast<fl::u8>((shifted * sc.cap_inv) >> 8);
460 PixelT c = sc.color;
461 c.nscale8(FL_PGM_READ_BYTE_NEAR(&distanceAA_LUT[idx]));
462 if (Overwrite) *ptr = c; else *ptr += c;
463 }
464 } else if (sc.cap == LineCap::SQUARE) {
465 if (dot >= -sc.dot_ext_q && dot <= sc.len2_q + sc.dot_ext_q) {
466 fl::u16 shifted = static_cast<fl::u16>(
467 static_cast<fl::u32>(abs_cross) >> sc.aa_shift);
468 fl::u8 linear_idx = static_cast<fl::u8>((shifted * sc.aa_inv) >> 8);
469 fl::u16 sq = static_cast<fl::u16>(linear_idx) * linear_idx;
470 fl::u8 sq_idx = static_cast<fl::u8>((sq + 1 + (sq >> 8)) >> 8);
471 PixelT c = sc.color;
472 c.nscale8(FL_PGM_READ_BYTE_NEAR(&distanceAA_LUT[sq_idx]));
473 if (Overwrite) *ptr = c; else *ptr += c;
474 }
475 }
476 // FLAT: pixels outside [0, len2] are silently dropped
477
478 cross += sc.dy_q;
479 dot += sc.dx_q;
480 ++ptr; ++px;
481 }
482 // Phase 3: remaining pixels are outside the band — nothing to do
483}
484
485} // namespace detail
486
492
497template<typename PixelT, typename Coord>
498void drawLine(Canvas<PixelT>& canvas, const PixelT& color,
499 Coord x0, Coord y0, Coord x1, Coord y1,
501
502template<typename PixelT, typename Coord>
503void drawDisc(Canvas<PixelT>& canvas, const PixelT& color,
504 Coord cx, Coord cy, Coord r,
506
507template<typename PixelT, typename Coord>
508void drawRing(Canvas<PixelT>& canvas, const PixelT& color,
509 Coord cx, Coord cy, Coord r, Coord thickness,
511
512template<typename PixelT, typename Coord>
513void drawStrokeLine(Canvas<PixelT>& canvas, const PixelT& color,
514 Coord x0, Coord y0, Coord x1, Coord y1, Coord thickness,
516
520
524
525// ---------------------------------------------------------------------------
526// drawLine: Integer Wu antialiased line (8.8 fixed-point internally)
527// Uses int loop counter and bit-shift for AA — no Coord ops in inner loop.
528// ---------------------------------------------------------------------------
529namespace detail {
530
531template<typename PixelT, typename Coord, bool Overwrite>
532inline void drawLineCore(Canvas<PixelT>& canvas, const PixelT& color,
533 Coord x0, Coord y0, Coord x1, Coord y1) FL_NO_EXCEPT {
534 PixelT* pixels = canvas.pixels;
535 int width = canvas.width;
536 int height = canvas.height;
537
538 // Convert to 8.8 fixed-point
539 fl::i32 fx0 = detail::toFixed8(x0);
540 fl::i32 fy0 = detail::toFixed8(y0);
541 fl::i32 fx1 = detail::toFixed8(x1);
542 fl::i32 fy1 = detail::toFixed8(y1);
543
544 bool steep = fl::abs(fy1 - fy0) > fl::abs(fx1 - fx0);
545 if (steep) { fl::swap(fx0, fy0); fl::swap(fx1, fy1); }
546 if (fx0 > fx1) { fl::swap(fx0, fx1); fl::swap(fy0, fy1); }
547
548 fl::i32 dx8 = fx1 - fx0;
549 fl::i32 dy8 = fy1 - fy0;
550
551 // Gradient per pixel step in 8.8: (dy * 256) / dx
552 fl::i32 gradient = (dx8 == 0) ? 0 : ((dy8 << 8) / dx8);
553
554 // First endpoint: round x to nearest pixel boundary
555 fl::i32 xend = (fx0 + 128) & ~0xFF;
556 fl::i32 yend = fy0 + ((gradient * (xend - fx0)) >> 8);
557 fl::i32 xgap = 256 - ((fx0 + 128) & 0xFF); // rfpart(x0 + 0.5)
558 int xpxl1 = xend >> 8;
559 int ypxl1 = yend >> 8;
560 fl::u8 yfrac1 = static_cast<fl::u8>(yend & 0xFF);
561
562 // Second endpoint
563 fl::i32 xend2 = (fx1 + 128) & ~0xFF;
564 fl::i32 yend2 = fy1 + ((gradient * (xend2 - fx1)) >> 8);
565 fl::i32 xgap2 = (fx1 + 128) & 0xFF; // fpart(x1 + 0.5)
566 int xpxl2 = xend2 >> 8;
567 int ypxl2 = yend2 >> 8;
568 fl::u8 yfrac2 = static_cast<fl::u8>(yend2 & 0xFF);
569
570 // Draw first endpoint
571 {
572 fl::u8 b1 = static_cast<fl::u8>(((255 - yfrac1) * xgap) >> 8);
573 fl::u8 b2 = static_cast<fl::u8>((yfrac1 * xgap) >> 8);
574 if (steep) {
575 PixelT c = color; c.nscale8(b1);
576 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl1, xpxl1, c);
577 c = color; c.nscale8(b2);
578 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl1 + 1, xpxl1, c);
579 } else {
580 PixelT c = color; c.nscale8(b1);
581 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl1, ypxl1, c);
582 c = color; c.nscale8(b2);
583 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl1, ypxl1 + 1, c);
584 }
585 }
586
587 // Draw second endpoint
588 {
589 fl::u8 b1 = static_cast<fl::u8>(((255 - yfrac2) * xgap2) >> 8);
590 fl::u8 b2 = static_cast<fl::u8>((yfrac2 * xgap2) >> 8);
591 if (steep) {
592 PixelT c = color; c.nscale8(b1);
593 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl2, xpxl2, c);
594 c = color; c.nscale8(b2);
595 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl2 + 1, xpxl2, c);
596 } else {
597 PixelT c = color; c.nscale8(b1);
598 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl2, ypxl2, c);
599 c = color; c.nscale8(b2);
600 addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl2, ypxl2 + 1, c);
601 }
602 }
603
604 // Main loop: integer counter, 8.8 intery accumulation
605 fl::i32 intery = yend + gradient;
606 if (steep) {
607 for (int x = xpxl1 + 1; x < xpxl2; ++x) {
608 int y = intery >> 8;
609 fl::u8 frac = static_cast<fl::u8>(intery & 0xFF);
610 PixelT c = color; c.nscale8(255 - frac);
612 c = color; c.nscale8(frac);
614 intery += gradient;
615 }
616 } else {
617 for (int x = xpxl1 + 1; x < xpxl2; ++x) {
618 int y = intery >> 8;
619 fl::u8 frac = static_cast<fl::u8>(intery & 0xFF);
620 PixelT c = color; c.nscale8(255 - frac);
622 c = color; c.nscale8(frac);
624 intery += gradient;
625 }
626 }
627}
628
629template<typename PixelT, typename Coord, bool Overwrite>
630inline void drawDiscCore(Canvas<PixelT>& canvas, const PixelT& color,
631 Coord cx, Coord cy, Coord r) FL_NO_EXCEPT;
632
633template<typename PixelT, typename Coord, bool Overwrite>
634inline void drawRingCore(Canvas<PixelT>& canvas, const PixelT& color,
635 Coord cx, Coord cy, Coord r, Coord thickness) FL_NO_EXCEPT;
636
637template<typename PixelT, typename Coord, bool Overwrite>
638inline void drawStrokeLineCore(Canvas<PixelT>& canvas, const PixelT& color,
639 Coord x0, Coord y0, Coord x1, Coord y1,
640 Coord thickness, LineCap cap) FL_NO_EXCEPT;
641
642} // namespace detail
643
644template<typename PixelT, typename Coord>
645inline void drawLine(Canvas<PixelT>& canvas, const PixelT& color,
646 Coord x0, Coord y0, Coord x1, Coord y1,
649 detail::drawLineCore<PixelT, Coord, true>(canvas, color, x0, y0, x1, y1);
650 else
651 detail::drawLineCore<PixelT, Coord, false>(canvas, color, x0, y0, x1, y1);
652}
653
654// ---------------------------------------------------------------------------
655// drawDiscCore: Templated on Overwrite for compile-time dispatch.
656// ---------------------------------------------------------------------------
657template<typename PixelT, typename Coord, bool Overwrite>
658inline void detail::drawDiscCore(Canvas<PixelT>& canvas, const PixelT& color,
659 Coord cx, Coord cy, Coord r) FL_NO_EXCEPT {
660 PixelT* pixels = canvas.pixels;
661 int width = canvas.width;
662 int height = canvas.height;
663
664 fl::i32 cx8 = detail::toFixed8(cx);
665 fl::i32 cy8 = detail::toFixed8(cy);
666 fl::i32 r8 = detail::toFixed8(r);
667
668 fl::i32 rin8 = r8 - 128; // r - 0.5 in 8.8
669 fl::i32 rout8 = r8 + 128; // r + 0.5 in 8.8
670
671 fl::i32 rin2 = rin8 * rin8;
672 fl::i32 rout2 = rout8 * rout8;
673 fl::i32 band = rout2 - rin2;
674 if (band <= 0) return;
675
676 int ri = (rout8 >> 8) + 1;
677 int cxi = cx8 >> 8;
678 int cyi = cy8 >> 8;
679
680 int xmin = cxi - ri; if (xmin < 0) xmin = 0;
681 int xmax = cxi + ri; if (xmax >= width) xmax = width - 1;
682 if (xmin > xmax) return;
683 if (cyi + ri < 0 || cyi - ri >= height) return;
684
685 fl::i32 dx8 = (static_cast<fl::i32>(xmin) << 8) - cx8;
686 fl::i32 dx2 = dx8 * dx8;
687
689 fc.xdelta0 = 512 * dx8 + 65536;
690 fc.xmin = xmin; fc.xmax = xmax;
691 fc.rin2 = rin2; fc.rout2 = rout2;
693 fc.color = color;
694
695 fl::i32 cyfrac = (static_cast<fl::i32>(cyi) << 8) - cy8;
696 fl::i32 d2c = dx2 + cyfrac * cyfrac;
697
698 if (cyi >= 0 && cyi < height)
699 detail::renderDiscRow<PixelT, Overwrite>(pixels, width, cyi, d2c, fc);
700
701 fl::i32 botd2 = d2c, botdelta = 512 * cyfrac + 65536;
702 fl::i32 topd2 = d2c, topdelta = -512 * cyfrac + 65536;
703 botd2 += botdelta; botdelta += 131072;
704 topd2 += topdelta; topdelta += 131072;
705
706 for (int dy = 1; dy <= ri; ++dy) {
707 bool cbot = (botd2 - dx2 <= rout2);
708 bool ctop = (topd2 - dx2 <= rout2);
709 if (!cbot && !ctop) break;
710 int pyb = cyi + dy, pyt = cyi - dy;
711 if (cbot && pyb >= 0 && pyb < height)
712 detail::renderDiscRow<PixelT, Overwrite>(pixels, width, pyb, botd2, fc);
713 if (ctop && pyt >= 0 && pyt < height)
714 detail::renderDiscRow<PixelT, Overwrite>(pixels, width, pyt, topd2, fc);
715 botd2 += botdelta; botdelta += 131072;
716 topd2 += topdelta; topdelta += 131072;
717 }
718}
719
720template<typename PixelT, typename Coord>
721inline void drawDisc(Canvas<PixelT>& canvas, const PixelT& color,
722 Coord cx, Coord cy, Coord r,
726 else
728}
729
730// ---------------------------------------------------------------------------
731// drawRingCore: Templated on Overwrite for compile-time dispatch.
732// ---------------------------------------------------------------------------
733template<typename PixelT, typename Coord, bool Overwrite>
734inline void detail::drawRingCore(Canvas<PixelT>& canvas, const PixelT& color,
735 Coord cx, Coord cy, Coord r, Coord thickness) FL_NO_EXCEPT {
736 PixelT* pixels = canvas.pixels;
737 int width = canvas.width;
738 int height = canvas.height;
739
740 fl::i32 cx8 = detail::toFixed8(cx);
741 fl::i32 cy8 = detail::toFixed8(cy);
742 fl::i32 r8 = detail::toFixed8(r);
743 fl::i32 t8 = detail::toFixed8(thickness);
744
745 fl::i32 r_ii8 = r8 - 128;
746 fl::i32 r_io8 = r8 + 128;
747 fl::i32 r_oi8 = r8 + t8 - 128;
748 fl::i32 r_oo8 = r8 + t8 + 128;
749
750 if (r_ii8 < 0) r_ii8 = 0;
751 if (r_io8 < 0) r_io8 = 0;
752
753 fl::i32 ii2 = r_ii8 * r_ii8;
754 fl::i32 io2 = r_io8 * r_io8;
755 fl::i32 oi2 = r_oi8 * r_oi8;
756 fl::i32 oo2 = r_oo8 * r_oo8;
757
758 fl::i32 inner_band = io2 - ii2;
759 fl::i32 outer_band = oo2 - oi2;
760
761 int ri = (r_oo8 >> 8) + 1;
762 int cxi = cx8 >> 8;
763 int cyi = cy8 >> 8;
764
765 int xmin = cxi - ri; if (xmin < 0) xmin = 0;
766 int xmax = cxi + ri; if (xmax >= width) xmax = width - 1;
767 if (xmin > xmax) return;
768 if (cyi + ri < 0 || cyi - ri >= height) return;
769
770 fl::i32 dx8 = (static_cast<fl::i32>(xmin) << 8) - cx8;
771 fl::i32 dx2 = dx8 * dx8;
772
774 gc.xdelta0 = 512 * dx8 + 65536;
775 gc.xmin = xmin; gc.xmax = xmax;
776 gc.ii2 = ii2; gc.io2 = io2; gc.oi2 = oi2; gc.oo2 = oo2;
777 if (inner_band > 0) detail::computeBandShift(inner_band, gc.inner_shift, gc.inner_inv);
778 else { gc.inner_shift = 0; gc.inner_inv = 65280u; }
779 if (outer_band > 0) detail::computeBandShift(outer_band, gc.outer_shift, gc.outer_inv);
780 else { gc.outer_shift = 0; gc.outer_inv = 65280u; }
781 gc.color = color;
782
783 fl::i32 cyfrac = (static_cast<fl::i32>(cyi) << 8) - cy8;
784 fl::i32 d2c = dx2 + cyfrac * cyfrac;
785
786 if (cyi >= 0 && cyi < height)
787 detail::renderRingRow<PixelT, Overwrite>(pixels, width, cyi, d2c, gc);
788
789 fl::i32 botd2 = d2c, botdelta = 512 * cyfrac + 65536;
790 fl::i32 topd2 = d2c, topdelta = -512 * cyfrac + 65536;
791 botd2 += botdelta; botdelta += 131072;
792 topd2 += topdelta; topdelta += 131072;
793
794 for (int dy = 1; dy <= ri; ++dy) {
795 bool cbot = (botd2 - dx2 <= oo2);
796 bool ctop = (topd2 - dx2 <= oo2);
797 if (!cbot && !ctop) break;
798 int pyb = cyi + dy, pyt = cyi - dy;
799 if (cbot && pyb >= 0 && pyb < height)
800 detail::renderRingRow<PixelT, Overwrite>(pixels, width, pyb, botd2, gc);
801 if (ctop && pyt >= 0 && pyt < height)
802 detail::renderRingRow<PixelT, Overwrite>(pixels, width, pyt, topd2, gc);
803 botd2 += botdelta; botdelta += 131072;
804 topd2 += topdelta; topdelta += 131072;
805 }
806}
807
808template<typename PixelT, typename Coord>
809inline void drawRing(Canvas<PixelT>& canvas, const PixelT& color,
810 Coord cx, Coord cy, Coord r, Coord thickness,
813 detail::drawRingCore<PixelT, Coord, true>(canvas, color, cx, cy, r, thickness);
814 else
815 detail::drawRingCore<PixelT, Coord, false>(canvas, color, cx, cy, r, thickness);
816}
817
818// ---------------------------------------------------------------------------
819// drawStrokeLineCore: Templated on Overwrite for compile-time dispatch.
820// ---------------------------------------------------------------------------
821template<typename PixelT, typename Coord, bool Overwrite>
822inline void detail::drawStrokeLineCore(Canvas<PixelT>& canvas, const PixelT& color,
823 Coord x0, Coord y0, Coord x1, Coord y1,
824 Coord thickness, LineCap cap) FL_NO_EXCEPT {
825 PixelT* pixels = canvas.pixels;
826 int width = canvas.width;
827 int height = canvas.height;
828
829 fl::i32 x0_8 = detail::toFixed8(x0), y0_8 = detail::toFixed8(y0);
830 fl::i32 x1_8 = detail::toFixed8(x1), y1_8 = detail::toFixed8(y1);
831 fl::i32 dx_8 = x1_8 - x0_8;
832 fl::i32 dy_8 = y1_8 - y0_8;
833 if (dx_8 == 0 && dy_8 == 0) return;
834
835 fl::i32 len2_16 = dx_8 * dx_8 + dy_8 * dy_8;
836 fl::i32 len_8 = static_cast<fl::i32>(
837 fl::isqrt32(static_cast<fl::u32>(len2_16)));
838 fl::i32 thickness_8 = detail::toFixed8(thickness);
839 fl::i32 r_max_8 = thickness_8 >> 1;
840 fl::i32 threshold_q = r_max_8 * len_8;
841
842 if (threshold_q <= 0) return;
843
844 int x0i = x0_8 >> 8, y0i = y0_8 >> 8;
845 int x1i = x1_8 >> 8, y1i = y1_8 >> 8;
846 int margin = ((r_max_8 + 255) >> 8) + 2;
847 int xmin = (x0i < x1i ? x0i : x1i) - margin;
848 int xmax = (x0i > x1i ? x0i : x1i) + margin;
849 int ymin = (y0i < y1i ? y0i : y1i) - margin;
850 int ymax = (y0i > y1i ? y0i : y1i) + margin;
851
852 if (xmin < 0) xmin = 0;
853 if (xmax >= width) xmax = width - 1;
854 if (ymin < 0) ymin = 0;
855 if (ymax >= height) ymax = height - 1;
856 if (xmin > xmax || ymin > ymax) return;
857
858 fl::i32 dx_q = dx_8 << 8;
859 fl::i32 dy_q = dy_8 << 8;
860
861 fl::i32 rx_base_8 = (static_cast<fl::i32>(xmin) << 8) - x0_8;
862 fl::i32 ry_row_8 = (static_cast<fl::i32>(ymin) << 8) - y0_8;
863 fl::i32 cross_row_q = rx_base_8 * dy_8 - ry_row_8 * dx_8;
864 fl::i32 dot_row_q = rx_base_8 * dx_8 + ry_row_8 * dy_8;
865
866 fl::i32 len2_q = len_8 * len_8;
867 fl::i32 dot_ext_q = (cap == LineCap::SQUARE) ? threshold_q : 0;
868
870 sc.threshold_q = threshold_q;
871 sc.len2_q = len2_q;
872 sc.dx_q = dx_q;
873 sc.dy_q = dy_q;
874 sc.dot_ext_q = dot_ext_q;
875 detail::computeBandShift(threshold_q, sc.aa_shift, sc.aa_inv);
876 sc.x0_8 = x0_8; sc.y0_8 = y0_8;
877 sc.x1_8 = x1_8; sc.y1_8 = y1_8;
878 sc.r_max2_8 = r_max_8 * r_max_8;
879 sc.cap_shift = 0; sc.cap_inv = 0;
880 if (cap == LineCap::ROUND && sc.r_max2_8 > 0) {
882 }
883 sc.cap = cap;
884 sc.color = color;
885 sc.xmin = xmin; sc.xmax = xmax;
886
887 for (int py = ymin; py <= ymax; ++py) {
888 if (py >= 0 && py < height) {
890 cross_row_q, dot_row_q, sc);
891 }
892 cross_row_q -= sc.dx_q;
893 dot_row_q += sc.dy_q;
894 }
895}
896
897template<typename PixelT, typename Coord>
898inline void drawStrokeLine(Canvas<PixelT>& canvas, const PixelT& color,
899 Coord x0, Coord y0, Coord x1, Coord y1, Coord thickness,
902 detail::drawStrokeLineCore<PixelT, Coord, true>(canvas, color, x0, y0, x1, y1, thickness, cap);
903 else
904 detail::drawStrokeLineCore<PixelT, Coord, false>(canvas, color, x0, y0, x1, y1, thickness, cap);
905}
906
907} // namespace gfx
908} // namespace fl
Canvas types for gfx primitives (implementation)
Generic canvas for any pixel type (e.g.
Definition gfx.h:53
static constexpr i32 SCALE
Definition s16x16.h:25
static constexpr FASTLED_FORCE_INLINE s16x16 from_raw(i32 raw) FL_NO_EXCEPT
Definition s16x16.h:56
#define FL_PGM_READ_BYTE_NEAR(x)
Read a byte (8-bit) from PROGMEM memory.
Legacy compatibility header for 8-bit scaling functions.
Compile-time linker keep-alive hook for a single fl::Bus.
Definition bus_info.h:57
Coord aaRatioDispatch(Coord num, Coord, Coord inv_denom, fl::false_type) FL_NO_EXCEPT
Definition primitives.h:152
void drawRingCore(Canvas< PixelT > &canvas, const PixelT &color, Coord cx, Coord cy, Coord r, Coord thickness) FL_NO_EXCEPT
Definition primitives.h:734
void drawLineCore(Canvas< PixelT > &canvas, const PixelT &color, Coord x0, Coord y0, Coord x1, Coord y1) FL_NO_EXCEPT
Definition primitives.h:532
void drawDiscCore(Canvas< PixelT > &canvas, const PixelT &color, Coord cx, Coord cy, Coord r) FL_NO_EXCEPT
Definition primitives.h:658
void renderDiscRow(PixelT *buf, int w, int py, fl::i32 d2_row, const DiscCtx< PixelT > &f) FL_NO_EXCEPT
Render one scanline of a disc using incremental d².
Definition primitives.h:280
void renderRingRow(PixelT *buf, int w, int py, fl::i32 d2_row, const RingCtx< PixelT > &g) FL_NO_EXCEPT
Render one scanline of a ring using incremental d² with phase-based scanning.
Definition primitives.h:319
void drawStrokeLineCore(Canvas< PixelT > &canvas, const PixelT &color, Coord x0, Coord y0, Coord x1, Coord y1, Coord thickness, LineCap cap) FL_NO_EXCEPT
Definition primitives.h:822
fl::i32 toQ16(Coord val) FL_NO_EXCEPT
Convert any Coord type to Q16.16 raw i32 for integer inner loops.
Definition primitives.h:225
fl::i32 toFixed8(Coord val) FL_NO_EXCEPT
Convert any Coord type to 8.8 fixed-point (fl::i32).
Definition primitives.h:182
void computeBandShift(fl::i32 band, fl::u8 &shift_out, fl::u16 &inv_out) FL_NO_EXCEPT
Precompute right-shift and reciprocal multiplier for AA division.
Definition primitives.h:211
void renderStrokeRow(PixelT *buf, int w, int py, fl::i32 cross_start, fl::i32 dot_start, const StrokeCtx< PixelT > &sc) FL_NO_EXCEPT
Render one scanline of a stroke line using phase-based scanning.
Definition primitives.h:403
const fl::u8 distanceAA_LUT[256]
Distance antialiasing lookup table Maps normalized squared distance d²/r_max² (scaled 0....
============================================================================
Definition primitives.h:134
Stroke line context: bundles per-line constants into a struct passed by reference.
Definition primitives.h:383
Ring context: bundles all per-circle constants into a struct passed by reference.
Definition primitives.h:267
Disc context: bundles per-circle constants into a struct passed by reference.
Definition primitives.h:254
T fromInt(int n) FL_NO_EXCEPT
Convert an integer to Coord without float intermediate.
Definition primitives.h:59
void drawStrokeLine(Canvas< PixelT > &canvas, const PixelT &color, Coord x0, Coord y0, Coord x1, Coord y1, Coord thickness, LineCap cap, fl::DrawMode mode=fl::DrawMode::DRAW_MODE_BLEND) FL_NO_EXCEPT
Definition primitives.h:898
fl::u8 coordToU8< float >(float alpha) FL_NO_EXCEPT
Definition primitives.h:32
int toInt< double >(const double &val) FL_NO_EXCEPT
Definition primitives.h:116
fl::u8 coordToU8< double >(double alpha) FL_NO_EXCEPT
Definition primitives.h:38
LineCap
Line cap styles for stroke operations.
Definition canvas.h:19
fl::s16x16 fromFrac< fl::s16x16 >(int p, int q) FL_NO_EXCEPT
Definition primitives.h:72
void drawLine(Canvas< PixelT > &canvas, const PixelT &color, Coord x0, Coord y0, Coord x1, Coord y1, fl::DrawMode mode=fl::DrawMode::DRAW_MODE_BLEND) FL_NO_EXCEPT
============================================================================
Definition primitives.h:645
int toInt< int >(const int &val) FL_NO_EXCEPT
Definition primitives.h:110
int toInt(const T &val) FL_NO_EXCEPT
Helper to convert any coordinate type to int Supports: s16x16 (via to_int()), float,...
Definition primitives.h:98
fl::u8 coordToU8< fl::s16x16 >(fl::s16x16 alpha) FL_NO_EXCEPT
Definition primitives.h:50
T fromFrac(int p, int q) FL_NO_EXCEPT
Convert rational p/q to Coord without float.
Definition primitives.h:69
float halfOf< float >(float val)
Definition primitives.h:87
void drawRing(Canvas< PixelT > &canvas, const PixelT &color, Coord cx, Coord cy, Coord r, Coord thickness, fl::DrawMode mode=fl::DrawMode::DRAW_MODE_BLEND) FL_NO_EXCEPT
Definition primitives.h:809
void addPixelToBuffer(PixelT *pixels, int width, int height, int x, int y, const PixelT &color) FL_NO_EXCEPT
Internal helper: add or set pixel to rectangular buffer with bounds checking Direct row-major indexin...
Definition primitives.h:124
fl::u8 coordToU8< int >(int alpha) FL_NO_EXCEPT
Definition primitives.h:44
Coord aaRatio(Coord num, Coord denom, Coord inv_denom) FL_NO_EXCEPT
Compute num/denom using the best strategy for the Coord type:
Definition primitives.h:168
int halfOf< int >(int val) FL_NO_EXCEPT
Definition primitives.h:93
T halfOf(T val) FL_NO_EXCEPT
Divide Coord by 2 using shift (avoids expensive division on embedded).
Definition primitives.h:79
fl::s16x16 fromInt< fl::s16x16 >(int n) FL_NO_EXCEPT
Definition primitives.h:62
fl::u8 coordToU8(T alpha) FL_NO_EXCEPT
sqrt is provided by fl::sqrt overloads in math.h for:
Definition primitives.h:26
int toInt< float >(const float &val) FL_NO_EXCEPT
Definition primitives.h:104
fl::s16x16 halfOf< fl::s16x16 >(fl::s16x16 val) FL_NO_EXCEPT
Definition primitives.h:82
void drawDisc(Canvas< PixelT > &canvas, const PixelT &color, Coord cx, Coord cy, Coord r, fl::DrawMode mode=fl::DrawMode::DRAW_MODE_BLEND) FL_NO_EXCEPT
Definition primitives.h:721
double halfOf< double >(double val)
Definition primitives.h:90
unsigned char u8
Definition stdint.h:131
void swap(array< T, N > &lhs, array< T, N > &rhs) FL_NO_EXCEPT
Definition array.h:209
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
u8 u8 height
Definition blur.h:186
FL_OPTIMIZE_FUNCTION constexpr u16 isqrt32(u32 x) FL_NO_EXCEPT
Definition isqrt.h:53
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NO_EXCEPT
integral_constant< bool, false > false_type
Definition type_traits.h:29
integral_constant< bool, B > bool_constant
Definition type_traits.h:33
enable_if<!is_integral< T >::value, T >::type round(T value) FL_NO_EXCEPT
Definition math.h:327
u8 width
Definition blur.h:186
gfx::LineCap LineCap
Line cap style.
Definition gfx.h:48
DrawMode
Definition draw_mode.h:5
@ DRAW_MODE_OVERWRITE
Definition draw_mode.h:5
InputGamut g
Definition rgbw.h:125
constexpr enable_if< is_fixed_point< T >::value, T >::type abs(T x) FL_NO_EXCEPT
integral_constant< bool, true > true_type
Definition type_traits.h:28
Base definition for an LED controller.
Definition crgb.hpp:179
Simple rectangular canvas for graphics operations Combines a pixel buffer with dimensions for cache-o...
Definition canvas.h:66