Const and 2D box blur

- added 2D blur
This commit is contained in:
Blaz Kristan 2024-08-14 22:15:48 +02:00
parent db5e66a9b0
commit cec67d8eff
4 changed files with 204 additions and 114 deletions

View File

@ -1226,15 +1226,18 @@ uint16_t mode_fireworks() {
} }
SEGMENT.fade_out(128); SEGMENT.fade_out(128);
uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte
if (!SEGENV.step) {
// fireworks mode (blur flares)
bool valid1 = (SEGENV.aux0 < width*height); bool valid1 = (SEGENV.aux0 < width*height);
bool valid2 = (SEGENV.aux1 < width*height); bool valid2 = (SEGENV.aux1 < width*height);
uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte
uint32_t sv1 = 0, sv2 = 0; uint32_t sv1 = 0, sv2 = 0;
if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color
if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1);
if (!SEGENV.step) SEGMENT.blur(16); SEGMENT.blur(16); // used in mode_rain()
if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur
if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur
}
for (int i=0; i<max(1, width/20); i++) { for (int i=0; i<max(1, width/20); i++) {
if (random8(129 - (SEGMENT.intensity >> 1)) == 0) { if (random8(129 - (SEGMENT.intensity >> 1)) == 0) {
@ -1261,7 +1264,7 @@ uint16_t mode_rain() {
SEGENV.step += FRAMETIME; SEGENV.step += FRAMETIME;
if (SEGENV.call && SEGENV.step > SPEED_FORMULA_L) { if (SEGENV.call && SEGENV.step > SPEED_FORMULA_L) {
SEGENV.step = 1; SEGENV.step = 1;
if (strip.isMatrix) { if (SEGMENT.is2D()) {
//uint32_t ctemp[width]; //uint32_t ctemp[width];
//for (int i = 0; i<width; i++) ctemp[i] = SEGMENT.getPixelColorXY(i, height-1); //for (int i = 0; i<width; i++) ctemp[i] = SEGMENT.getPixelColorXY(i, height-1);
SEGMENT.move(6, 1, true); // move all pixels down SEGMENT.move(6, 1, true); // move all pixels down
@ -3651,7 +3654,7 @@ uint16_t mode_exploding_fireworks(void)
else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c.red, c.green, c.blue); else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c.red, c.green, c.blue);
} }
} }
SEGMENT.blur(16); if (SEGMENT.check3) SEGMENT.blur(16);
*dying_gravity *= .8f; // as sparks burn out they fall slower *dying_gravity *= .8f; // as sparks burn out they fall slower
} else { } else {
SEGENV.aux0 = 6 + random8(10); //wait for this many frames SEGENV.aux0 = 6 + random8(10); //wait for this many frames
@ -3666,7 +3669,7 @@ uint16_t mode_exploding_fireworks(void)
return FRAMETIME; return FRAMETIME;
} }
#undef MAX_SPARKS #undef MAX_SPARKS
static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!;!;12;pal=11,ix=128"; static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side,,,,,,Blur;!,!;!;12;pal=11,ix=128";
/* /*
@ -4890,11 +4893,11 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma
// central white dot // central white dot
SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE);
// blur everything a bit // blur everything a bit
SEGMENT.blur(cols*rows > 100 ? 16 : 0); if (SEGMENT.check3) SEGMENT.blur(16, cols*rows < 100);
return FRAMETIME; return FRAMETIME;
} // mode_2DBlackHole() } // mode_2DBlackHole()
static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.,Solid;!;!;2;pal=11"; static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.,Solid,,Blur;!;!;2;pal=11";
//////////////////////////// ////////////////////////////
@ -5636,7 +5639,7 @@ uint16_t mode_2DPulser(void) { // By: ldirko https://edi
int y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); int y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0);
SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND));
SEGMENT.blur(1 + (SEGMENT.intensity>>4)); SEGMENT.blur(SEGMENT.intensity>>4);
return FRAMETIME; return FRAMETIME;
} // mode_2DPulser() } // mode_2DPulser()
@ -6219,7 +6222,7 @@ uint16_t mode_2Ddriftrose(void) {
uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f;
SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255));
} }
SEGMENT.blur((SEGMENT.intensity>>4)+1); SEGMENT.blur(SEGMENT.intensity>>4);
return FRAMETIME; return FRAMETIME;
} }
@ -6475,11 +6478,11 @@ uint16_t mode_2DWaverly(void) {
SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND));
} }
} }
SEGMENT.blur(cols*rows > 100 ? 16 : 0); if (SEGMENT.check3) SEGMENT.blur(16, cols*rows < 100);
return FRAMETIME; return FRAMETIME;
} // mode_2DWaverly() } // mode_2DWaverly()
static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sensitivity;;!;2v;ix=64,si=0"; // Beatsin static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sensitivity,,,,,Blur;;!;2v;ix=64,si=0"; // Beatsin
#endif // WLED_DISABLE_2D #endif // WLED_DISABLE_2D

View File

@ -636,7 +636,8 @@ typedef struct Segment {
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); }
void box_blur(uint16_t i, bool vertical, fract8 blur_amount); // 1D box blur (with weight) void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur
void blur2D(uint8_t blur_amount, bool smear = false);
void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); void blurRow(uint32_t row, fract8 blur_amount, bool smear = false);
void blurCol(uint32_t col, fract8 blur_amount, bool smear = false); void blurCol(uint32_t col, fract8 blur_amount, bool smear = false);
void moveX(int8_t delta, bool wrap = false); void moveX(int8_t delta, bool wrap = false);
@ -666,14 +667,15 @@ typedef struct Segment {
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); }
#endif #endif
inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); }
inline void box_blur(uint16_t i, bool vertical, fract8 blur_amount) {} inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {}
inline void blur2D(uint8_t blur_amount, bool smear = false) {}
inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {} inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {}
inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {} inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {}
inline void moveX(int8_t delta, bool wrap = false) {} inline void moveX(int8_t delta, bool wrap = false) {}
@ -800,56 +802,55 @@ class WS2812FX { // 96 bytes
bool bool
paletteFade, paletteFade,
checkSegmentAlignment(void), checkSegmentAlignment(void),
hasRGBWBus(void), hasRGBWBus(void) const,
hasCCTBus(void), hasCCTBus(void) const,
// return true if the strip is being sent pixel updates isUpdating(void) const, // return true if the strip is being sent pixel updates
isUpdating(void),
deserializeMap(uint8_t n=0); deserializeMap(uint8_t n=0);
inline bool isServicing(void) { return _isServicing; } // returns true if strip.service() is executing inline bool isServicing(void) const { return _isServicing; } // returns true if strip.service() is executing
inline bool hasWhiteChannel(void) { return _hasWhiteChannel; } // returns true if strip contains separate white chanel inline bool hasWhiteChannel(void) const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel
inline bool isOffRefreshRequired(void) { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) inline bool isOffRefreshRequired(void) const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset)
inline bool isSuspended(void) { return _suspend; } // returns true if strip.service() execution is suspended inline bool isSuspended(void) const { return _suspend; } // returns true if strip.service() execution is suspended
inline bool needsUpdate(void) { return _triggered; } // returns true if strip received a trigger() request inline bool needsUpdate(void) const { return _triggered; } // returns true if strip received a trigger() request
uint8_t uint8_t
paletteBlend, paletteBlend,
cctBlending, cctBlending,
getActiveSegmentsNum(void), getActiveSegmentsNum(void) const,
getFirstSelectedSegId(void), getFirstSelectedSegId(void) const,
getLastActiveSegmentId(void), getLastActiveSegmentId(void) const,
getActiveSegsLightCapabilities(bool selectedOnly = false); getActiveSegsLightCapabilities(bool selectedOnly = false) const;
inline uint8_t getBrightness(void) { return _brightness; } // returns current strip brightness inline uint8_t getBrightness(void) const { return _brightness; } // returns current strip brightness
inline uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline uint8_t getMaxSegments(void) const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments inline uint8_t getSegmentsNum(void) const { return _segments.size(); } // returns currently present segments
inline uint8_t getCurrSegmentId(void) { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) inline uint8_t getCurrSegmentId(void) const { return _segment_index; } // returns current segment index (only valid while strip.isServicing())
inline uint8_t getMainSegmentId(void) { return _mainSegment; } // returns main segment index inline uint8_t getMainSegmentId(void) const { return _mainSegment; } // returns main segment index
inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); }
inline uint8_t getTargetFps() { return _targetFps; } // returns rough FPS value for las 2s interval inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval
inline uint8_t getModeCount() { return _modeCount; } // returns number of registered modes/effects inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects
uint16_t uint16_t
getLengthPhysical(void), getLengthPhysical(void) const,
getLengthTotal(void), // will include virtual/nonexistent pixels in matrix getLengthTotal(void) const, // will include virtual/nonexistent pixels in matrix
getFps(), getFps() const,
getMappedPixelIndex(uint16_t index); getMappedPixelIndex(uint16_t index) const;
inline uint16_t getFrameTime(void) { return _frametime; } // returns amount of time a frame should take (in ms) inline uint16_t getFrameTime(void) const { return _frametime; } // returns amount of time a frame should take (in ms)
inline uint16_t getMinShowDelay(void) { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getMinShowDelay(void) const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getLength(void) { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getLength(void) const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H)
inline uint16_t getTransition(void) { return _transitionDur; } // returns currently set transition time (in ms) inline uint16_t getTransition(void) const { return _transitionDur; } // returns currently set transition time (in ms)
uint32_t uint32_t
now, now,
timebase, timebase,
getPixelColor(uint16_t); getPixelColor(uint16_t) const;
inline uint32_t getLastShow(void) { return _lastShow; } // returns millis() timestamp of last strip.show() call inline uint32_t getLastShow(void) const { return _lastShow; } // returns millis() timestamp of last strip.show() call
inline uint32_t segColor(uint8_t i) { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition
const char * const char *
getModeData(uint8_t id = 0) { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); }
const char ** const char **
getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data
@ -900,7 +901,7 @@ class WS2812FX { // 96 bytes
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x);} inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); }
// end 2D support // end 2D support

View File

@ -184,13 +184,16 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col)
x *= groupLength(); // expand to physical pixels x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels
if (x >= width() || y >= height()) return; // if pixel would fall out of segment just exit
unsigned W = width();
unsigned H = height();
if (x >= W || y >= H) return; // if pixel would fall out of segment just exit
uint32_t tmpCol = col; uint32_t tmpCol = col;
for (int j = 0; j < grouping; j++) { // groupping vertically for (unsigned j = 0; j < grouping; j++) { // groupping vertically
for (int g = 0; g < grouping; g++) { // groupping horizontally for (unsigned g = 0; g < grouping; g++) { // groupping horizontally
unsigned xX = (x+g), yY = (y+j); unsigned xX = (x+g), yY = (y+j);
if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end if (xX >= W || yY >= H) continue; // we have reached one dimension's end
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel // if blending modes, blend with underlying pixel
@ -339,39 +342,126 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) {
setPixelColorXY(col, rows - 1, curnew); setPixelColorXY(col, rows - 1, curnew);
} }
// 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) void Segment::blur2D(uint8_t blur_amount, bool smear) {
void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) {
if (!isActive() || blur_amount == 0) return; // not active if (!isActive() || blur_amount == 0) return; // not active
const int cols = virtualWidth(); const unsigned cols = virtualWidth();
const int rows = virtualHeight(); const unsigned rows = virtualHeight();
const int dim1 = vertical ? rows : cols;
const int dim2 = vertical ? cols : rows; const uint8_t keep = smear ? 255 : 255 - blur_amount;
if (i >= dim2) return; const uint8_t seep = blur_amount >> (1 + smear);
const float seep = blur_amount/255.f; uint32_t lastnew;
const float keep = 3.f - 2.f*seep; uint32_t last;
// 1D box blur for (unsigned row = 0; row < rows; row++) {
uint32_t out[dim1], in[dim1]; uint32_t carryover = BLACK;
for (int j = 0; j < dim1; j++) { uint32_t curnew = BLACK;
int x = vertical ? i : j; for (unsigned x = 0; x < cols; x++) {
int y = vertical ? j : i; uint32_t cur = getPixelColorXY(x, row);
in[j] = getPixelColorXY(x, y); uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(x - 1, row, prev);
} else setPixelColorXY(x, row, curnew); // first pixel
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
} }
for (int j = 0; j < dim1; j++) { setPixelColorXY(cols-1, row, curnew); // set last pixel
uint32_t curr = in[j];
uint32_t prev = j > 0 ? in[j-1] : BLACK;
uint32_t next = j < dim1-1 ? in[j+1] : BLACK;
uint8_t r, g, b, w;
r = (R(curr)*keep + (R(prev) + R(next))*seep) / 3;
g = (G(curr)*keep + (G(prev) + G(next))*seep) / 3;
b = (B(curr)*keep + (B(prev) + B(next))*seep) / 3;
w = (W(curr)*keep + (W(prev) + W(next))*seep) / 3;
out[j] = RGBW32(r,g,b,w);
} }
for (int j = 0; j < dim1; j++) { for (unsigned col = 0; col < cols; col++) {
int x = vertical ? i : j; uint32_t carryover = BLACK;
int y = vertical ? j : i; uint32_t curnew = BLACK;
setPixelColorXY(x, y, out[j]); for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(col, y - 1, prev);
} else setPixelColorXY(col, y, curnew); // first pixel
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
} }
setPixelColorXY(col, rows - 1, curnew);
}
}
// 2D Box blur
void Segment::box_blur(unsigned radius, bool smear) {
if (!isActive() || radius == 0) return; // not active
if (radius > 3) radius = 3;
const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
uint16_t *tmpRSum = new uint16_t[cols*rows];
uint16_t *tmpGSum = new uint16_t[cols*rows];
uint16_t *tmpBSum = new uint16_t[cols*rows];
uint16_t *tmpWSum = new uint16_t[cols*rows];
// fill summed-area table (https://en.wikipedia.org/wiki/Summed-area_table)
for (unsigned x = 0; x < cols; x++) {
unsigned rS, gS, bS, wS;
unsigned index;
rS = gS = bS = wS = 0;
for (unsigned y = 0; y < rows; y++) {
index = x * cols + y;
if (x > 0) {
unsigned index2 = (x - 1) * cols + y;
tmpRSum[index] = tmpRSum[index2];
tmpGSum[index] = tmpGSum[index2];
tmpBSum[index] = tmpBSum[index2];
tmpWSum[index] = tmpWSum[index2];
} else {
tmpRSum[index] = 0;
tmpGSum[index] = 0;
tmpBSum[index] = 0;
tmpWSum[index] = 0;
}
uint32_t c = getPixelColorXY(x, y);
rS += R(c);
gS += G(c);
bS += B(c);
wS += W(c);
tmpRSum[index] += rS;
tmpGSum[index] += gS;
tmpBSum[index] += bS;
tmpWSum[index] += wS;
}
}
// do a box blur using pre-calculated sums
for (unsigned x = 0; x < cols; x++) {
for (unsigned y = 0; y < rows; y++) {
// sum = D + A - B - C where k = (x,y)
// +----+-+---- (x)
// | | |
// +----A-B
// | |k|
// +----C-D
// |
//(y)
unsigned x0 = x < radius ? 0 : x - radius;
unsigned y0 = y < radius ? 0 : y - radius;
unsigned x1 = x >= cols - radius ? cols - 1 : x + radius;
unsigned y1 = y >= rows - radius ? rows - 1 : y + radius;
unsigned A = x0 * cols + y0;
unsigned B = x1 * cols + y0;
unsigned C = x0 * cols + y1;
unsigned D = x1 * cols + y1;
unsigned r = tmpRSum[D] + tmpRSum[A] - tmpRSum[C] - tmpRSum[B];
unsigned g = tmpGSum[D] + tmpGSum[A] - tmpGSum[C] - tmpGSum[B];
unsigned b = tmpBSum[D] + tmpBSum[A] - tmpBSum[C] - tmpBSum[B];
unsigned w = tmpWSum[D] + tmpWSum[A] - tmpWSum[C] - tmpWSum[B];
setPixelColorXY(x, y, RGBW32(r/d, g/d, b/d, w/d));
}
}
delete[] tmpRSum;
delete[] tmpGSum;
delete[] tmpBSum;
delete[] tmpWSum;
} }
void Segment::moveX(int8_t delta, bool wrap) { void Segment::moveX(int8_t delta, bool wrap) {

View File

@ -954,9 +954,9 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) const
if (reverse) i = virtualLength() - i - 1; if (reverse) i = virtualLength() - i - 1;
i *= groupLength(); i *= groupLength();
i += start; i += start;
/* offset/phase */ // offset/phase
i += offset; i += offset;
if ((i >= stop) && (stop>0)) i -= length(); // avoids negative pixel index (stop = 0 is a possible value) if (i >= stop) i -= length();
return strip.getPixelColor(i); return strip.getPixelColor(i);
} }
@ -1110,15 +1110,13 @@ void Segment::blur(uint8_t blur_amount, bool smear) {
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
if (is2D()) { if (is2D()) {
// compatibility with 2D // compatibility with 2D
const unsigned cols = virtualWidth(); blur2D(blur_amount, smear);
const unsigned rows = virtualHeight(); //box_blur(map(blur_amount,1,255,1,3), smear);
for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); // blur all rows
for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); // blur all columns
return; return;
} }
#endif #endif
uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1; uint8_t seep = blur_amount >> (1 + smear);
unsigned vlength = virtualLength(); unsigned vlength = virtualLength();
uint32_t carryover = BLACK; uint32_t carryover = BLACK;
uint32_t lastnew; uint32_t lastnew;
@ -1129,13 +1127,11 @@ void Segment::blur(uint8_t blur_amount, bool smear) {
uint32_t part = color_fade(cur, seep); uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep); curnew = color_fade(cur, keep);
if (i > 0) { if (i > 0) {
if (carryover) if (carryover) curnew = color_add(curnew, carryover, true);
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true); uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed // optimization: only set pixel if color has changed
setPixelColor(i - 1, prev); if (last != prev) setPixelColor(i - 1, prev);
} } else // first pixel
else // first pixel
setPixelColor(i, curnew); setPixelColor(i, curnew);
lastnew = curnew; lastnew = curnew;
last = cur; // save original value for comparison on next iteration last = cur; // save original value for comparison on next iteration
@ -1357,7 +1353,7 @@ void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) {
BusManager::setPixelColor(i, col); BusManager::setPixelColor(i, col);
} }
uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) { uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) const {
i = getMappedPixelIndex(i); i = getMappedPixelIndex(i);
if (i >= _length) return 0; if (i >= _length) return 0;
return BusManager::getPixelColor(i); return BusManager::getPixelColor(i);
@ -1385,7 +1381,7 @@ void WS2812FX::show(void) {
* Returns a true value if any of the strips are still being updated. * Returns a true value if any of the strips are still being updated.
* On some hardware (ESP32), strip updates are done asynchronously. * On some hardware (ESP32), strip updates are done asynchronously.
*/ */
bool WS2812FX::isUpdating() { bool WS2812FX::isUpdating() const {
return !BusManager::canAllShow(); return !BusManager::canAllShow();
} }
@ -1393,7 +1389,7 @@ bool WS2812FX::isUpdating() {
* Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough. * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough.
* Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies
*/ */
uint16_t WS2812FX::getFps() { uint16_t WS2812FX::getFps() const {
if (millis() - _lastShow > 2000) return 0; if (millis() - _lastShow > 2000) return 0;
return _cumulativeFps +1; return _cumulativeFps +1;
} }
@ -1452,17 +1448,17 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
} }
} }
uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) { uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const {
uint8_t totalLC = 0; uint8_t totalLC = 0;
for (segment &seg : _segments) { for (const segment &seg : _segments) {
if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities(); if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities();
} }
return totalLC; return totalLC;
} }
uint8_t WS2812FX::getFirstSelectedSegId(void) { uint8_t WS2812FX::getFirstSelectedSegId(void) const {
size_t i = 0; size_t i = 0;
for (segment &seg : _segments) { for (const segment &seg : _segments) {
if (seg.isActive() && seg.isSelected()) return i; if (seg.isActive() && seg.isSelected()) return i;
i++; i++;
} }
@ -1478,14 +1474,14 @@ void WS2812FX::setMainSegmentId(uint8_t n) {
return; return;
} }
uint8_t WS2812FX::getLastActiveSegmentId(void) { uint8_t WS2812FX::getLastActiveSegmentId(void) const {
for (size_t i = _segments.size() -1; i > 0; i--) { for (size_t i = _segments.size() -1; i > 0; i--) {
if (_segments[i].isActive()) return i; if (_segments[i].isActive()) return i;
} }
return 0; return 0;
} }
uint8_t WS2812FX::getActiveSegmentsNum(void) { uint8_t WS2812FX::getActiveSegmentsNum(void) const {
uint8_t c = 0; uint8_t c = 0;
for (size_t i = 0; i < _segments.size(); i++) { for (size_t i = 0; i < _segments.size(); i++) {
if (_segments[i].isActive()) c++; if (_segments[i].isActive()) c++;
@ -1493,13 +1489,13 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) {
return c; return c;
} }
uint16_t WS2812FX::getLengthTotal(void) { uint16_t WS2812FX::getLengthTotal(void) const {
unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D
if (isMatrix && _length > len) len = _length; // for 2D with trailing strip if (isMatrix && _length > len) len = _length; // for 2D with trailing strip
return len; return len;
} }
uint16_t WS2812FX::getLengthPhysical(void) { uint16_t WS2812FX::getLengthPhysical(void) const {
unsigned len = 0; unsigned len = 0;
for (size_t b = 0; b < BusManager::getNumBusses(); b++) { for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b); Bus *bus = BusManager::getBus(b);
@ -1512,7 +1508,7 @@ uint16_t WS2812FX::getLengthPhysical(void) {
//used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw.
//returns if there is an RGBW bus (supports RGB and White, not only white) //returns if there is an RGBW bus (supports RGB and White, not only white)
//not influenced by auto-white mode, also true if white slider does not affect output white channel //not influenced by auto-white mode, also true if white slider does not affect output white channel
bool WS2812FX::hasRGBWBus(void) { bool WS2812FX::hasRGBWBus(void) const {
for (size_t b = 0; b < BusManager::getNumBusses(); b++) { for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b); Bus *bus = BusManager::getBus(b);
if (bus == nullptr || bus->getLength()==0) break; if (bus == nullptr || bus->getLength()==0) break;
@ -1521,7 +1517,7 @@ bool WS2812FX::hasRGBWBus(void) {
return false; return false;
} }
bool WS2812FX::hasCCTBus(void) { bool WS2812FX::hasCCTBus(void) const {
if (cctFromRgb && !correctWB) return false; if (cctFromRgb && !correctWB) return false;
for (size_t b = 0; b < BusManager::getNumBusses(); b++) { for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b); Bus *bus = BusManager::getBus(b);
@ -1813,7 +1809,7 @@ bool WS2812FX::deserializeMap(uint8_t n) {
return (customMappingSize > 0); return (customMappingSize > 0);
} }
uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) { uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const {
// convert logical address to physical // convert logical address to physical
if (index < customMappingSize if (index < customMappingSize
&& (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index];