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