FastLED 3.10.8
Loading...
Searching...
No Matches
blur.cpp.hpp
Go to the documentation of this file.
1
2
3#include "fl/stl/stdint.h"
4
5#define FASTLED_INTERNAL
6#include "fl/system/fastled.h"
7
8#include "crgb.h"
9#include "fl/gfx/blur.h"
12#include "fl/log/log.h"
13#include "fl/math/xymap.h"
14#include "fl/math/scale8.h"
15#include "fl/stl/int.h"
16#include "fl/stl/span.h"
17#include "fl/gfx/crgb.h"
18#include "fl/gfx/crgb16.h"
19#include "fl/stl/singleton.h"
20#include "fl/stl/vector.h"
21
22// Platform-neutral SIMD for blur kernels (SSE2, NEON, Xtensa PIE, scalar).
23#if !defined(FL_IS_AVR)
24#include "fl/math/simd.h"
25#include "fl/stl/noexcept.h"
26#endif
27
28// Force O3 even in debug builds so blur benchmarks don't hit watchdog timeouts.
30
31// Legacy XY function. This is a weak symbol that can be overridden by the user.
32// IMPORTANT: This MUST be in the global namespace (not fl::) for backward compatibility
33// with user code from FastLED 3.7.6 that defines: uint16_t XY(uint8_t x, uint8_t y)
35
39 FL_ERROR("XY function not provided - using default [0][0]. Use blur2d with XYMap instead");
40 return 0;
41}
42
43namespace fl {
44
45// make this a weak symbol
46namespace {
47fl::u16 xy_legacy_wrapper(fl::u16 x, fl::u16 y, fl::u16 width,
48 fl::u16 height) FL_NO_EXCEPT {
51 return ::XY(x, y); // Call global namespace XY
52}
53} // namespace
54
55namespace gfx {
56
57// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors.
58// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors.
59//
60// 0 = no spread at all
61// 64 = moderate spreading
62// 172 = maximum smooth, even spreading
63//
64// 173..255 = wider spreading, but increasing flicker
65//
66// Total light is NOT entirely conserved, so many repeated
67// calls to 'blur' will also result in the light fading,
68// eventually all the way to black; this is by design so that
69// it can be used to (slowly) clear the LEDs to black.
71 const fl::u16 numLeds = static_cast<fl::u16>(leds.size());
72 fl::u8 keep = 255 - blur_amount;
73 fl::u8 seep = blur_amount >> 1;
74 CRGB carryover = CRGB::Black;
75 for (fl::u16 i = 0; i < numLeds; ++i) {
76 CRGB cur = leds[i];
77 CRGB part = cur;
78 part.nscale8(seep);
79 cur.nscale8(keep);
80 cur += carryover;
81 if (i)
82 leds[i - 1] += part;
83 leds[i] = cur;
84 carryover = part;
85 }
86}
87
89 fract8 blur_amount, const XYMap &xymap) FL_NO_EXCEPT {
90 gfx::blurRows(leds, width, height, blur_amount, xymap);
91 gfx::blurColumns(leds, width, height, blur_amount, xymap);
92}
93
95 // Legacy path: uses global XY() via XYMap for user-defined layouts.
96 // Keeps its own blur algorithm copy because XYMap indexing differs from
97 // the cache-coherent rectangular layout used by Canvas.
98 XYMap xyMap =
100 fl::u8 keep = 255 - blur_amount;
101 fl::u8 seep = blur_amount >> 1;
102 // blur rows
103 for (fl::u8 row = 0; row < height; ++row) {
104 CRGB carryover = CRGB::Black;
105 for (fl::u8 i = 0; i < width; ++i) {
106 CRGB cur = leds[xyMap.mapToIndex(i, row)];
107 CRGB part = cur;
108 part.nscale8(seep);
109 cur.nscale8(keep);
110 cur += carryover;
111 if (i)
112 leds[xyMap.mapToIndex(i - 1, row)] += part;
113 leds[xyMap.mapToIndex(i, row)] = cur;
114 carryover = part;
115 }
116 }
117 // blur columns
118 for (fl::u8 col = 0; col < width; ++col) {
119 CRGB carryover = CRGB::Black;
120 for (fl::u8 i = 0; i < height; ++i) {
121 CRGB cur = leds[xyMap.mapToIndex(col, i)];
122 CRGB part = cur;
123 part.nscale8(seep);
124 cur.nscale8(keep);
125 cur += carryover;
126 if (i)
127 leds[xyMap.mapToIndex(col, i - 1)] += part;
128 leds[xyMap.mapToIndex(col, i)] = cur;
129 carryover = part;
130 }
131 }
132}
133
135 fract8 blur_amount, const XYMap &xyMap) FL_NO_EXCEPT {
136 CRGB *pixels = leds.data();
137 fl::u8 keep = 255 - blur_amount;
138 fl::u8 seep = blur_amount >> 1;
139 if (xyMap.isRectangularGrid()) {
140 for (fl::u8 row = 0; row < height; ++row) {
141 CRGB carryover = CRGB::Black;
142 CRGB *rowBase = pixels + row * width;
143 for (fl::u8 col = 0; col < width; ++col) {
144 CRGB cur = rowBase[col];
145 CRGB part = cur;
146 part.nscale8(seep);
147 cur.nscale8(keep);
148 cur += carryover;
149 if (col)
150 rowBase[col - 1] += part;
151 rowBase[col] = cur;
152 carryover = part;
153 }
154 }
155 return;
156 }
157 for (fl::u8 row = 0; row < height; ++row) {
158 CRGB carryover = CRGB::Black;
159 for (fl::u8 i = 0; i < width; ++i) {
160 CRGB cur = leds[xyMap.mapToIndex(i, row)];
161 CRGB part = cur;
162 part.nscale8(seep);
163 cur.nscale8(keep);
164 cur += carryover;
165 if (i)
166 leds[xyMap.mapToIndex(i - 1, row)] += part;
167 leds[xyMap.mapToIndex(i, row)] = cur;
168 carryover = part;
169 }
170 }
171}
172
174 fract8 blur_amount, const XYMap &xyMap) FL_NO_EXCEPT {
175 CRGB *pixels = leds.data();
176 fl::u8 keep = 255 - blur_amount;
177 fl::u8 seep = blur_amount >> 1;
178 if (xyMap.isRectangularGrid()) {
179 for (fl::u8 col = 0; col < width; ++col) {
180 CRGB carryover = CRGB::Black;
181 for (fl::u8 row = 0; row < height; ++row) {
182 CRGB cur = pixels[row * width + col];
183 CRGB part = cur;
184 part.nscale8(seep);
185 cur.nscale8(keep);
186 cur += carryover;
187 if (row)
188 pixels[(row - 1) * width + col] += part;
189 pixels[row * width + col] = cur;
190 carryover = part;
191 }
192 }
193 return;
194 }
195 for (fl::u8 col = 0; col < width; ++col) {
196 CRGB carryover = CRGB::Black;
197 for (fl::u8 i = 0; i < height; ++i) {
198 CRGB cur = leds[xyMap.mapToIndex(col, i)];
199 CRGB part = cur;
200 part.nscale8(seep);
201 cur.nscale8(keep);
202 cur += carryover;
203 if (i)
204 leds[xyMap.mapToIndex(col, i - 1)] += part;
205 leds[xyMap.mapToIndex(col, i)] = cur;
206 carryover = part;
207 }
208 }
209}
210
211void blurRows(Canvas<CRGB> &canvas, alpha8 blur_amount) FL_NO_EXCEPT {
212 const int w = canvas.width;
213 const int h = canvas.height;
214 CRGB *pixels = canvas.pixels;
215 fl::u8 keep = 255 - blur_amount;
216 fl::u8 seep = blur_amount >> 1;
217 for (int row = 0; row < h; ++row) {
218 CRGB carryover = CRGB::Black;
219 CRGB *rowBase = pixels + row * w;
220 for (int col = 0; col < w; ++col) {
221 CRGB cur = rowBase[col];
222 CRGB part = cur;
223 part.nscale8(seep);
224 cur.nscale8(keep);
225 cur += carryover;
226 if (col)
227 rowBase[col - 1] += part;
228 rowBase[col] = cur;
229 carryover = part;
230 }
231 }
232}
233
234void blurColumns(Canvas<CRGB> &canvas, alpha8 blur_amount) FL_NO_EXCEPT {
235 const int w = canvas.width;
236 const int h = canvas.height;
237 CRGB *pixels = canvas.pixels;
238 fl::u8 keep = 255 - blur_amount;
239 fl::u8 seep = blur_amount >> 1;
240 for (int col = 0; col < w; ++col) {
241 CRGB carryover = CRGB::Black;
242 for (int row = 0; row < h; ++row) {
243 CRGB cur = pixels[row * w + col];
244 CRGB part = cur;
245 part.nscale8(seep);
246 cur.nscale8(keep);
247 cur += carryover;
248 if (row)
249 pixels[(row - 1) * w + col] += part;
250 pixels[row * w + col] = cur;
251 carryover = part;
252 }
253 }
254}
255
256void blur2d(Canvas<CRGB> &canvas, alpha8 blur_amount) FL_NO_EXCEPT {
257 gfx::blurRows(canvas, blur_amount);
258 gfx::blurColumns(canvas, blur_amount);
259}
260
261} // namespace gfx
262} // namespace fl
263
264// ── Separable Gaussian blur — two-pass binomial convolution ──────────
265//
266// Based on sutaburosu's SKIPSM Gaussian blur implementation.
267// Uses binomial coefficient weights (Pascal's triangle) for a fast and
268// flexible Gaussian blur approximation, optimized for kernel sizes up to 9×9.
269// https://people.videolan.org/~tmatth/papers/Gaussian%20blur%20using%20finite-state%20machines.pdf
270//
271// Two separable passes (horizontal then vertical), each applying the 1D
272// binomial kernel for the given radius:
273// radius 0: [1] sum = 1 shift = 0
274// radius 1: [1, 2, 1] sum = 4 shift = 2
275// radius 2: [1, 4, 6, 4, 1] sum = 16 shift = 4
276// radius 3: [1, 6, 15, 20, 15, 6, 1] sum = 64 shift = 6
277// radius 4: [1, 8, 28, 56, 70, 56, 28, 8, 1] sum = 256 shift = 8
278//
279// Out-of-bounds pixels are treated as zero (zero-padding).
280// Normalization by right-shift of 2*radius bits per pass.
281
282namespace fl {
283namespace gfx {
284
285
286namespace blur_detail {
287
288// Identity alpha value for each type (no dimming).
289template <typename AlphaT> constexpr AlphaT alpha_identity() FL_NO_EXCEPT;
291template <> constexpr alpha16 alpha_identity<alpha16>() FL_NO_EXCEPT { return alpha16(65535); }
292
293// Channel extraction and alpha-scaled pixel construction.
294template <typename RGB_T>
296
297template <>
299 FL_ALWAYS_INLINE u16 ch(u8 v) FL_NO_EXCEPT { return v; }
301
303 return CRGB(static_cast<u8>(r), static_cast<u8>(g),
304 static_cast<u8>(b));
305 }
306
308 if (a.value == 255) return make(r, g, b);
309 u16 a1 = static_cast<u16>(a.value) + 1;
310 return CRGB(static_cast<u8>((r * a1) >> 8),
311 static_cast<u8>((g * a1) >> 8),
312 static_cast<u8>((b * a1) >> 8));
313 }
314
316 if (a.value >= 65535) return make(r, g, b);
317 u32 a1 = static_cast<u32>(a.value) + 1;
318 return CRGB(static_cast<u8>((r * a1) >> 16),
319 static_cast<u8>((g * a1) >> 16),
320 static_cast<u8>((b * a1) >> 16));
321 }
322};
323
324template <>
326 FL_ALWAYS_INLINE u32 ch(u8x8 v) FL_NO_EXCEPT { return v.raw(); }
328
330 return CRGB16(u8x8::from_raw(static_cast<u16>(r)),
331 u8x8::from_raw(static_cast<u16>(g)),
332 u8x8::from_raw(static_cast<u16>(b)));
333 }
334
336 if (a.value == 255) return make(r, g, b);
337 u32 a1 = static_cast<u32>(a.value) + 1;
338 return make((r * a1) >> 8, (g * a1) >> 8, (b * a1) >> 8);
339 }
340
342 if (a.value >= 65535) return make(r, g, b);
343 u32 a1 = static_cast<u32>(a.value) + 1;
344 return make((r * a1) >> 16, (g * a1) >> 16, (b * a1) >> 16);
345 }
346};
347
348// Thread-local padded pixel buffer for zero-padding approach.
349template <typename RGB_T>
352 if (static_cast<int>(buf.size()) < minSize) {
353 buf.resize(minSize);
354 }
355 return buf;
356}
357
358// Interior row pixel — fully-unrolled, no bounds checks.
359// Also reused for vertical pass via linearized column data.
360// Template-specialized per radius for direct hardcoded weights.
361template <int R, typename RGB_T, typename acc_t>
363
364template <typename RGB_T, typename acc_t>
365struct interior_row<0, RGB_T, acc_t> {
366 FL_ALWAYS_INLINE void apply(const RGB_T *row, int x,
367 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
368 using P = pixel_ops<RGB_T>;
369 r = P::ch(row[x].r); g = P::ch(row[x].g); b = P::ch(row[x].b);
370 }
371};
372
373template <typename RGB_T, typename acc_t>
374struct interior_row<1, RGB_T, acc_t> {
375 FL_ALWAYS_INLINE void apply(const RGB_T *row, int x,
376 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
377 using P = pixel_ops<RGB_T>;
378 P p;
379 r = 0; g = 0; b = 0;
380 { const RGB_T &px = row[x-1]; r += p.ch(px.r); g += p.ch(px.g); b += p.ch(px.b); }
381 { const RGB_T &px = row[x]; r += p.ch(px.r) * 2; g += p.ch(px.g) * 2; b += p.ch(px.b) * 2; }
382 { const RGB_T &px = row[x+1]; r += p.ch(px.r); g += p.ch(px.g); b += p.ch(px.b); }
383 }
384};
385
386template <typename RGB_T, typename acc_t>
387struct interior_row<2, RGB_T, acc_t> {
388 FL_ALWAYS_INLINE void apply(const RGB_T *row, int x,
389 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
390 using P = pixel_ops<RGB_T>;
391 P p;
392 // [1, 4, 6, 4, 1] — symmetric: (e0+e4) + 4*(e1+e3) + 6*e2
393 const RGB_T &pm2 = row[x-2], &pm1 = row[x-1], &pc = row[x], &pp1 = row[x+1], &pp2 = row[x+2];
394 const acc_t s04r = p.ch(pm2.r) + p.ch(pp2.r), s13r = p.ch(pm1.r) + p.ch(pp1.r);
395 const acc_t s04g = p.ch(pm2.g) + p.ch(pp2.g), s13g = p.ch(pm1.g) + p.ch(pp1.g);
396 const acc_t s04b = p.ch(pm2.b) + p.ch(pp2.b), s13b = p.ch(pm1.b) + p.ch(pp1.b);
397 r = s04r + s13r * 4 + p.ch(pc.r) * 6;
398 g = s04g + s13g * 4 + p.ch(pc.g) * 6;
399 b = s04b + s13b * 4 + p.ch(pc.b) * 6;
400 }
401};
402
403template <typename RGB_T, typename acc_t>
404struct interior_row<3, RGB_T, acc_t> {
405 FL_ALWAYS_INLINE void apply(const RGB_T *row, int x,
406 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
407 using P = pixel_ops<RGB_T>;
408 P p;
409 // [1, 6, 15, 20, 15, 6, 1] — symmetric: (e0+e6) + 6*(e1+e5) + 15*(e2+e4) + 20*e3
410 const RGB_T &pm3 = row[x-3], &pm2 = row[x-2], &pm1 = row[x-1], &pc = row[x];
411 const RGB_T &pp1 = row[x+1], &pp2 = row[x+2], &pp3 = row[x+3];
412 const acc_t s06r = p.ch(pm3.r) + p.ch(pp3.r), s15r = p.ch(pm2.r) + p.ch(pp2.r), s24r = p.ch(pm1.r) + p.ch(pp1.r);
413 const acc_t s06g = p.ch(pm3.g) + p.ch(pp3.g), s15g = p.ch(pm2.g) + p.ch(pp2.g), s24g = p.ch(pm1.g) + p.ch(pp1.g);
414 const acc_t s06b = p.ch(pm3.b) + p.ch(pp3.b), s15b = p.ch(pm2.b) + p.ch(pp2.b), s24b = p.ch(pm1.b) + p.ch(pp1.b);
415 r = s06r + s15r * 6 + s24r * 15 + p.ch(pc.r) * 20;
416 g = s06g + s15g * 6 + s24g * 15 + p.ch(pc.g) * 20;
417 b = s06b + s15b * 6 + s24b * 15 + p.ch(pc.b) * 20;
418 }
419};
420
421template <typename RGB_T, typename acc_t>
422struct interior_row<4, RGB_T, acc_t> {
423 FL_ALWAYS_INLINE void apply(const RGB_T *row, int x,
424 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
425 using P = pixel_ops<RGB_T>;
426 P p;
427 // [1, 8, 28, 56, 70, 56, 28, 8, 1] — symmetric: (e0+e8) + 8*(e1+e7) + 28*(e2+e6) + 56*(e3+e5) + 70*e4
428 const RGB_T &pm4 = row[x-4], &pm3 = row[x-3], &pm2 = row[x-2], &pm1 = row[x-1], &pc = row[x];
429 const RGB_T &pp1 = row[x+1], &pp2 = row[x+2], &pp3 = row[x+3], &pp4 = row[x+4];
430 const acc_t s08r = p.ch(pm4.r) + p.ch(pp4.r), s17r = p.ch(pm3.r) + p.ch(pp3.r);
431 const acc_t s26r = p.ch(pm2.r) + p.ch(pp2.r), s35r = p.ch(pm1.r) + p.ch(pp1.r);
432 const acc_t s08g = p.ch(pm4.g) + p.ch(pp4.g), s17g = p.ch(pm3.g) + p.ch(pp3.g);
433 const acc_t s26g = p.ch(pm2.g) + p.ch(pp2.g), s35g = p.ch(pm1.g) + p.ch(pp1.g);
434 const acc_t s08b = p.ch(pm4.b) + p.ch(pp4.b), s17b = p.ch(pm3.b) + p.ch(pp3.b);
435 const acc_t s26b = p.ch(pm2.b) + p.ch(pp2.b), s35b = p.ch(pm1.b) + p.ch(pp1.b);
436 r = s08r + s17r * 8 + s26r * 28 + s35r * 56 + p.ch(pc.r) * 70;
437 g = s08g + s17g * 8 + s26g * 28 + s35g * 56 + p.ch(pc.g) * 70;
438 b = s08b + s17b * 8 + s26b * 28 + s35b * 56 + p.ch(pc.b) * 70;
439 }
440};
441
442// ── AVR per-channel convolution kernels ──────────────────────────────
443// On AVR, processing one color channel at a time cuts live accumulator
444// registers from 6 (r,g,b as u16 pairs) to 2, dramatically reducing
445// register spilling in the 32-register AVR architecture.
446//
447// conv1ch<R>::apply(center): computes the 1D binomial kernel for a
448// single u8 channel, where `center` points to the center pixel's
449// channel byte and the pixel stride is sizeof(CRGB) = 3 (compile-time
450// constant).
451#if defined(FL_IS_AVR)
452
453template <int R> struct conv1ch;
454
455// All conv1ch specializations use positive-only offsets from the window
456// start (p = center - R*S). This maps cleanly to AVR's LDD instruction
457// which supports displacement 0-63. Max offset = 2*R*S = 24 for R4.
458template <> struct conv1ch<0> {
459 static inline u16 __attribute__((always_inline)) apply(const u8 *c) {
460 return (u16)c[0];
461 }
462};
463
464template <> struct conv1ch<1> {
465 static inline u16 __attribute__((always_inline)) apply(const u8 *c) {
466 constexpr int S = sizeof(CRGB); // 3
467 const u8 *p = c - S;
468 // [64, 128, 64] (rescaled from [1,2,1] so sum=256, shift-by-8 is free)
469 return (u16)p[0] * 64 + (u16)p[S] * 128 + (u16)p[2*S] * 64;
470 }
471};
472
473template <> struct conv1ch<2> {
474 static inline u16 __attribute__((always_inline)) apply(const u8 *c) {
475 constexpr int S = sizeof(CRGB);
476 const u8 *p = c - 2*S;
477 // [16, 64, 96, 64, 16] (rescaled from [1,4,6,4,1] so sum=256, shift-by-8 is free)
478 return (u16)p[0] * 16 + (u16)p[S] * 64 + (u16)p[2*S] * 96
479 + (u16)p[3*S] * 64 + (u16)p[4*S] * 16;
480 }
481};
482
483template <> struct conv1ch<3> {
484 static inline u16 __attribute__((always_inline)) apply(const u8 *c) {
485 constexpr int S = sizeof(CRGB);
486 const u8 *p = c - 3*S;
487 // [4, 24, 60, 80, 60, 24, 4] (rescaled from [1,6,15,20,15,6,1] so sum=256, shift-by-8 is free)
488 return (u16)p[0] * 4 + (u16)p[S] * 24 + (u16)p[2*S] * 60
489 + (u16)p[3*S] * 80 + (u16)p[4*S] * 60 + (u16)p[5*S] * 24
490 + (u16)p[6*S] * 4;
491 }
492};
493
494template <> struct conv1ch<4> {
495 static inline u16 __attribute__((always_inline)) apply(const u8 *c) {
496 constexpr int S = sizeof(CRGB);
497 const u8 *p = c - 4*S;
498 // [1, 8, 28, 56, 70, 56, 28, 8, 1]
499 return (u16)p[0] + (u16)p[S] * 8 + (u16)p[2*S] * 28
500 + (u16)p[3*S] * 56 + (u16)p[4*S] * 70 + (u16)p[5*S] * 56
501 + (u16)p[6*S] * 28 + (u16)p[7*S] * 8 + (u16)p[8*S];
502 }
503};
504
505// AVR noinline per-channel pass for CRGB (no alpha).
506template <int R>
508static void apply_pass_1ch(const CRGB *pad, CRGB *out, int count, int stride) {
509 constexpr int shift = (R == 0) ? 0 : 8;
510 for (int i = 0; i < count; ++i) {
511 const u8 *base = pad[R + i].raw;
512 out->r = static_cast<u8>(conv1ch<R>::apply(base + 0) >> shift);
513 out->g = static_cast<u8>(conv1ch<R>::apply(base + 1) >> shift);
514 out->b = static_cast<u8>(conv1ch<R>::apply(base + 2) >> shift);
515 out += stride;
516 }
517}
518
519// AVR noinline per-channel pass for CRGB with alpha dim.
520template <int R>
522static void apply_pass_alpha_1ch(const CRGB *pad, CRGB *out, int count,
523 int stride, alpha8 alpha) {
524 constexpr int shift = (R == 0) ? 0 : 8;
525 u16 a1 = static_cast<u16>(alpha.value) + 1;
526 for (int i = 0; i < count; ++i) {
527 const u8 *base = pad[R + i].raw;
528 u16 r = conv1ch<R>::apply(base + 0) >> shift;
529 u16 g = conv1ch<R>::apply(base + 1) >> shift;
530 u16 b = conv1ch<R>::apply(base + 2) >> shift;
531 out->r = static_cast<u8>((r * a1) >> 8);
532 out->g = static_cast<u8>((g * a1) >> 8);
533 out->b = static_cast<u8>((b * a1) >> 8);
534 out += stride;
535 }
536}
537
538// AVR noinline per-channel pass for CRGB with alpha16 dim.
539template <int R>
541static void apply_pass_alpha_1ch(const CRGB *pad, CRGB *out, int count,
542 int stride, alpha16 alpha) {
543 constexpr int shift = (R == 0) ? 0 : 8;
544 u32 a1 = static_cast<u32>(alpha.value) + 1;
545 for (int i = 0; i < count; ++i) {
546 const u8 *base = pad[R + i].raw;
547 u16 r = conv1ch<R>::apply(base + 0) >> shift;
548 u16 g = conv1ch<R>::apply(base + 1) >> shift;
549 u16 b = conv1ch<R>::apply(base + 2) >> shift;
550 out->r = static_cast<u8>((r * a1) >> 16);
551 out->g = static_cast<u8>((g * a1) >> 16);
552 out->b = static_cast<u8>((b * a1) >> 16);
553 out += stride;
554 }
555}
556
557#endif // FL_IS_AVR
558
559// Row-level kernel application — noinline on AVR to isolate register pressure.
560// On non-AVR platforms (or CRGB16 on AVR), processes all 3 channels
561// simultaneously using the interior_row kernel.
562template <int R, typename RGB_T, typename acc_t>
564static void apply_pass(const RGB_T *pad, RGB_T *out, int count, int stride) FL_NO_EXCEPT {
565 constexpr int shift = 2 * R;
566 using P = pixel_ops<RGB_T>;
567 for (int i = 0; i < count; ++i) {
568 acc_t r, g, b;
569 interior_row<R, RGB_T, acc_t>::apply(pad, R + i, r, g, b);
570 *out = P::make(static_cast<acc_t>(r >> shift),
571 static_cast<acc_t>(g >> shift),
572 static_cast<acc_t>(b >> shift));
573 out += stride;
574 }
575}
576
577template <int R, typename RGB_T, typename acc_t, typename AlphaT>
579static void apply_pass_alpha(const RGB_T *pad, RGB_T *out, int count,
580 int stride, AlphaT alpha) FL_NO_EXCEPT {
581 constexpr int shift = 2 * R;
582 using P = pixel_ops<RGB_T>;
583 for (int i = 0; i < count; ++i) {
584 acc_t r, g, b;
585 interior_row<R, RGB_T, acc_t>::apply(pad, R + i, r, g, b);
586 *out = P::make(static_cast<acc_t>(r >> shift),
587 static_cast<acc_t>(g >> shift),
588 static_cast<acc_t>(b >> shift), alpha);
589 out += stride;
590 }
591}
592
593
594// ── Platform-neutral SIMD byte-level convolution kernels ────────────────
595// Process nbytes of output using stride-S byte-level convolution.
596// For horizontal pass: S = sizeof(CRGB) = 3 (neighboring pixels).
597// For vertical pass: S = 1 (same column, consecutive row buffers).
598// The same kernel weights apply to all bytes (R, G, B treated uniformly).
599// Uses fl::simd u8x16/u16x8 operations — compiles to SSE2, NEON, PIE, or scalar.
600#if !defined(FL_IS_AVR)
601
602// Kernel: [1, 2, 1] >> 2 ≈ avg(avg(a, c), b)
603// Uses hardware byte averaging which computes (x+y+1)>>1.
604// Two nested avg ops approximate (a + 2b + c) >> 2 with at most +1 rounding
605// per channel — imperceptible for blur and ~3x fewer SIMD instructions.
606static void simd_conv_121(const u8 * FL_RESTRICT_PARAM a,
607 const u8 * FL_RESTRICT_PARAM b,
608 const u8 * FL_RESTRICT_PARAM c,
609 u8 * FL_RESTRICT_PARAM out, int nbytes) FL_NO_EXCEPT {
610 namespace fsimd = fl::simd; // ok bare using
611 int i = 0;
612 for (; i + 63 < nbytes; i += 64) {
613 auto va0 = fsimd::load_u8_32(a+i), vb0 = fsimd::load_u8_32(b+i), vc0 = fsimd::load_u8_32(c+i);
614 auto va1 = fsimd::load_u8_32(a+i+32), vb1 = fsimd::load_u8_32(b+i+32), vc1 = fsimd::load_u8_32(c+i+32);
615 fsimd::store_u8_32(out+i, fsimd::avg_round_u8_32(fsimd::avg_round_u8_32(va0, vc0), vb0));
616 fsimd::store_u8_32(out+i+32, fsimd::avg_round_u8_32(fsimd::avg_round_u8_32(va1, vc1), vb1));
617 }
618 for (; i + 31 < nbytes; i += 32) {
619 auto va = fsimd::load_u8_32(a+i), vb = fsimd::load_u8_32(b+i), vc = fsimd::load_u8_32(c+i);
620 fsimd::store_u8_32(out+i, fsimd::avg_round_u8_32(fsimd::avg_round_u8_32(va, vc), vb));
621 }
622 for (; i + 15 < nbytes; i += 16) {
623 auto va = fsimd::load_u8_16(a+i), vb = fsimd::load_u8_16(b+i), vc = fsimd::load_u8_16(c+i);
624 fsimd::store_u8_16(out+i, fsimd::avg_round_u8_16(fsimd::avg_round_u8_16(va, vc), vb));
625 }
626 for (; i < nbytes; ++i)
627 out[i] = (u8)(((u16)a[i] + ((u16)b[i] << 1) + (u16)c[i]) >> 2);
628}
629
630// Helper: weighted sum for u16x8 — computes (s_sym + w*s_sym_pair + ... + wc*center) >> shift.
631// Used by R=2, R=3, R=4 kernels for both low and high halves of the 16-byte register.
632
633// Kernel: [1, 4, 6, 4, 1] >> 4
634static void simd_conv_14641(const u8 *p0, const u8 *p1, const u8 *p2,
635 const u8 *p3, const u8 *p4,
636 u8 *out, int nbytes) FL_NO_EXCEPT {
637 namespace fsimd = fl::simd; // ok bare using
638 const auto w4w = fsimd::set1_u16_16(4), w6w = fsimd::set1_u16_16(6);
639 const auto w4 = fsimd::set1_u16_8(4), w6 = fsimd::set1_u16_8(6);
640 int i = 0;
641 for (; i + 31 < nbytes; i += 32) {
642 auto v0 = fsimd::load_u8_32(p0+i), v1 = fsimd::load_u8_32(p1+i);
643 auto v2 = fsimd::load_u8_32(p2+i), v3 = fsimd::load_u8_32(p3+i), v4 = fsimd::load_u8_32(p4+i);
644 auto s04 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v0), fsimd::widen_lo_u8x32_to_u16(v4));
645 auto s13 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v1), fsimd::widen_lo_u8x32_to_u16(v3));
646 auto lo = fsimd::add_u16_16(s04, fsimd::add_u16_16(fsimd::mullo_u16_16(s13, w4w), fsimd::mullo_u16_16(fsimd::widen_lo_u8x32_to_u16(v2), w6w)));
647 lo = fsimd::srli_u16_16(lo, 4);
648 auto s04h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v0), fsimd::widen_hi_u8x32_to_u16(v4));
649 auto s13h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v1), fsimd::widen_hi_u8x32_to_u16(v3));
650 auto hi = fsimd::add_u16_16(s04h, fsimd::add_u16_16(fsimd::mullo_u16_16(s13h, w4w), fsimd::mullo_u16_16(fsimd::widen_hi_u8x32_to_u16(v2), w6w)));
651 hi = fsimd::srli_u16_16(hi, 4);
652 fsimd::store_u8_32(out+i, fsimd::narrow_u16x16_to_u8(lo, hi));
653 }
654 for (; i + 15 < nbytes; i += 16) {
655 auto v0 = fsimd::load_u8_16(p0+i), v1 = fsimd::load_u8_16(p1+i);
656 auto v2 = fsimd::load_u8_16(p2+i), v3 = fsimd::load_u8_16(p3+i), v4 = fsimd::load_u8_16(p4+i);
657 auto s04 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v0), fsimd::widen_lo_u8_to_u16(v4));
658 auto s13 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v1), fsimd::widen_lo_u8_to_u16(v3));
659 auto lo = fsimd::add_u16_8(s04, fsimd::add_u16_8(fsimd::mullo_u16_8(s13, w4), fsimd::mullo_u16_8(fsimd::widen_lo_u8_to_u16(v2), w6)));
660 lo = fsimd::srli_u16_8(lo, 4);
661 auto s04h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v0), fsimd::widen_hi_u8_to_u16(v4));
662 auto s13h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v1), fsimd::widen_hi_u8_to_u16(v3));
663 auto hi = fsimd::add_u16_8(s04h, fsimd::add_u16_8(fsimd::mullo_u16_8(s13h, w4), fsimd::mullo_u16_8(fsimd::widen_hi_u8_to_u16(v2), w6)));
664 hi = fsimd::srli_u16_8(hi, 4);
665 fsimd::store_u8_16(out+i, fsimd::narrow_u16_to_u8(lo, hi));
666 }
667 for (; i < nbytes; ++i) {
668 u16 s04 = (u16)p0[i] + (u16)p4[i];
669 u16 s13 = (u16)p1[i] + (u16)p3[i];
670 out[i] = (u8)((s04 + s13 * 4 + (u16)p2[i] * 6) >> 4);
671 }
672}
673
674// Kernel: [1, 6, 15, 20, 15, 6, 1] >> 6
675static void simd_conv_r3(const u8 *p0, const u8 *p1, const u8 *p2,
676 const u8 *p3, const u8 *p4, const u8 *p5,
677 const u8 *p6, u8 *out, int nbytes) FL_NO_EXCEPT {
678 namespace fsimd = fl::simd; // ok bare using
679 const auto w6w = fsimd::set1_u16_16(6), w15w = fsimd::set1_u16_16(15), w20w = fsimd::set1_u16_16(20);
680 const auto w6 = fsimd::set1_u16_8(6), w15 = fsimd::set1_u16_8(15), w20 = fsimd::set1_u16_8(20);
681 int i = 0;
682 for (; i + 31 < nbytes; i += 32) {
683 auto v0 = fsimd::load_u8_32(p0+i), v1 = fsimd::load_u8_32(p1+i), v2 = fsimd::load_u8_32(p2+i);
684 auto v3 = fsimd::load_u8_32(p3+i), v4 = fsimd::load_u8_32(p4+i), v5 = fsimd::load_u8_32(p5+i);
685 auto v6v = fsimd::load_u8_32(p6+i);
686 auto s06 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v0), fsimd::widen_lo_u8x32_to_u16(v6v));
687 auto s15 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v1), fsimd::widen_lo_u8x32_to_u16(v5));
688 auto s24 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v2), fsimd::widen_lo_u8x32_to_u16(v4));
689 auto lo = fsimd::add_u16_16(s06, fsimd::add_u16_16(fsimd::mullo_u16_16(s15, w6w),
690 fsimd::add_u16_16(fsimd::mullo_u16_16(s24, w15w), fsimd::mullo_u16_16(fsimd::widen_lo_u8x32_to_u16(v3), w20w))));
691 lo = fsimd::srli_u16_16(lo, 6);
692 auto s06h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v0), fsimd::widen_hi_u8x32_to_u16(v6v));
693 auto s15h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v1), fsimd::widen_hi_u8x32_to_u16(v5));
694 auto s24h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v2), fsimd::widen_hi_u8x32_to_u16(v4));
695 auto hi = fsimd::add_u16_16(s06h, fsimd::add_u16_16(fsimd::mullo_u16_16(s15h, w6w),
696 fsimd::add_u16_16(fsimd::mullo_u16_16(s24h, w15w), fsimd::mullo_u16_16(fsimd::widen_hi_u8x32_to_u16(v3), w20w))));
697 hi = fsimd::srli_u16_16(hi, 6);
698 fsimd::store_u8_32(out+i, fsimd::narrow_u16x16_to_u8(lo, hi));
699 }
700 for (; i + 15 < nbytes; i += 16) {
701 auto v0 = fsimd::load_u8_16(p0+i), v1 = fsimd::load_u8_16(p1+i), v2 = fsimd::load_u8_16(p2+i);
702 auto v3 = fsimd::load_u8_16(p3+i), v4 = fsimd::load_u8_16(p4+i), v5 = fsimd::load_u8_16(p5+i);
703 auto v6v = fsimd::load_u8_16(p6+i);
704 auto s06 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v0), fsimd::widen_lo_u8_to_u16(v6v));
705 auto s15 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v1), fsimd::widen_lo_u8_to_u16(v5));
706 auto s24 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v2), fsimd::widen_lo_u8_to_u16(v4));
707 auto lo = fsimd::add_u16_8(s06, fsimd::add_u16_8(fsimd::mullo_u16_8(s15, w6),
708 fsimd::add_u16_8(fsimd::mullo_u16_8(s24, w15), fsimd::mullo_u16_8(fsimd::widen_lo_u8_to_u16(v3), w20))));
709 lo = fsimd::srli_u16_8(lo, 6);
710 auto s06h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v0), fsimd::widen_hi_u8_to_u16(v6v));
711 auto s15h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v1), fsimd::widen_hi_u8_to_u16(v5));
712 auto s24h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v2), fsimd::widen_hi_u8_to_u16(v4));
713 auto hi = fsimd::add_u16_8(s06h, fsimd::add_u16_8(fsimd::mullo_u16_8(s15h, w6),
714 fsimd::add_u16_8(fsimd::mullo_u16_8(s24h, w15), fsimd::mullo_u16_8(fsimd::widen_hi_u8_to_u16(v3), w20))));
715 hi = fsimd::srli_u16_8(hi, 6);
716 fsimd::store_u8_16(out+i, fsimd::narrow_u16_to_u8(lo, hi));
717 }
718 for (; i < nbytes; ++i) {
719 u16 s06=(u16)p0[i]+(u16)p6[i], s15=(u16)p1[i]+(u16)p5[i], s24=(u16)p2[i]+(u16)p4[i];
720 out[i]=(u8)((s06+s15*6+s24*15+(u16)p3[i]*20)>>6);
721 }
722}
723
724// Kernel: [1, 8, 28, 56, 70, 56, 28, 8, 1] >> 8
725static void simd_conv_r4(const u8 *p0, const u8 *p1, const u8 *p2, const u8 *p3,
726 const u8 *p4, const u8 *p5, const u8 *p6, const u8 *p7,
727 const u8 *p8, u8 *out, int nbytes) FL_NO_EXCEPT {
728 namespace fsimd = fl::simd; // ok bare using
729 const auto w8w = fsimd::set1_u16_16(8), w28w = fsimd::set1_u16_16(28);
730 const auto w56w = fsimd::set1_u16_16(56), w70w = fsimd::set1_u16_16(70);
731 const auto w8 = fsimd::set1_u16_8(8), w28 = fsimd::set1_u16_8(28);
732 const auto w56 = fsimd::set1_u16_8(56), w70 = fsimd::set1_u16_8(70);
733 int i = 0;
734 for (; i + 31 < nbytes; i += 32) {
735 auto v0 = fsimd::load_u8_32(p0+i), v1 = fsimd::load_u8_32(p1+i), v2 = fsimd::load_u8_32(p2+i);
736 auto v3 = fsimd::load_u8_32(p3+i), v4 = fsimd::load_u8_32(p4+i), v5 = fsimd::load_u8_32(p5+i);
737 auto v6v = fsimd::load_u8_32(p6+i), v7 = fsimd::load_u8_32(p7+i), v8v = fsimd::load_u8_32(p8+i);
738 auto s08 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v0), fsimd::widen_lo_u8x32_to_u16(v8v));
739 auto s17 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v1), fsimd::widen_lo_u8x32_to_u16(v7));
740 auto s26 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v2), fsimd::widen_lo_u8x32_to_u16(v6v));
741 auto s35 = fsimd::add_u16_16(fsimd::widen_lo_u8x32_to_u16(v3), fsimd::widen_lo_u8x32_to_u16(v5));
742 auto lo = fsimd::add_u16_16(s08, fsimd::add_u16_16(fsimd::mullo_u16_16(s17, w8w),
743 fsimd::add_u16_16(fsimd::mullo_u16_16(s26, w28w),
744 fsimd::add_u16_16(fsimd::mullo_u16_16(s35, w56w), fsimd::mullo_u16_16(fsimd::widen_lo_u8x32_to_u16(v4), w70w)))));
745 lo = fsimd::srli_u16_16(lo, 8);
746 auto s08h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v0), fsimd::widen_hi_u8x32_to_u16(v8v));
747 auto s17h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v1), fsimd::widen_hi_u8x32_to_u16(v7));
748 auto s26h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v2), fsimd::widen_hi_u8x32_to_u16(v6v));
749 auto s35h = fsimd::add_u16_16(fsimd::widen_hi_u8x32_to_u16(v3), fsimd::widen_hi_u8x32_to_u16(v5));
750 auto hi = fsimd::add_u16_16(s08h, fsimd::add_u16_16(fsimd::mullo_u16_16(s17h, w8w),
751 fsimd::add_u16_16(fsimd::mullo_u16_16(s26h, w28w),
752 fsimd::add_u16_16(fsimd::mullo_u16_16(s35h, w56w), fsimd::mullo_u16_16(fsimd::widen_hi_u8x32_to_u16(v4), w70w)))));
753 hi = fsimd::srli_u16_16(hi, 8);
754 fsimd::store_u8_32(out+i, fsimd::narrow_u16x16_to_u8(lo, hi));
755 }
756 for (; i + 15 < nbytes; i += 16) {
757 auto v0 = fsimd::load_u8_16(p0+i), v1 = fsimd::load_u8_16(p1+i), v2 = fsimd::load_u8_16(p2+i);
758 auto v3 = fsimd::load_u8_16(p3+i), v4 = fsimd::load_u8_16(p4+i), v5 = fsimd::load_u8_16(p5+i);
759 auto v6v = fsimd::load_u8_16(p6+i), v7 = fsimd::load_u8_16(p7+i), v8v = fsimd::load_u8_16(p8+i);
760 auto s08 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v0), fsimd::widen_lo_u8_to_u16(v8v));
761 auto s17 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v1), fsimd::widen_lo_u8_to_u16(v7));
762 auto s26 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v2), fsimd::widen_lo_u8_to_u16(v6v));
763 auto s35 = fsimd::add_u16_8(fsimd::widen_lo_u8_to_u16(v3), fsimd::widen_lo_u8_to_u16(v5));
764 auto lo = fsimd::add_u16_8(s08, fsimd::add_u16_8(fsimd::mullo_u16_8(s17, w8),
765 fsimd::add_u16_8(fsimd::mullo_u16_8(s26, w28),
766 fsimd::add_u16_8(fsimd::mullo_u16_8(s35, w56), fsimd::mullo_u16_8(fsimd::widen_lo_u8_to_u16(v4), w70)))));
767 lo = fsimd::srli_u16_8(lo, 8);
768 auto s08h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v0), fsimd::widen_hi_u8_to_u16(v8v));
769 auto s17h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v1), fsimd::widen_hi_u8_to_u16(v7));
770 auto s26h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v2), fsimd::widen_hi_u8_to_u16(v6v));
771 auto s35h = fsimd::add_u16_8(fsimd::widen_hi_u8_to_u16(v3), fsimd::widen_hi_u8_to_u16(v5));
772 auto hi = fsimd::add_u16_8(s08h, fsimd::add_u16_8(fsimd::mullo_u16_8(s17h, w8),
773 fsimd::add_u16_8(fsimd::mullo_u16_8(s26h, w28),
774 fsimd::add_u16_8(fsimd::mullo_u16_8(s35h, w56), fsimd::mullo_u16_8(fsimd::widen_hi_u8_to_u16(v4), w70)))));
775 hi = fsimd::srli_u16_8(hi, 8);
776 fsimd::store_u8_16(out+i, fsimd::narrow_u16_to_u8(lo, hi));
777 }
778 for (; i < nbytes; ++i) {
779 u16 s08=(u16)p0[i]+(u16)p8[i], s17=(u16)p1[i]+(u16)p7[i];
780 u16 s26=(u16)p2[i]+(u16)p6[i], s35=(u16)p3[i]+(u16)p5[i];
781 out[i]=(u8)((s08+s17*8+s26*28+s35*56+(u16)p4[i]*70)>>8);
782 }
783}
784
785// ── Template dispatch: SIMD vertical convolution by radius ─────────────
786// Compile-time selection eliminates runtime if-else chain on R.
787template <int R> struct simd_vconv_dispatch;
788
789template <> struct simd_vconv_dispatch<0> {
790 template <typename RGB_T>
791 static void apply(RGB_T **bufs, const RGB_T **, u8 *out, int nbytes) FL_NO_EXCEPT {
792 FL_BUILTIN_MEMCPY(out, (const u8*)bufs[0], nbytes);
793 }
794};
795
796template <> struct simd_vconv_dispatch<1> {
797 template <typename RGB_T>
798 static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT {
799 simd_conv_121((const u8*)bufs[0], (const u8*)bufs[1],
800 (const u8*)fwd[0], out, nbytes);
801 }
802};
803
804template <> struct simd_vconv_dispatch<2> {
805 template <typename RGB_T>
806 static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT {
807 simd_conv_14641((const u8*)bufs[0], (const u8*)bufs[1],
808 (const u8*)bufs[2], (const u8*)fwd[0],
809 (const u8*)fwd[1], out, nbytes);
810 }
811};
812
813template <> struct simd_vconv_dispatch<3> {
814 template <typename RGB_T>
815 static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT {
816 simd_conv_r3((const u8*)bufs[0], (const u8*)bufs[1],
817 (const u8*)bufs[2], (const u8*)bufs[3],
818 (const u8*)fwd[0], (const u8*)fwd[1],
819 (const u8*)fwd[2], out, nbytes);
820 }
821};
822
823template <> struct simd_vconv_dispatch<4> {
824 template <typename RGB_T>
825 static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT {
826 simd_conv_r4((const u8*)bufs[0], (const u8*)bufs[1],
827 (const u8*)bufs[2], (const u8*)bufs[3],
828 (const u8*)bufs[4], (const u8*)fwd[0],
829 (const u8*)fwd[1], (const u8*)fwd[2],
830 (const u8*)fwd[3], out, nbytes);
831 }
832};
833
834// ── Template dispatch: SIMD horizontal convolution by radius ───────────
835template <int R> struct simd_hconv_dispatch;
836
837template <> struct simd_hconv_dispatch<0> {
838 static void apply(const u8 *pb, int, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT {
839 FL_BUILTIN_MEMCPY(ob, pb, nbytes);
840 }
841};
842
843template <> struct simd_hconv_dispatch<1> {
844 static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT {
845 simd_conv_121(pb, pb + S, pb + 2*S, ob, nbytes);
846 }
847};
848
849template <> struct simd_hconv_dispatch<2> {
850 static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT {
851 // Direct [1,4,6,4,1] kernel — exact u16 multiply+shift, no cascaded
852 // avg_round rounding. Slightly more SIMD ops than cascaded R=1 but
853 // produces bit-exact results matching the scalar interior_row path.
854 simd_conv_14641(pb, pb+S, pb+2*S, pb+3*S, pb+4*S, ob, nbytes);
855 }
856};
857
858template <> struct simd_hconv_dispatch<3> {
859 static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT {
860 simd_conv_r3(pb, pb+S, pb+2*S, pb+3*S, pb+4*S, pb+5*S, pb+6*S, ob, nbytes);
861 }
862};
863
864template <> struct simd_hconv_dispatch<4> {
865 static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT {
866 simd_conv_r4(pb, pb+S, pb+2*S, pb+3*S, pb+4*S, pb+5*S, pb+6*S, pb+7*S, pb+8*S, ob, nbytes);
867 }
868};
869
870// ── Template dispatch: per-pixel vertical convolution by radius ────────
871// Used by the generic path (CRGB16 or alpha case).
872template <int R, typename RGB_T, typename acc_t> struct vpass_pixel_kernel;
873
874template <typename RGB_T, typename acc_t>
875struct vpass_pixel_kernel<0, RGB_T, acc_t> {
876 FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **, int x,
877 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
879 r = p.ch(bufs[0][x].r);
880 g = p.ch(bufs[0][x].g);
881 b = p.ch(bufs[0][x].b);
882 }
883};
884
885template <typename RGB_T, typename acc_t>
886struct vpass_pixel_kernel<1, RGB_T, acc_t> {
887 FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x,
888 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
890 r = (p.ch(bufs[0][x].r) + p.ch(fwd[0][x].r)) + (p.ch(bufs[1][x].r) << 1);
891 g = (p.ch(bufs[0][x].g) + p.ch(fwd[0][x].g)) + (p.ch(bufs[1][x].g) << 1);
892 b = (p.ch(bufs[0][x].b) + p.ch(fwd[0][x].b)) + (p.ch(bufs[1][x].b) << 1);
893 }
894};
895
896template <typename RGB_T, typename acc_t>
897struct vpass_pixel_kernel<2, RGB_T, acc_t> {
898 FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x,
899 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
901 const acc_t sr04 = p.ch(bufs[0][x].r) + p.ch(fwd[1][x].r);
902 const acc_t sg04 = p.ch(bufs[0][x].g) + p.ch(fwd[1][x].g);
903 const acc_t sb04 = p.ch(bufs[0][x].b) + p.ch(fwd[1][x].b);
904 const acc_t sr13 = p.ch(bufs[1][x].r) + p.ch(fwd[0][x].r);
905 const acc_t sg13 = p.ch(bufs[1][x].g) + p.ch(fwd[0][x].g);
906 const acc_t sb13 = p.ch(bufs[1][x].b) + p.ch(fwd[0][x].b);
907 r = sr04 + sr13 * 4 + p.ch(bufs[2][x].r) * 6;
908 g = sg04 + sg13 * 4 + p.ch(bufs[2][x].g) * 6;
909 b = sb04 + sb13 * 4 + p.ch(bufs[2][x].b) * 6;
910 }
911};
912
913template <typename RGB_T, typename acc_t>
914struct vpass_pixel_kernel<3, RGB_T, acc_t> {
915 FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x,
916 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
918 const acc_t sr06 = p.ch(bufs[0][x].r) + p.ch(fwd[2][x].r);
919 const acc_t sg06 = p.ch(bufs[0][x].g) + p.ch(fwd[2][x].g);
920 const acc_t sb06 = p.ch(bufs[0][x].b) + p.ch(fwd[2][x].b);
921 const acc_t sr15 = p.ch(bufs[1][x].r) + p.ch(fwd[1][x].r);
922 const acc_t sg15 = p.ch(bufs[1][x].g) + p.ch(fwd[1][x].g);
923 const acc_t sb15 = p.ch(bufs[1][x].b) + p.ch(fwd[1][x].b);
924 const acc_t sr24 = p.ch(bufs[2][x].r) + p.ch(fwd[0][x].r);
925 const acc_t sg24 = p.ch(bufs[2][x].g) + p.ch(fwd[0][x].g);
926 const acc_t sb24 = p.ch(bufs[2][x].b) + p.ch(fwd[0][x].b);
927 r = sr06 + sr15 * 6 + sr24 * 15 + p.ch(bufs[3][x].r) * 20;
928 g = sg06 + sg15 * 6 + sg24 * 15 + p.ch(bufs[3][x].g) * 20;
929 b = sb06 + sb15 * 6 + sb24 * 15 + p.ch(bufs[3][x].b) * 20;
930 }
931};
932
933template <typename RGB_T, typename acc_t>
934struct vpass_pixel_kernel<4, RGB_T, acc_t> {
935 FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x,
936 acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT {
938 const acc_t sr08 = p.ch(bufs[0][x].r) + p.ch(fwd[3][x].r);
939 const acc_t sg08 = p.ch(bufs[0][x].g) + p.ch(fwd[3][x].g);
940 const acc_t sb08 = p.ch(bufs[0][x].b) + p.ch(fwd[3][x].b);
941 const acc_t sr17 = p.ch(bufs[1][x].r) + p.ch(fwd[2][x].r);
942 const acc_t sg17 = p.ch(bufs[1][x].g) + p.ch(fwd[2][x].g);
943 const acc_t sb17 = p.ch(bufs[1][x].b) + p.ch(fwd[2][x].b);
944 const acc_t sr26 = p.ch(bufs[2][x].r) + p.ch(fwd[1][x].r);
945 const acc_t sg26 = p.ch(bufs[2][x].g) + p.ch(fwd[1][x].g);
946 const acc_t sb26 = p.ch(bufs[2][x].b) + p.ch(fwd[1][x].b);
947 const acc_t sr35 = p.ch(bufs[3][x].r) + p.ch(fwd[0][x].r);
948 const acc_t sg35 = p.ch(bufs[3][x].g) + p.ch(fwd[0][x].g);
949 const acc_t sb35 = p.ch(bufs[3][x].b) + p.ch(fwd[0][x].b);
950 r = sr08 + sr17 * 8 + sr26 * 28 + sr35 * 56 + p.ch(bufs[4][x].r) * 70;
951 g = sg08 + sg17 * 8 + sg26 * 28 + sg35 * 56 + p.ch(bufs[4][x].g) * 70;
952 b = sb08 + sb17 * 8 + sb26 * 28 + sb35 * 56 + p.ch(bufs[4][x].b) * 70;
953 }
954};
955
956#endif // !FL_IS_AVR
957
958// ── Row-major vertical pass (non-AVR) ──────────────────────────────────
959// Processes vertical convolution in row-major order for cache efficiency.
960// Instead of gathering individual columns into a linear buffer (strided
961// reads + writes), iterates row by row with sequential memory access.
962// Uses a ring buffer of R+1 saved rows to hold originals of overwritten rows.
963// scratch must have at least (R+2)*w elements.
964#if !defined(FL_IS_AVR)
965
966template <int R, typename RGB_T, typename acc_t, bool ApplyAlpha, typename AlphaT>
969 RGB_T *pixels, int w, int h,
970 RGB_T *scratch, AlphaT alpha)
972 constexpr int shift = 2 * R;
973 using P = pixel_ops<RGB_T>;
974
975 // Ring buffer: bufs[0..R-1] = saved previous rows, bufs[R] = save slot.
976 // Extra zero_row for bottom-boundary padding.
977 RGB_T *bufs[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};
978 for (int i = 0; i <= R; ++i)
979 bufs[i] = scratch + i * w;
980 RGB_T *zero_row = scratch + (R + 1) * w;
981
982 // Zero all: first R buffers (top-boundary padding) + zero_row.
983 FL_BUILTIN_MEMSET(scratch, 0, (R + 2) * w * sizeof(RGB_T));
984
985 for (int y = 0; y < h; ++y) {
986 RGB_T *out_row = pixels + y * w;
987
988 // Save current row before we overwrite it.
989 FL_BUILTIN_MEMCPY(bufs[R], out_row, w * sizeof(RGB_T));
990
991 // Forward row pointers (rows y+1 .. y+R, or zero_row if OOB).
992 const RGB_T *fwd[4] = {zero_row, zero_row, zero_row, zero_row};
993 for (int k = 0; k < R; ++k)
994 fwd[k] = (y + 1 + k < h) ? (pixels + (y + 1 + k) * w) : zero_row;
995
996 // Prefetch the furthest-ahead row needed by the NEXT iteration.
997 // At row y, next iteration needs fwd[R-1] = row y+1+R.
998 // Prefetching 2 rows ahead gives the memory subsystem time to fetch.
999 {
1000 const int prefetch_y = y + R + 2;
1001 if (prefetch_y < h) {
1002 const char *pf = (const char *)(pixels + prefetch_y * w);
1003 const int row_bytes = w * (int)sizeof(RGB_T);
1004 for (int off = 0; off < row_bytes; off += 64)
1005 __builtin_prefetch(pf + off, 0, 3);
1006 }
1007 }
1008
1009 // Process all pixels in this output row.
1010 // For u8-channel types (CRGB), process as raw byte stream — all
1011 // channels use the same kernel weights, so we treat the row as a
1012 // flat u8 array of w*sizeof(RGB_T) bytes. This produces a simpler
1013 // loop that the compiler can optimize better at low -O levels.
1014 if (sizeof(typename RGB_T::fp) == 1 && !ApplyAlpha) {
1015 // Raw byte fast path (CRGB without alpha).
1016 const int nbytes = w * (int)sizeof(RGB_T);
1017 u8 *ob = (u8 *)out_row;
1018
1019 simd_vconv_dispatch<R>::apply(bufs, fwd, ob, nbytes);
1020 } else {
1021 // Generic path: per-pixel struct access (CRGB16 or alpha case).
1022 for (int x = 0; x < w; ++x) {
1023 acc_t r, g, b;
1024
1026
1027 if (ApplyAlpha) {
1028 out_row[x] = P::make(static_cast<acc_t>(r >> shift),
1029 static_cast<acc_t>(g >> shift),
1030 static_cast<acc_t>(b >> shift), alpha);
1031 } else {
1032 out_row[x] = P::make(static_cast<acc_t>(r >> shift),
1033 static_cast<acc_t>(g >> shift),
1034 static_cast<acc_t>(b >> shift));
1035 }
1036 }
1037 }
1038
1039 // Rotate ring buffer: discard oldest, current becomes newest saved.
1040 RGB_T *recycled = bufs[0];
1041 for (int i = 0; i < R; ++i) bufs[i] = bufs[i + 1];
1042 bufs[R] = recycled;
1043 }
1044}
1045
1046#endif // !FL_IS_AVR
1047
1048// ── Helper: pad buffer size calculation ─────────────────────────────────
1049template <int hR, int vR, typename RGB_T>
1050static int compute_pad_size(int w, int h) FL_NO_EXCEPT {
1051 int hPad = 2 * hR + w;
1052#if defined(FL_IS_AVR)
1053 int vPad = 2 * vR + h;
1054#else
1055 int vPad = vR > 0 ? (vR + 2) * w : 0;
1056#endif
1057 return hPad > vPad ? hPad : vPad;
1058}
1059
1060// ── Helper: horizontal pass for one padded row ─────────────────────────
1061// Dispatches to the appropriate kernel based on platform and pixel type.
1062template <int R, typename RGB_T, typename acc_t, bool ApplyAlpha, typename AlphaT>
1064void hpass_row(RGB_T *pad, RGB_T *out, int w, AlphaT alpha) FL_NO_EXCEPT {
1065#if defined(FL_IS_AVR)
1066 if (ApplyAlpha)
1067 apply_pass_alpha_1ch<R>(pad, out, w, 1, alpha);
1068 else
1069 apply_pass_1ch<R>(pad, out, w, 1);
1070#else
1071 // SIMD fast path: u8-channel (CRGB), no alpha on this pass.
1072 if (sizeof(typename RGB_T::fp) == 1 && !ApplyAlpha) {
1073 constexpr int S = (int)sizeof(RGB_T);
1074 const int nbytes = w * S;
1075 const u8 *pb = (const u8 *)pad;
1076 u8 *ob = (u8 *)out;
1078 pb, S, ob, nbytes, (u8 *)(pad + 2 * R + w), w);
1079 } else if (ApplyAlpha) {
1080 apply_pass_alpha<R, RGB_T, acc_t>(pad, out, w, 1, alpha);
1081 } else {
1082 apply_pass<R, RGB_T, acc_t>(pad, out, w, 1);
1083 }
1084#endif
1085}
1086
1087// ── Helper: vertical pass over entire image ─────────────────────────────
1088template <int R, typename RGB_T, typename acc_t, bool ApplyAlpha, typename AlphaT>
1089static void vpass_full(RGB_T *pixels, int w, int h, RGB_T *scratch, AlphaT alpha) FL_NO_EXCEPT {
1090#if defined(FL_IS_AVR)
1091 // AVR: column-by-column with per-channel noinline + O3.
1092 FL_BUILTIN_MEMSET(scratch, 0, R * sizeof(RGB_T));
1093 FL_BUILTIN_MEMSET(scratch + R + h, 0, R * sizeof(RGB_T));
1094
1095 for (int x = 0; x < w; ++x) {
1096 // Linearize column into padded region.
1097 {
1098 const RGB_T *src = pixels + x;
1099 RGB_T *dst = scratch + R;
1100 for (int i = 0; i < h; ++i) {
1101 *dst++ = *src;
1102 src += w;
1103 }
1104 }
1105 if (ApplyAlpha)
1106 apply_pass_alpha_1ch<R>(scratch, pixels + x, h, w, alpha);
1107 else
1108 apply_pass_1ch<R>(scratch, pixels + x, h, w);
1109 }
1110#else
1111 // Non-AVR: row-major vertical pass for cache efficiency.
1112 // Direct R=2 V-pass using exact [1,4,6,4,1] kernel (no cascaded R=1).
1113 if (ApplyAlpha) {
1115 pixels, w, h, scratch, alpha);
1116 } else {
1118 pixels, w, h, scratch, alpha);
1119 }
1120#endif
1121}
1122
1123} // namespace blur_detail
1124
1125// Separable Gaussian blur: horizontal pass then vertical pass.
1126// Zero-padding approach: copy row/column to a padded buffer with zeros,
1127// then apply the fast unrolled interior_row kernel to ALL positions.
1128// This eliminates slow edge handling and reuses interior_row for both passes.
1129// Dim (alpha) is applied once at the final output.
1130template <int hRadius, int vRadius, typename RGB_T, typename AlphaT>
1132void blurGaussianImpl(Canvas<RGB_T> &canvas, AlphaT alpha) FL_NO_EXCEPT {
1133 const int w = canvas.width;
1134 const int h = canvas.height;
1135 if (w <= 0 || h <= 0)
1136 return;
1137
1139
1140 // Accumulator: u16 for 8-bit channels (max per-pass sum: 255*256=65280),
1141 // u32 for wider channels.
1142 using acc_t = fl::conditional_t<sizeof(typename RGB_T::fp) == 1, u16, u32>;
1143
1144 const bool applyAlpha = !(alpha == blur_detail::alpha_identity<AlphaT>());
1145
1146 // Handle no-blur case (radius 0 in both dimensions).
1147 if (hRadius == 0 && vRadius == 0) {
1148 if (applyAlpha) {
1149 RGB_T *pixels = canvas.pixels;
1150 for (int i = 0; i < w * h; ++i) {
1151 RGB_T &p = pixels[i];
1152 p = P::make(P::ch(p.r), P::ch(p.g), P::ch(p.b), alpha);
1153 }
1154 }
1155 return;
1156 }
1157
1160 RGB_T *pad = padbuf.data();
1161 RGB_T *pixels = canvas.pixels;
1162
1163 // ── Horizontal pass ──────────────────────────────────────────────
1164 if (hRadius > 0) {
1165 // Zero the fixed padding regions once (reused for every row).
1166 FL_BUILTIN_MEMSET(pad, 0, hRadius * sizeof(RGB_T));
1167 FL_BUILTIN_MEMSET(pad + hRadius + w, 0, hRadius * sizeof(RGB_T));
1168
1169 for (int y = 0; y < h; ++y) {
1170 RGB_T *row = pixels + y * w;
1171 FL_BUILTIN_MEMCPY(pad + hRadius, row, w * sizeof(RGB_T));
1172
1173 if (vRadius == 0 && applyAlpha)
1175 pad, row, w, alpha);
1176 else
1178 pad, row, w, alpha);
1179 }
1180 }
1181
1182 // ── Vertical pass ──────────────────────────────────────────────────
1183 if (vRadius > 0) {
1184 if (applyAlpha)
1186 pixels, w, h, pad, alpha);
1187 else
1189 pixels, w, h, pad, alpha);
1190 }
1191}
1192
1193// ── alpha8 overload (UNORM8 dim) ─────────────────────────────────────────
1194
1195template <int hRadius, int vRadius, typename RGB_T>
1197 blurGaussianImpl<hRadius, vRadius>(canvas, dimFactor);
1198}
1199
1200// ── alpha16 overload (UNORM16 dim — true 16-bit precision) ───────────────
1201
1202template <int hRadius, int vRadius, typename RGB_T>
1204 blurGaussianImpl<hRadius, vRadius>(canvas, dimFactor);
1205}
1206
1207// ── CanvasMapped: XYMap-based Gaussian blur ──────────────────────────────
1208// Delegates to the optimized Canvas path when the XYMap is rectangular.
1209// Falls back to per-pixel gather/scatter through XYMap otherwise.
1210template <int hRadius, int vRadius, typename RGB_T, typename AlphaT>
1213 const int w = canvas.width;
1214 const int h = canvas.height;
1215 if (w <= 0 || h <= 0)
1216 return;
1217
1218 // Fast path: rectangular XYMap → delegate to optimized Canvas blur.
1219 if (canvas.xymap->isRectangularGrid()) {
1220 Canvas<RGB_T> rect(canvas.pixels, w, h);
1222 return;
1223 }
1224
1225 // Slow path: non-rectangular XYMap → per-pixel gather/scatter.
1227 using acc_t = fl::conditional_t<sizeof(typename RGB_T::fp) == 1, u16, u32>;
1228
1229 const bool applyAlpha = !(alpha == blur_detail::alpha_identity<AlphaT>());
1230
1231 // Handle no-blur case.
1232 if (hRadius == 0 && vRadius == 0) {
1233 if (applyAlpha) {
1234 for (int y = 0; y < h; ++y) {
1235 for (int x = 0; x < w; ++x) {
1236 RGB_T &p = canvas.at(x, y);
1237 P ops;
1238 p = ops.make(ops.ch(p.r), ops.ch(p.g), ops.ch(p.b), alpha);
1239 }
1240 }
1241 }
1242 return;
1243 }
1244
1247 RGB_T *pad = padbuf.data();
1248
1249 // ── Horizontal pass: gather row via XYMap, convolve, scatter back ──
1250 if (hRadius > 0) {
1251 FL_BUILTIN_MEMSET(pad, 0, hRadius * sizeof(RGB_T));
1252 FL_BUILTIN_MEMSET(pad + hRadius + w, 0, hRadius * sizeof(RGB_T));
1253
1254 for (int y = 0; y < h; ++y) {
1255 for (int x = 0; x < w; ++x)
1256 pad[hRadius + x] = canvas.at(x, y);
1257
1258 constexpr int shift = 2 * hRadius;
1259 for (int x = 0; x < w; ++x) {
1260 acc_t r, g, b;
1262 RGB_T result;
1263 if (vRadius == 0 && applyAlpha)
1264 result = P().make(static_cast<acc_t>(r >> shift),
1265 static_cast<acc_t>(g >> shift),
1266 static_cast<acc_t>(b >> shift), alpha);
1267 else
1268 result = P().make(static_cast<acc_t>(r >> shift),
1269 static_cast<acc_t>(g >> shift),
1270 static_cast<acc_t>(b >> shift));
1271 canvas.at(x, y) = result;
1272 }
1273 }
1274 }
1275
1276 // ── Vertical pass: gather column via XYMap, convolve, scatter back ──
1277 if (vRadius > 0) {
1278 FL_BUILTIN_MEMSET(pad, 0, vRadius * sizeof(RGB_T));
1279 FL_BUILTIN_MEMSET(pad + vRadius + h, 0, vRadius * sizeof(RGB_T));
1280
1281 for (int x = 0; x < w; ++x) {
1282 for (int y = 0; y < h; ++y)
1283 pad[vRadius + y] = canvas.at(x, y);
1284
1285 constexpr int shift = 2 * vRadius;
1286 for (int y = 0; y < h; ++y) {
1287 acc_t r, g, b;
1289 RGB_T result;
1290 if (applyAlpha)
1291 result = P().make(static_cast<acc_t>(r >> shift),
1292 static_cast<acc_t>(g >> shift),
1293 static_cast<acc_t>(b >> shift), alpha);
1294 else
1295 result = P().make(static_cast<acc_t>(r >> shift),
1296 static_cast<acc_t>(g >> shift),
1297 static_cast<acc_t>(b >> shift));
1298 canvas.at(x, y) = result;
1299 }
1300 }
1301 }
1302}
1303
1304// ── CanvasMapped alpha8 overload ─────────────────────────────────────────
1305
1306template <int hRadius, int vRadius, typename RGB_T>
1309}
1310
1311// ── CanvasMapped alpha16 overload ────────────────────────────────────────
1312
1313template <int hRadius, int vRadius, typename RGB_T>
1317
1318// ── Explicit instantiations: alpha8 overload ─────────────────────────────
1319
1320#define BLUR_INST_F8(H, V, T) \
1321 template void blurGaussian<H, V, T>(Canvas<T> &, alpha8);
1322
1323// CRGB — symmetric, h-only, v-only, asymmetric.
1331
1332// CRGB16 — same combos (not available on AVR due to RAM constraints).
1333#if !defined(FL_IS_AVR)
1341#endif
1342
1343#undef BLUR_INST_F8
1344
1345// ── Explicit instantiations: alpha16 overload ────────────────────────────
1346
1347#define BLUR_INST_F16(H, V, T) \
1348 template void blurGaussian<H, V, T>(Canvas<T> &, alpha16);
1349
1350// CRGB
1358
1359// CRGB16
1360#if !defined(FL_IS_AVR)
1368#endif
1369
1370#undef BLUR_INST_F16
1371
1372// ── Explicit instantiations: CanvasMapped alpha8 ─────────────────────────
1373
1374#define BLUR_MAPPED_INST_F8(H, V, T) \
1375 template void blurGaussian<H, V, T>(CanvasMapped<T> &, alpha8);
1376
1384
1385#if !defined(FL_IS_AVR)
1393#endif
1394
1395#undef BLUR_MAPPED_INST_F8
1396
1397// ── Explicit instantiations: CanvasMapped alpha16 ────────────────────────
1398
1399#define BLUR_MAPPED_INST_F16(H, V, T) \
1400 template void blurGaussian<H, V, T>(CanvasMapped<T> &, alpha16);
1401
1409
1410#if !defined(FL_IS_AVR)
1418#endif
1419
1420#undef BLUR_MAPPED_INST_F16
1421
1422} // namespace gfx
1423} // namespace fl
1424
CRGB leds[1]
fl::XYMap xyMap
XYMap xymap
int y
Definition simple.h:93
int x
Definition simple.h:92
FL_OPTIMIZATION_LEVEL_O3_BEGIN fl::u16 XY(fl::u8 x, fl::u8 y) FL_NO_EXCEPT FL_LINK_WEAK
Definition blur.cpp.hpp:36
static XYMap constructWithUserFunction(u16 width, u16 height, XYFunction xyFunction, u16 offset=0) FL_NO_EXCEPT
Definition xymap.cpp.hpp:28
const T * data() const FL_NO_EXCEPT
Definition span.h:486
static constexpr FASTLED_FORCE_INLINE u8x8 from_raw(u16 raw) FL_NO_EXCEPT
Definition u8x8.h:53
fl::size size() const FL_NO_EXCEPT
void resize(fl::size n) FL_NO_EXCEPT
Definition vector.h:593
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN
#define FL_ALWAYS_INLINE
#define FL_BUILTIN_MEMCPY(dest, src, n)
#define FL_NO_INLINE
#define FL_NO_INLINE_IF_AVR
#define FL_OPTIMIZATION_LEVEL_O3_END
#define FASTLED_UNUSED(x)
#define FL_OPTIMIZE_FUNCTION
#define FL_BUILTIN_MEMSET(dest, val, n)
#define FL_RESTRICT_PARAM
#define FL_LINK_WEAK
#define constexpr
Declares that it is possible to evaluate a value at compile time, introduced in C++11.
Definition cpp_compat.h:15
Defines the 8-bit red, green, and blue (RGB) pixel type in the fl namespace.
Legacy compatibility header for 8-bit scaling functions.
Internal FastLED header for implementation files.
void blur1d(fl::span< CRGB > leds, fract8 blur_amount) FL_NO_EXCEPT
One-dimensional blur filter (span version).
Definition blur.cpp.hpp:70
void blur2d(fl::span< CRGB > leds, fl::u8 width, fl::u8 height, fract8 blur_amount, const XYMap &xymap) FL_NO_EXCEPT
Two-dimensional blur filter (span version).
Definition blur.cpp.hpp:88
void blurColumns(fl::span< CRGB > leds, fl::u8 width, fl::u8 height, fract8 blur_amount, const XYMap &xyMap) FL_NO_EXCEPT
Perform a blur1d() on every column of a rectangular matrix (span version).
Definition blur.cpp.hpp:173
void blurRows(fl::span< CRGB > leds, fl::u8 width, fl::u8 height, fract8 blur_amount, const XYMap &xyMap) FL_NO_EXCEPT
Perform a blur1d() on every row of a rectangular matrix (span version).
Definition blur.cpp.hpp:134
fl::CRGB CRGB
Definition crgb.h:25
#define FL_ERROR(...)
Definition log.h:373
Centralized logging categories for FastLED hardware interfaces and subsystems.
fl::u16 xy_legacy_wrapper(fl::u16 x, fl::u16 y, fl::u16 width, fl::u16 height) FL_NO_EXCEPT
Definition blur.cpp.hpp:47
static void simd_conv_14641(const u8 *p0, const u8 *p1, const u8 *p2, const u8 *p3, const u8 *p4, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:634
static void vpass_full(RGB_T *pixels, int w, int h, RGB_T *scratch, AlphaT alpha) FL_NO_EXCEPT
constexpr alpha16 alpha_identity< alpha16 >() FL_NO_EXCEPT
Definition blur.cpp.hpp:291
static void simd_conv_r4(const u8 *p0, const u8 *p1, const u8 *p2, const u8 *p3, const u8 *p4, const u8 *p5, const u8 *p6, const u8 *p7, const u8 *p8, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:725
static fl::span< RGB_T > get_padbuf(int minSize) FL_NO_EXCEPT
Definition blur.cpp.hpp:350
static void simd_conv_r3(const u8 *p0, const u8 *p1, const u8 *p2, const u8 *p3, const u8 *p4, const u8 *p5, const u8 *p6, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:675
FL_ALWAYS_INLINE void hpass_row(RGB_T *pad, RGB_T *out, int w, AlphaT alpha) FL_NO_EXCEPT
FL_NO_INLINE_IF_AVR static FL_OPTIMIZE_FUNCTION void apply_pass_alpha(const RGB_T *pad, RGB_T *out, int count, int stride, AlphaT alpha) FL_NO_EXCEPT
Definition blur.cpp.hpp:579
static int compute_pad_size(int w, int h) FL_NO_EXCEPT
FL_NO_INLINE_IF_AVR static FL_OPTIMIZE_FUNCTION void apply_pass(const RGB_T *pad, RGB_T *out, int count, int stride) FL_NO_EXCEPT
Definition blur.cpp.hpp:564
static FL_OPTIMIZE_FUNCTION void vpass_rowmajor_impl(RGB_T *pixels, int w, int h, RGB_T *scratch, AlphaT alpha) FL_NO_EXCEPT
Definition blur.cpp.hpp:968
constexpr AlphaT alpha_identity() FL_NO_EXCEPT
static void simd_conv_121(const u8 *FL_RESTRICT_PARAM a, const u8 *FL_RESTRICT_PARAM b, const u8 *FL_RESTRICT_PARAM c, u8 *FL_RESTRICT_PARAM out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:606
FL_OPTIMIZE_FUNCTION void blurGaussianImpl(Canvas< RGB_T > &canvas, AlphaT alpha) FL_NO_EXCEPT
CRGB BLUR_INST_F8(2, 2, CRGB) BLUR_INST_F8(3
void blurGaussian(Canvas< RGB_T > &canvas, alpha8 dimFactor) FL_NO_EXCEPT
Compile-time Gaussian blur with independent H/V radii.
FL_OPTIMIZE_FUNCTION void blurGaussianMappedImpl(CanvasMapped< RGB_T > &canvas, AlphaT alpha) FL_NO_EXCEPT
CRGB BLUR_INST_F16(2, 2, CRGB) BLUR_INST_F16(3
CRGB BLUR_MAPPED_INST_F16(2, 2, CRGB) BLUR_MAPPED_INST_F16(3
CRGB BLUR_MAPPED_INST_F8(2, 2, CRGB) BLUR_MAPPED_INST_F8(3
unsigned char u8
Definition stdint.h:131
fl::CRGB CRGB
Definition video.h:18
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
typename conditional< B, T, F >::type conditional_t
FASTLED_FORCE_INLINE fl::u8 P(fl::u8 x) FL_NO_EXCEPT
u8 u8 height
Definition blur.h:186
u8 fract8
Fixed-Point Fractional Types.
Definition stdint.h:160
u8 width
Definition blur.h:186
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
InputGamut g
Definition rgbw.h:125
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NO_EXCEPT
Umbrella header for SIMD subsystem.
CRGB & nscale8(u8 scaledown) FL_NO_EXCEPT
Scale down a RGB to N/256ths of its current brightness, using "plain math" dimming rules.
Definition crgb.cpp.hpp:87
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:517
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
Unsigned 16-bit alpha / brightness — UNORM16.
Definition alpha.h:87
Unsigned 8-bit alpha / brightness — UNORM8.
Definition alpha.h:42
Simple rectangular canvas for graphics operations Combines a pixel buffer with dimensions for cache-o...
Definition canvas.h:66
XYMap-backed canvas for non-rectangular or remapped layouts.
Definition canvas.h:130
FL_ALWAYS_INLINE void apply(const RGB_T *row, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:366
FL_ALWAYS_INLINE void apply(const RGB_T *row, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:375
FL_ALWAYS_INLINE void apply(const RGB_T *row, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:388
FL_ALWAYS_INLINE void apply(const RGB_T *row, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:405
FL_ALWAYS_INLINE void apply(const RGB_T *row, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:423
FL_ALWAYS_INLINE CRGB16 make(u32 r, u32 g, u32 b) FL_NO_EXCEPT
Definition blur.cpp.hpp:329
FL_ALWAYS_INLINE CRGB16 make(u32 r, u32 g, u32 b, alpha16 a) FL_NO_EXCEPT
Definition blur.cpp.hpp:341
FL_ALWAYS_INLINE CRGB16 make(u32 r, u32 g, u32 b, alpha8 a) FL_NO_EXCEPT
Definition blur.cpp.hpp:335
FL_ALWAYS_INLINE CRGB16 zero() FL_NO_EXCEPT
Definition blur.cpp.hpp:327
FL_ALWAYS_INLINE u32 ch(u8x8 v) FL_NO_EXCEPT
Definition blur.cpp.hpp:326
FL_ALWAYS_INLINE CRGB zero() FL_NO_EXCEPT
Definition blur.cpp.hpp:300
FL_ALWAYS_INLINE CRGB make(u16 r, u16 g, u16 b) FL_NO_EXCEPT
Definition blur.cpp.hpp:302
FL_ALWAYS_INLINE CRGB make(u16 r, u16 g, u16 b, alpha8 a) FL_NO_EXCEPT
Definition blur.cpp.hpp:307
FL_ALWAYS_INLINE u16 ch(u8 v) FL_NO_EXCEPT
Definition blur.cpp.hpp:299
FL_ALWAYS_INLINE CRGB make(u16 r, u16 g, u16 b, alpha16 a) FL_NO_EXCEPT
Definition blur.cpp.hpp:315
static void apply(const u8 *pb, int, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT
Definition blur.cpp.hpp:838
static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT
Definition blur.cpp.hpp:844
static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT
Definition blur.cpp.hpp:850
static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT
Definition blur.cpp.hpp:859
static void apply(const u8 *pb, int S, u8 *ob, int nbytes, u8 *, int) FL_NO_EXCEPT
Definition blur.cpp.hpp:865
static void apply(RGB_T **bufs, const RGB_T **, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:791
static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:798
static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:806
static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:815
static void apply(RGB_T **bufs, const RGB_T **fwd, u8 *out, int nbytes) FL_NO_EXCEPT
Definition blur.cpp.hpp:825
FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:876
FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:887
FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:898
FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:915
FL_ALWAYS_INLINE void apply(RGB_T **bufs, const RGB_T **fwd, int x, acc_t &r, acc_t &g, acc_t &b) FL_NO_EXCEPT
Definition blur.cpp.hpp:935