FastLED 3.10.8
Loading...
Searching...
No Matches

◆ renderStrokeRow()

template<typename PixelT, bool Overwrite>
void fl::gfx::detail::renderStrokeRow ( PixelT * buf,
int w,
int py,
fl::i32 cross_start,
fl::i32 dot_start,
const StrokeCtx< PixelT > & sc )
inline

Render one scanline of a stroke line using phase-based scanning.

Templated on Overwrite for compile-time dispatch (no per-pixel branch). AA uses precomputed shift+reciprocal — zero Coord multiplies.

Definition at line 403 of file primitives.h.

405 {
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}
#define FL_PGM_READ_BYTE_NEAR(x)
Read a byte (8-bit) from PROGMEM memory.
const fl::u8 distanceAA_LUT[256]
Distance antialiasing lookup table Maps normalized squared distance d²/r_max² (scaled 0....
unsigned char u8
Definition stdint.h:131

References distanceAA_LUT, fl::FL_NO_EXCEPT, FL_PGM_READ_BYTE_NEAR, and renderStrokeRow().

Referenced by drawStrokeLineCore(), and renderStrokeRow().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: