From 0c8d9d5614a2e4f0013604cfcac8552cf1646279 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 18:38:06 +0200 Subject: [PATCH 01/18] Mode blending styles - alternative to #3669 --- wled00/FX.h | 28 ++++++++++ wled00/FX_2Dfcn.cpp | 33 +++++++++++- wled00/FX_fcn.cpp | 123 +++++++++++++++++++++++++++++++++++++----- wled00/data/index.htm | 20 ++++++- wled00/data/index.js | 4 ++ wled00/fcn_declare.h | 1 + wled00/json.cpp | 8 +++ wled00/util.cpp | 7 +++ wled00/wled.h | 3 +- 9 files changed, 212 insertions(+), 15 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 1089a0b8bc..44c5e1549a 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -316,6 +316,25 @@ #define MODE_COUNT 187 + +#define BLEND_STYLE_FADE 0 +#define BLEND_STYLE_FAIRY_DUST 1 +#define BLEND_STYLE_SWIPE_RIGHT 2 +#define BLEND_STYLE_SWIPE_LEFT 3 +#define BLEND_STYLE_PINCH_OUT 4 +#define BLEND_STYLE_INSIDE_OUT 5 +#define BLEND_STYLE_SWIPE_UP 6 +#define BLEND_STYLE_SWIPE_DOWN 7 +#define BLEND_STYLE_OPEN_H 8 +#define BLEND_STYLE_OPEN_V 9 +#define BLEND_STYLE_PUSH_TL 10 +#define BLEND_STYLE_PUSH_TR 11 +#define BLEND_STYLE_PUSH_BR 12 +#define BLEND_STYLE_PUSH_BL 13 + +#define BLEND_STYLE_COUNT 14 + + typedef enum mapping1D2D { M12_Pixels = 0, M12_pBar = 1, @@ -419,6 +438,9 @@ typedef struct Segment { static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF #ifndef WLED_DISABLE_MODE_BLEND static bool _modeBlend; // mode/effect blending semaphore + // clipping + static uint16_t _clipStart, _clipStop; + static uint8_t _clipStartY, _clipStopY; #endif // transition data, valid only if transitional==true, holds values during transition (72 bytes) @@ -578,6 +600,10 @@ typedef struct Segment { void setPixelColor(float i, uint32_t c, bool aa = true); inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + #ifndef WLED_DISABLE_MODE_BLEND + inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + #endif + bool isPixelClipped(int i); uint32_t getPixelColor(int i); // 1D support functions (some implement 2D as well) void blur(uint8_t); @@ -606,6 +632,7 @@ typedef struct Segment { void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + bool isPixelXYClipped(int x, int y); uint32_t getPixelColorXY(uint16_t x, uint16_t y); // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } @@ -640,6 +667,7 @@ typedef struct Segment { inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, 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 bool isPixelXYClipped(int x, int y) { return isPixelClipped(x); } inline uint32_t getPixelColorXY(uint16_t x, uint16_t 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, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7aecd22716..7e14192930 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -167,10 +167,41 @@ uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) return isActive() ? (x%width) + (y%height) * width : 0; } +// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// if clipping start > stop the clipping range is inverted +// _modeBlend==true -> old effect during transition +// _modeBlend==false -> new effect during transition +bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { +#ifndef WLED_DISABLE_MODE_BLEND + if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { + const bool invertX = _clipStart > _clipStop; + const bool invertY = _clipStartY > _clipStopY; + const unsigned startX = invertX ? _clipStop : _clipStart; + const unsigned stopX = invertX ? _clipStart : _clipStop; + const unsigned startY = invertY ? _clipStopY : _clipStartY; + const unsigned stopY = invertY ? _clipStartY : _clipStopY; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth()) + const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight()) + if (len < 2) return false; + const unsigned shuffled = hashInt(x + y * width) % len; + const unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + bool xInside = (x >= startX && x < stopX); if (invertX) xInside = !xInside; + bool yInside = (y >= startY && y < stopY); if (invertY) yInside = !yInside; + const bool clip = (invertX && invertY) ? !_modeBlend : _modeBlend; + if (xInside && yInside) return clip; // covers window & corners (inverted) + return !clip; + } +#endif + return false; +} + void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + if (x >= virtualWidth() || y >= virtualHeight() || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f97268f9ba..989809d026 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -84,6 +84,10 @@ uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) #ifndef WLED_DISABLE_MODE_BLEND bool Segment::_modeBlend = false; +uint16_t Segment::_clipStart = 0; +uint16_t Segment::_clipStop = 0; +uint8_t Segment::_clipStartY = 0; +uint8_t Segment::_clipStopY = 1; #endif // copy constructor @@ -413,12 +417,17 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint32_t prog = progress(); + uint32_t curBri = useCct ? cct : (on ? opacity : 0); if (prog < 0xFFFFU) { - uint32_t curBri = (useCct ? cct : (on ? opacity : 0)) * prog; - curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); + uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness +#endif + curBri *= prog; + curBri += tmpBri * (0xFFFFU - prog); return curBri / 0xFFFFU; } - return (useCct ? cct : (on ? opacity : 0)); + return curBri; } uint8_t IRAM_ATTR Segment::currentMode() { @@ -431,16 +440,23 @@ uint8_t IRAM_ATTR Segment::currentMode() { uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { if (slot >= NUM_COLORS) slot = 0; + uint32_t prog = progress(); + if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else - return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; + return color_blend(_t->_colorT[slot], colors[slot], prog, true); #endif } CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); uint16_t prog = progress(); +#ifndef WLED_DISABLE_MODE_BLEND + if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + else +#endif if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) @@ -456,9 +472,9 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u void Segment::handleRandomPalette() { // is it time to generate a new palette? if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = millis()/1000U; - _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = millis()/1000U; + _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately } // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) @@ -662,6 +678,32 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } +// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// if clipping start > stop the clipping range is inverted +// _modeBlend==true -> old effect during transition +// _modeBlend==false -> new effect during transition +bool IRAM_ATTR Segment::isPixelClipped(int i) { +#ifndef WLED_DISABLE_MODE_BLEND + if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { + bool invert = _clipStart > _clipStop; + unsigned start = invert ? _clipStop : _clipStart; + unsigned stop = invert ? _clipStart : _clipStop; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + unsigned len = stop - start; + if (len < 2) return false; + unsigned shuffled = hashInt(i) % len; + unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + const bool iInside = (i >= start && i < stop); + if (!invert && iInside) return _modeBlend; + if ( invert && !iInside) return _modeBlend; + return !_modeBlend; + } +#endif + return false; +} + void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active @@ -732,6 +774,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif + if (isPixelClipped(i)) return; // handle clipping on 1D + uint16_t len = length(); uint8_t _bri_t = currentBri(); if (_bri_t < 255) { @@ -763,14 +807,16 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) indexMir += offset; // offset/phase if (indexMir >= stop) indexMir -= len; // wrap #ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); + // _modeBlend==true -> old effect + if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); #endif strip.setPixelColor(indexMir, tmpCol); } indexSet += offset; // offset/phase if (indexSet >= stop) indexSet -= len; // wrap #ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); + // _modeBlend==true -> old effect + if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); #endif strip.setPixelColor(indexSet, tmpCol); } @@ -1062,7 +1108,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGBPalette16 curPal; - curPal = currentPalette(curPal, palette); + currentPalette(curPal, palette); CRGB fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); @@ -1185,8 +1231,59 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND + seg.setClippingRect(0, 0); // disable clipping + if (modeBlending && seg.mode != tmpMode) { + // set clipping rectangle + // new mode is run inside clipping area and old mode outside clipping area + unsigned p = seg.progress(); + unsigned w = seg.is2D() ? seg.virtualWidth() : _virtualSegmentLength; + unsigned h = seg.virtualHeight(); + unsigned dw = p * w / 0xFFFFU + 1; + unsigned dh = p * h / 0xFFFFU + 1; + switch (blendingStyle) { + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + seg.setClippingRect(0, w, 0, h); + break; + case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + seg.setClippingRect(0, dw, 0, h); + break; + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + seg.setClippingRect(w - dw, w, 0, h); + break; + case BLEND_STYLE_PINCH_OUT: // corners + seg.setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! + break; + case BLEND_STYLE_INSIDE_OUT: // outward + seg.setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); + break; + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + seg.setClippingRect(0, w, 0, dh); + break; + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + seg.setClippingRect(0, w, h - dh, h); + break; + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + seg.setClippingRect((w - dw)/2, (w + dw)/2, 0, h); + break; + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + seg.setClippingRect(0, w, (h - dh)/2, (h + dh)/2); + break; + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + seg.setClippingRect(0, dw, 0, dh); + break; + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + seg.setClippingRect(w - dw, w, 0, dh); + break; + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + seg.setClippingRect(w - dw, w, h - dh, h); + break; + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + seg.setClippingRect(0, dw, h - dh, h); + break; + } + } + delay = (*_mode[seg.mode])(); // run new/current mode if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore @@ -1197,6 +1294,8 @@ void WS2812FX::service() { delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } +#else + delay = (*_mode[seg.mode])(); // run effect mode #endif seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 4a532abb7a..dc14317675 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -265,7 +265,25 @@
-

Transition:  s

+

Transition:  s

+

Blend: + +

diff --git a/wled00/data/index.js b/wled00/data/index.js index 4ad2044ad8..1a50b6a3b6 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1426,6 +1426,9 @@ function readState(s,command=false) tr = s.transition; gId('tt').value = tr/10; + gId('bs').value = s.bs || 0; + if (tr===0) gId('bsp').classList.add('hide') + else gId('bsp').classList.remove('hide') populateSegments(s); var selc=0; @@ -1682,6 +1685,7 @@ function requestJson(command=null) var tn = parseInt(t.value*10); if (tn != tr) command.transition = tn; } + //command.bs = parseInt(gId('bs').value); req = JSON.stringify(command); if (req.length > 1340) useWs = false; // do not send very long requests over websocket if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index f1b013e99b..9a843dbf46 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -383,6 +383,7 @@ uint16_t crc16(const unsigned char* data_p, size_t length); um_data_t* simulateSound(uint8_t simulationId); void enumerateLedmaps(); uint8_t get_random_wheel_index(uint8_t pos); +uint32_t hashInt(uint32_t s); // RAII guard class for the JSON Buffer lock // Modeled after std::lock_guard diff --git a/wled00/json.cpp b/wled00/json.cpp index fd1527a219..bde6c36c73 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -355,6 +355,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } +#ifndef WLED_DISABLE_MODE_BLEND + blendingStyle = root[F("bs")] | blendingStyle; + blendingStyle = constrain(blendingStyle, 0, BLEND_STYLE_COUNT-1); +#endif + // temporary transition (applies only once) tr = root[F("tt")] | -1; if (tr >= 0) { @@ -581,6 +586,9 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme root["on"] = (bri > 0); root["bri"] = briLast; root[F("transition")] = transitionDelay/100; //in 100ms +#ifndef WLED_DISABLE_MODE_BLEND + root[F("bs")] = blendingStyle; +#endif } if (!forPreset) { diff --git a/wled00/util.cpp b/wled00/util.cpp index ad7e4b6701..eabd3e3837 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -599,3 +599,10 @@ uint8_t get_random_wheel_index(uint8_t pos) { } return r; } + +uint32_t hashInt(uint32_t s) { + // borrowed from https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key + s = ((s >> 16) ^ s) * 0x45d9f3b; + s = ((s >> 16) ^ s) * 0x45d9f3b; + return (s >> 16) ^ s; +} diff --git a/wled00/wled.h b/wled00/wled.h index 35b99260a0..a58e1ceabf 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403280 +#define VERSION 2404030 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -547,6 +547,7 @@ WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random co // transitions WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending +WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) From f5199d2b73bb3b516b7d98b4fee8b2af2b688c07 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 20:55:59 +0200 Subject: [PATCH 02/18] Fix compile. --- wled00/FX_fcn.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 989809d026..bd5758e314 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -419,9 +419,11 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint32_t prog = progress(); uint32_t curBri = useCct ? cct : (on ? opacity : 0); if (prog < 0xFFFFU) { - uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); #ifndef WLED_DISABLE_MODE_BLEND + uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness +#else + uint8_t tmpBri = useCct ? _t->_cctT : _t->_briT; #endif curBri *= prog; curBri += tmpBri * (0xFFFFU - prog); From a3a8fa1cef1f54c2d1028a0c3e5a5fcc6ef8235e Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 8 Apr 2024 16:24:27 +0200 Subject: [PATCH 03/18] Remove conditional fade/blend - transitions always enabled (use delay 0 to disable) - optimisation in on/off fade - fix for palette/color blend when blending style is not fade - various tweaks and optimisations --- CHANGELOG.md | 3 + .../stairway-wipe-usermod-v2.h | 2 +- .../stairway_wipe_basic/wled06_usermod.ino | 111 ------------------ wled00/FX.cpp | 61 +++++----- wled00/FX.h | 7 +- wled00/FX_2Dfcn.cpp | 10 +- wled00/FX_fcn.cpp | 104 ++++++++-------- wled00/bus_manager.cpp | 6 +- wled00/bus_manager.h | 3 +- wled00/cfg.cpp | 14 +-- wled00/data/settings_leds.htm | 7 +- wled00/fcn_declare.h | 1 - wled00/json.cpp | 6 +- wled00/led.cpp | 54 +++------ wled00/playlist.cpp | 2 +- wled00/set.cpp | 9 +- wled00/udp.cpp | 6 +- wled00/wled.cpp | 6 +- wled00/wled.h | 2 - wled00/wled_eeprom.cpp | 2 +- wled00/xml.cpp | 5 +- 21 files changed, 135 insertions(+), 286 deletions(-) delete mode 100644 usermods/stairway_wipe_basic/wled06_usermod.ino diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f6df2de6..0d86c8b2da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## WLED changelog +#### Build 2404050 +- Blending styles (with help from @tkadauke) + #### Build 2403280 - Individual color channel control for JSON API (fixes #3860) - "col":[int|string|object|array, int|string|object|array, int|string|object|array] diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h index f712316b86..707479df17 100644 --- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -96,7 +96,7 @@ void setup() { jsonTransitionOnce = true; strip.setTransition(0); //no transition effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning + strip.resetTimebase(); //make sure wipe starts from beginning //set wipe direction Segment& seg = strip.getSegment(0); diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino deleted file mode 100644 index c1264ebfb2..0000000000 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This file allows you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) - * bytes 2400+ are currently ununsed, but might be used for future wled features - */ - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - -byte wipeState = 0; //0: inactive 1: wiping 2: solid -unsigned long timeStaticStart = 0; -uint16_t previousUserVar0 = 0; - -//comment this out if you want the turn off effect to be just fading out instead of reverse wipe -#define STAIRCASE_WIPE_OFF - -//gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() -{ - //setup PIR sensor here, if needed -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ - -} - -//loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() -{ - //userVar0 (U0 in HTTP API): - //has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266 - //has to be set to 2 if movement is detected on the PIR that is the opposite side - //can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds) - - if (userVar0 > 0) - { - if ((previousUserVar0 == 1 && userVar0 == 2) || (previousUserVar0 == 2 && userVar0 == 1)) wipeState = 3; //turn off if other PIR triggered - previousUserVar0 = userVar0; - - if (wipeState == 0) { - startWipe(); - wipeState = 1; - } else if (wipeState == 1) { //wiping - uint32_t cycleTime = 360 + (255 - effectSpeed)*75; //this is how long one wipe takes (minus 25 ms to make sure we switch in time) - if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete - effectCurrent = FX_MODE_STATIC; - timeStaticStart = millis(); - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 2; - } - } else if (wipeState == 2) { //static - if (userVar1 > 0) //if U1 is not set, the light will stay on until second PIR or external command is triggered - { - if (millis() - timeStaticStart > userVar1*1000) wipeState = 3; - } - } else if (wipeState == 3) { //switch to wipe off - #ifdef STAIRCASE_WIPE_OFF - effectCurrent = FX_MODE_COLOR_WIPE; - strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 4; - #else - turnOff(); - #endif - } else { //wiping off - if (millis() + strip.timebase > (725 + (255 - effectSpeed)*150)) turnOff(); //wipe complete - } - } else { - wipeState = 0; //reset for next time - if (previousUserVar0) { - #ifdef STAIRCASE_WIPE_OFF - userVar0 = previousUserVar0; - wipeState = 3; - #else - turnOff(); - #endif - } - previousUserVar0 = 0; - } -} - -void startWipe() -{ - bri = briLast; //turn on - transitionDelayTemp = 0; //no transition - effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning - - //set wipe direction - Segment& seg = strip.getSegment(0); - bool doReverse = (userVar0 == 2); - seg.setOption(1, doReverse); - - colorUpdated(CALL_MODE_NOTIFICATION); -} - -void turnOff() -{ - #ifdef STAIRCASE_WIPE_OFF - transitionDelayTemp = 0; //turn off immediately after wipe completed - #else - transitionDelayTemp = 4000; //fade out slowly - #endif - bri = 0; - stateUpdated(CALL_MODE_NOTIFICATION); - wipeState = 0; - userVar0 = 0; - previousUserVar0 = 0; -} diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 14341f5b99..e75e20cd2d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1211,8 +1211,9 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" */ uint16_t mode_fireworks() { if (SEGLEN == 1) return mode_static(); - const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const uint16_t height = SEGMENT.virtualHeight(); + const unsigned width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); + const unsigned height = SEGMENT.virtualHeight(); + const unsigned dimension = width * height; if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1220,19 +1221,19 @@ uint16_t mode_fireworks() { } SEGMENT.fade_out(128); - bool valid1 = (SEGENV.aux0 < 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 + bool valid1 = (SEGENV.aux0 < dimension); + bool valid2 = (SEGENV.aux1 < dimension); + unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte uint32_t sv1 = 0, sv2 = 0; 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 (!SEGENV.step) SEGMENT.blur(16); + if (!SEGENV.step) SEGMENT.blur(dimension > 100 ? 16 : 8); 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 - for (int i=0; i> 1)) == 0) { - uint16_t index = random16(width*height); + unsigned index = random16(dimension); x = index % width; y = index / width; uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); @@ -2066,41 +2067,41 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const unsigned ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (int i = 0; i < SEGLEN; i++) { - uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); - uint8_t minTemp = (i 1; k--) { + for (unsigned k = SEGLEN -1; k > 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } // Step 3. Randomly ignite new 'sparks' of heat near the bottom if (random8() <= SEGMENT.intensity) { - uint8_t y = random8(ignition); - uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! + unsigned y = random8(ignition); + unsigned boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost)); } } // Step 4. Map from heat cells to LED colors for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND)); + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, LINEARBLEND_NOWRAP)); } } }; - for (int stripNr=0; stripNr 100 ? 32 : 0); if (it != SEGENV.step) SEGENV.step = it; @@ -4856,9 +4857,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); - uint16_t x, y; + const unsigned cols = SEGMENT.virtualWidth(); + const unsigned rows = SEGMENT.virtualHeight(); + unsigned x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase @@ -4877,7 +4878,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit - SEGMENT.blur(16); + SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); return FRAMETIME; } // mode_2DBlackHole() @@ -6436,8 +6437,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const unsigned cols = SEGMENT.virtualWidth(); + const unsigned rows = SEGMENT.virtualHeight(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6449,21 +6450,21 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fadeToBlackBy(SEGMENT.speed); long t = strip.now / 2; - for (int i = 0; i < cols; i++) { - uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + for (unsigned i = 0; i < cols; i++) { + unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available if (um_data) { thisVal /= 32; // reduce intensity of inoise8() thisVal *= volumeSmth; } - uint16_t thisMax = map(thisVal, 0, 512, 0, rows); + unsigned thisMax = map(thisVal, 0, 512, 0, rows); - for (int j = 0; j < thisMax; j++) { + for (unsigned j = 0; j < thisMax; j++) { SEGMENT.addPixelColorXY(i, 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(16); + SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); return FRAMETIME; } // mode_2DWaverly() diff --git a/wled00/FX.h b/wled00/FX.h index 44c5e1549a..fd0bb297b5 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -601,7 +601,7 @@ typedef struct Segment { inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #ifndef WLED_DISABLE_MODE_BLEND - inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + static inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; #endif bool isPixelClipped(int i); uint32_t getPixelColor(int i); @@ -708,9 +708,7 @@ class WS2812FX { // 96 bytes public: WS2812FX() : - paletteFade(0), paletteBlend(0), - cctBlending(0), now(millis()), timebase(0), isMatrix(false), @@ -792,6 +790,7 @@ class WS2812FX { // 96 bytes addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name), // add effect to the list; defined in FX.cpp setupEffectData(void); // add default effects to the list; defined in FX.cpp + inline void resetTimebase() { timebase = 0U - millis(); } inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } @@ -806,7 +805,6 @@ class WS2812FX { // 96 bytes inline void resume(void) { _suspend = false; } // will resume strip.service() execution bool - paletteFade, checkSegmentAlignment(void), hasRGBWBus(void), hasCCTBus(void), @@ -822,7 +820,6 @@ class WS2812FX { // 96 bytes uint8_t paletteBlend, - cctBlending, getActiveSegmentsNum(void), getFirstSelectedSegId(void), getLastActiveSegmentId(void), diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7e14192930..a37e5d11ff 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -176,10 +176,10 @@ bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { const bool invertX = _clipStart > _clipStop; const bool invertY = _clipStartY > _clipStopY; - const unsigned startX = invertX ? _clipStop : _clipStart; - const unsigned stopX = invertX ? _clipStart : _clipStop; - const unsigned startY = invertY ? _clipStopY : _clipStartY; - const unsigned stopY = invertY ? _clipStartY : _clipStopY; + const int startX = invertX ? _clipStop : _clipStart; + const int stopX = invertX ? _clipStart : _clipStop; + const int startY = invertY ? _clipStopY : _clipStartY; + const int stopY = invertY ? _clipStartY : _clipStopY; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth()) const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight()) @@ -295,7 +295,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 00196e38e2..4ef110e070 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -293,21 +293,17 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) { - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; - } + swapSegenv(_t->_segT); + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; } - } else { - for (size_t i=0; i_segT._colorT[i] = colors[i]; } #else for (size_t i=0; i_colorT[i] = colors[i]; @@ -435,7 +431,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint8_t IRAM_ATTR Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND uint16_t prog = progress(); - if (modeBlending && prog < 0xFFFFU) return _t->_modeT; + if (prog < 0xFFFFU) return _t->_modeT; #endif return mode; } @@ -445,7 +441,7 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE && mode != _t->_modeT) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); @@ -456,10 +452,10 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u loadPalette(targetPalette, pal); uint16_t prog = progress(); #ifndef WLED_DISABLE_MODE_BLEND - if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend && mode != _t->_modeT) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette else #endif - if (strip.paletteFade && prog < 0xFFFFU) { + if (prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms @@ -473,19 +469,16 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { // is it time to generate a new palette? - if ((uint16_t)(millis() / 1000U) - _lastPaletteChange > randomPaletteChangeTime){ - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = (uint16_t)(millis() / 1000U); - _lastPaletteBlend = (uint16_t)millis() - 512; // starts blending immediately + if ((uint16_t)(millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = (uint16_t)(millis()/1000U); + _lastPaletteBlend = (uint16_t)(millis())-512; // starts blending immediately } - // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) - if (strip.paletteFade) { - // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) - // in reality there need to be 255 blends to fully blend two entirely different palettes - if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = (uint16_t)millis(); - } + // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) + // in reality there need to be 255 blends to fully blend two entirely different palettes + if ((uint16_t)millis() - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = (uint16_t)millis(); nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } @@ -549,7 +542,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black } - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast return true; @@ -562,21 +555,21 @@ void Segment::setCCT(uint16_t k) { k = (k - 1900) >> 5; } if (cct == k) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change cct = k; stateChanged = true; // send UDP/WS broadcast } void Segment::setOpacity(uint8_t o) { if (opacity == o) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast } void Segment::setOption(uint8_t n, bool val) { bool prevOn = on; - if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change + if (n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast @@ -589,7 +582,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx != mode) { #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) startTransition(strip.getTransition()); // set effect transitions + startTransition(strip.getTransition()); // set effect transitions #endif mode = fx; // load default values from effect string @@ -620,7 +613,7 @@ void Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { - if (strip.paletteFade) startTransition(strip.getTransition()); + startTransition(strip.getTransition()); palette = pal; stateChanged = true; // send UDP/WS broadcast } @@ -688,8 +681,8 @@ bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { bool invert = _clipStart > _clipStop; - unsigned start = invert ? _clipStop : _clipStart; - unsigned stop = invert ? _clipStart : _clipStop; + int start = invert ? _clipStop : _clipStart; + int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { unsigned len = stop - start; if (len < 2) return false; @@ -888,6 +881,8 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) } #endif + if (isPixelClipped(i)) return 0; // handle clipping on 1D + if (reverse) i = virtualLength() - i - 1; i *= groupLength(); i += start; @@ -1234,8 +1229,8 @@ void WS2812FX::service() { // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition #ifndef WLED_DISABLE_MODE_BLEND - seg.setClippingRect(0, 0); // disable clipping - if (modeBlending && seg.mode != tmpMode) { + Segment::setClippingRect(0, 0); // disable clipping (just in case) + if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles // set clipping rectangle // new mode is run inside clipping area and old mode outside clipping area unsigned p = seg.progress(); @@ -1245,48 +1240,48 @@ void WS2812FX::service() { unsigned dh = p * h / 0xFFFFU + 1; switch (blendingStyle) { case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) - seg.setClippingRect(0, w, 0, h); + Segment::setClippingRect(0, w, 0, h); break; case BLEND_STYLE_SWIPE_RIGHT: // left-to-right - seg.setClippingRect(0, dw, 0, h); + Segment::setClippingRect(0, dw, 0, h); break; case BLEND_STYLE_SWIPE_LEFT: // right-to-left - seg.setClippingRect(w - dw, w, 0, h); + Segment::setClippingRect(w - dw, w, 0, h); break; case BLEND_STYLE_PINCH_OUT: // corners - seg.setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! + Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! break; case BLEND_STYLE_INSIDE_OUT: // outward - seg.setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); + Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); break; case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) - seg.setClippingRect(0, w, 0, dh); + Segment::setClippingRect(0, w, 0, dh); break; case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) - seg.setClippingRect(0, w, h - dh, h); + Segment::setClippingRect(0, w, h - dh, h); break; case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D - seg.setClippingRect((w - dw)/2, (w + dw)/2, 0, h); + Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h); break; case BLEND_STYLE_OPEN_V: // vertical-outward (2D) - seg.setClippingRect(0, w, (h - dh)/2, (h + dh)/2); + Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2); break; case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) - seg.setClippingRect(0, dw, 0, dh); + Segment::setClippingRect(0, dw, 0, dh); break; case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) - seg.setClippingRect(w - dw, w, 0, dh); + Segment::setClippingRect(w - dw, w, 0, dh); break; case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) - seg.setClippingRect(w - dw, w, h - dh, h); + Segment::setClippingRect(w - dw, w, h - dh, h); break; case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) - seg.setClippingRect(0, dw, h - dh, h); + Segment::setClippingRect(0, dw, h - dh, h); break; } } delay = (*_mode[seg.mode])(); // run new/current mode - if (modeBlending && seg.mode != tmpMode) { + if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) @@ -1301,13 +1296,14 @@ void WS2812FX::service() { #endif seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition - BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments + BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } seg.next_time = nowUp + delay; } _segment_index++; } + Segment::setClippingRect(0, 0); // disable clipping for overlays _virtualSegmentLength = 0; _isServicing = false; _triggered = false; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 82e81a3877..02a76f69d1 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -391,14 +391,14 @@ BusPwm::BusPwm(BusConfig &bc) uint8_t numPins = NUM_PWM_PINS(bc.type); _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; - #ifdef ESP8266 +#ifdef ESP8266 // duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth if (_frequency > 1760) _depth = 8; else if (_frequency > 880) _depth = 9; else _depth = 10; // WLED_PWM_FREQ <= 880Hz analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); - #else +#else _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels deallocatePins(); return; @@ -408,7 +408,7 @@ BusPwm::BusPwm(BusConfig &bc) else if (_frequency > 39062) _depth = 10; else if (_frequency > 19531) _depth = 11; else _depth = 12; // WLED_PWM_FREQ <= 19531Hz - #endif +#endif for (unsigned i = 0; i < numPins; i++) { uint8_t currentPin = bc.pins[i]; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index c128f8c099..cdd1e82ff9 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -173,10 +173,11 @@ class Bus { type == TYPE_FW1906 || type == TYPE_WS2805 ) return true; return false; } - static int16_t getCCT() { return _cct; } + static inline int16_t getCCT() { return _cct; } static void setCCT(int16_t cct) { _cct = cct; } + static inline uint8_t getCCTBlend() { return _cctBlend; } static void setCCTBlend(uint8_t b) { if (b > 100) b = 100; _cctBlend = (b * 127) / 100; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 530777ab54..01c407f950 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -111,8 +111,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); CJSON(cctICused, hw_led[F("ic")]); - CJSON(strip.cctBlending, hw_led[F("cb")]); - Bus::setCCTBlend(strip.cctBlending); + uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend(); + Bus::setCCTBlend(cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS CJSON(useGlobalLedBuffer, hw_led[F("ld")]); @@ -408,12 +408,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } JsonObject light_tr = light["tr"]; - CJSON(fadeTransition, light_tr["mode"]); - CJSON(modeBlending, light_tr["fx"]); int tdd = light_tr["dur"] | -1; if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; - strip.setTransition(fadeTransition ? transitionDelayDefault : 0); - CJSON(strip.paletteFade, light_tr["pal"]); + strip.setTransition(transitionDelayDefault); CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]); @@ -777,7 +774,7 @@ void serializeConfig() { hw_led["cct"] = correctWB; hw_led[F("cr")] = cctFromRgb; hw_led[F("ic")] = cctICused; - hw_led[F("cb")] = strip.cctBlending; + hw_led[F("cb")] = Bus::getCCTBlend(); hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override hw_led[F("ld")] = useGlobalLedBuffer; @@ -894,10 +891,7 @@ void serializeConfig() { light_gc["val"] = gammaCorrectVal; JsonObject light_tr = light.createNestedObject("tr"); - light_tr["mode"] = fadeTransition; - light_tr["fx"] = modeBlending; light_tr["dur"] = transitionDelayDefault / 100; - light_tr["pal"] = strip.paletteFade; light_tr[F("rpc")] = randomPaletteChangeTime; light_tr[F("hrp")] = useHarmonicRandomPalette; diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index dddedd471d..2a5267825c 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -834,12 +834,7 @@

Defaults

Use Gamma value:

Brightness factor: %

Transitions

- Enable transitions:
- - Effect blending:
- Transition Time: ms
- Palette transitions:
-
+ Transition Time: ms
Random Cycle Palette Time: s
Use harmonic Random Cycle Palette:

Timed light

diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 010ad3a53b..85893dfae4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -181,7 +181,6 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); void setValuesFromSegment(uint8_t s); void setValuesFromMainSeg(); void setValuesFromFirstSelectedSeg(); -void resetTimebase(); void toggleOnOff(); void applyBri(); void applyFinalBri(); diff --git a/wled00/json.cpp b/wled00/json.cpp index 269d8a7f69..2e00ec09c9 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -351,7 +351,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) tr = root[F("transition")] | -1; if (tr >= 0) { transitionDelay = tr * 100; - if (fadeTransition) strip.setTransition(transitionDelay); + strip.setTransition(transitionDelay); } } @@ -364,7 +364,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) tr = root[F("tt")] | -1; if (tr >= 0) { jsonTransitionOnce = true; - if (fadeTransition) strip.setTransition(tr * 100); + strip.setTransition(tr * 100); } tr = root[F("tb")] | -1; @@ -779,7 +779,7 @@ void serializeInfo(JsonObject root) root[F("freeheap")] = ESP.getFreeHeap(); #if defined(ARDUINO_ARCH_ESP32) - if (psramSafe && psramFound()) root[F("psram")] = ESP.getFreePsram(); + if (psramFound()) root[F("psram")] = ESP.getFreePsram(); #endif root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; diff --git a/wled00/led.cpp b/wled00/led.cpp index 23c8d03c57..acfa3ac36e 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -47,12 +47,6 @@ void applyValuesToSelectedSegs() } -void resetTimebase() -{ - strip.timebase = 0 - millis(); -} - - void toggleOnOff() { if (bri == 0) @@ -76,7 +70,7 @@ byte scaledBri(byte in) } -//applies global brightness +//applies global temporary brightness (briT) to strip void applyBri() { if (!realtimeMode || !arlsForceMaxBri) { @@ -90,6 +84,7 @@ void applyFinalBri() { briOld = bri; briT = bri; applyBri(); + strip.trigger(); // force one last update } @@ -122,7 +117,7 @@ void stateUpdated(byte callMode) { nightlightStartTime = millis(); } if (briT == 0) { - if (callMode != CALL_MODE_NOTIFICATION) resetTimebase(); //effect start from beginning + if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning } if (bri > 0) briLast = bri; @@ -133,31 +128,24 @@ void stateUpdated(byte callMode) { // notify usermods of state change usermods.onStateChange(callMode); - if (fadeTransition) { - if (strip.getTransition() == 0) { - jsonTransitionOnce = false; - transitionActive = false; - applyFinalBri(); - strip.trigger(); - return; - } - - if (transitionActive) { - briOld = briT; - tperLast = 0; - } else - strip.setTransitionMode(true); // force all segments to transition mode - transitionActive = true; - transitionStartTime = millis(); - } else { + if (strip.getTransition() == 0) { + jsonTransitionOnce = false; + transitionActive = false; applyFinalBri(); - strip.trigger(); + return; } + + if (transitionActive) { + briOld = briT; + tperLast = 0; + } else + strip.setTransitionMode(true); // force all segments to transition mode + transitionActive = true; + transitionStartTime = millis(); } -void updateInterfaces(uint8_t callMode) -{ +void updateInterfaces(uint8_t callMode) { if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return; sendDataWs(); @@ -178,8 +166,7 @@ void updateInterfaces(uint8_t callMode) } -void handleTransitions() -{ +void handleTransitions() { //handle still pending interface update updateInterfaces(interfaceUpdateCallMode); @@ -198,7 +185,6 @@ void handleTransitions() if (tper - tperLast < 0.004f) return; tperLast = tper; briT = briOld + ((bri - briOld) * tper); - applyBri(); } } @@ -211,8 +197,7 @@ void colorUpdated(byte callMode) { } -void handleNightlight() -{ +void handleNightlight() { unsigned long now = millis(); if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second @@ -292,7 +277,6 @@ void handleNightlight() } //utility for FastLED to use our custom timer -uint32_t get_millisecond_timer() -{ +uint32_t get_millisecond_timer() { return strip.now; } diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 67c4f60494..225102da64 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -146,7 +146,7 @@ void handlePlaylist() { } jsonTransitionOnce = true; - strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0); + strip.setTransition(playlistEntries[playlistIndex].tr * 100); playlistEntryDur = playlistEntries[playlistIndex].dur; applyPresetFromPlaylist(playlistEntries[playlistIndex].preset); } diff --git a/wled00/set.cpp b/wled00/set.cpp index a2e884c817..c5b9f262c0 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -128,8 +128,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) correctWB = request->hasArg(F("CCT")); cctFromRgb = request->hasArg(F("CR")); cctICused = request->hasArg(F("IC")); - strip.cctBlending = request->arg(F("CB")).toInt(); - Bus::setCCTBlend(strip.cctBlending); + uint8_t cctBlending = request->arg(F("CB")).toInt(); + Bus::setCCTBlend(cctBlending); Bus::setGlobalAWMode(request->arg(F("AW")).toInt()); strip.setTargetFps(request->arg(F("FR")).toInt()); useGlobalLedBuffer = request->hasArg(F("LD")); @@ -313,11 +313,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) gammaCorrectCol = false; } - fadeTransition = request->hasArg(F("TF")); - modeBlending = request->hasArg(F("EB")); t = request->arg(F("TD")).toInt(); if (t >= 0) transitionDelayDefault = t; - strip.paletteFade = request->hasArg(F("PF")); t = request->arg(F("TP")).toInt(); randomPaletteChangeTime = MIN(255,MAX(1,t)); useHarmonicRandomPalette = request->hasArg(F("TH")); @@ -1124,7 +1121,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("TT=")); if (pos > 0) transitionDelay = getNumVal(&req, pos); - if (fadeTransition) strip.setTransition(transitionDelay); + strip.setTransition(transitionDelay); //set time (unix timestamp) pos = req.indexOf(F("ST=")); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 100ace1663..d2f49144ab 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -225,10 +225,8 @@ void parseNotifyPacket(uint8_t *udpIn) { // set transition time before making any segment changes if (version > 3) { - if (fadeTransition) { - jsonTransitionOnce = true; - strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00)); - } + jsonTransitionOnce = true; + strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00)); } //apply colors from notification to main segment, only if not syncing full segments diff --git a/wled00/wled.cpp b/wled00/wled.cpp index eb78608516..8f64a10e96 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -538,10 +538,10 @@ void WLED::beginStrip() } else { // fix for #3196 if (bootPreset > 0) { - bool oldTransition = fadeTransition; // workaround if transitions are enabled - fadeTransition = false; // ignore transitions temporarily + uint16_t oldTransition = strip.getTransition(); // workaround if transitions are enabled + strip.setTransition(0); // ignore transitions temporarily strip.setColor(0, BLACK); // set all segments black - fadeTransition = oldTransition; // restore transitions + strip.setTransition(oldTransition); // restore transitions col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated() } briLast = briS; bri = 0; diff --git a/wled00/wled.h b/wled00/wled.h index 5a9b8dcf5a..8e21343f30 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -545,8 +545,6 @@ WLED_GLOBAL bool wasConnected _INIT(false); WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same // transitions -WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color -WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 4f2c14d474..a43c23e8d3 100755 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -220,7 +220,7 @@ void loadSettingsFromEEPROM() if (lastEEPROMversion > 7) { - strip.paletteFade = EEPROM.read(374); + //strip.paletteFade = EEPROM.read(374); strip.paletteBlend = EEPROM.read(382); for (int i = 0; i < 8; ++i) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 3915d9b0e5..e9c1fac46c 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -357,7 +357,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("CCT"),correctWB); sappend('c',SET_F("IC"),cctICused); sappend('c',SET_F("CR"),cctFromRgb); - sappend('v',SET_F("CB"),strip.cctBlending); + sappend('v',SET_F("CB"),Bus::getCCTBlend()); sappend('v',SET_F("FR"),strip.getTargetFps()); sappend('v',SET_F("AW"),Bus::getGlobalAWMode()); sappend('c',SET_F("LD"),useGlobalLedBuffer); @@ -445,10 +445,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("GB"),gammaCorrectBri); sappend('c',SET_F("GC"),gammaCorrectCol); dtostrf(gammaCorrectVal,3,1,nS); sappends('s',SET_F("GV"),nS); - sappend('c',SET_F("TF"),fadeTransition); - sappend('c',SET_F("EB"),modeBlending); sappend('v',SET_F("TD"),transitionDelayDefault); - sappend('c',SET_F("PF"),strip.paletteFade); sappend('v',SET_F("TP"),randomPaletteChangeTime); sappend('c',SET_F("TH"),useHarmonicRandomPalette); sappend('v',SET_F("BF"),briMultiplier); From ef017fd343bc0329125c52681015fc6b85284be1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 14 Apr 2024 15:34:59 +0200 Subject: [PATCH 04/18] Revert FX.cpp --- wled00/FX.cpp | 61 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e75e20cd2d..14341f5b99 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1211,9 +1211,8 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" */ uint16_t mode_fireworks() { if (SEGLEN == 1) return mode_static(); - const unsigned width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const unsigned height = SEGMENT.virtualHeight(); - const unsigned dimension = width * height; + const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); + const uint16_t height = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1221,19 +1220,19 @@ uint16_t mode_fireworks() { } SEGMENT.fade_out(128); - bool valid1 = (SEGENV.aux0 < dimension); - bool valid2 = (SEGENV.aux1 < dimension); - unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte + bool valid1 = (SEGENV.aux0 < 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; 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 (!SEGENV.step) SEGMENT.blur(dimension > 100 ? 16 : 8); + if (!SEGENV.step) SEGMENT.blur(16); 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 - for (unsigned i=0; i> 1)) == 0) { - unsigned index = random16(dimension); + uint16_t index = random16(width*height); x = index % width; y = index / width; uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); @@ -2067,41 +2066,41 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const unsigned ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (unsigned i = 0; i < SEGLEN; i++) { - unsigned cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); - unsigned minTemp = (i 1; k--) { + for (int k = SEGLEN -1; k > 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } // Step 3. Randomly ignite new 'sparks' of heat near the bottom if (random8() <= SEGMENT.intensity) { - unsigned y = random8(ignition); - unsigned boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! + uint8_t y = random8(ignition); + uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost)); } } // Step 4. Map from heat cells to LED colors for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, LINEARBLEND_NOWRAP)); + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND)); } } }; - for (unsigned stripNr=0; stripNr 100 ? 32 : 0); + if (SEGMENT.is2D()) SEGMENT.blur(32); if (it != SEGENV.step) SEGENV.step = it; @@ -4857,9 +4856,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const unsigned cols = SEGMENT.virtualWidth(); - const unsigned rows = SEGMENT.virtualHeight(); - unsigned x, y; + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + uint16_t x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase @@ -4878,7 +4877,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit - SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); + SEGMENT.blur(16); return FRAMETIME; } // mode_2DBlackHole() @@ -6437,8 +6436,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const unsigned cols = SEGMENT.virtualWidth(); - const unsigned rows = SEGMENT.virtualHeight(); + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6450,21 +6449,21 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fadeToBlackBy(SEGMENT.speed); long t = strip.now / 2; - for (unsigned i = 0; i < cols; i++) { - unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + for (int i = 0; i < cols; i++) { + uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available if (um_data) { thisVal /= 32; // reduce intensity of inoise8() thisVal *= volumeSmth; } - unsigned thisMax = map(thisVal, 0, 512, 0, rows); + uint16_t thisMax = map(thisVal, 0, 512, 0, rows); - for (unsigned j = 0; j < thisMax; j++) { + for (int j = 0; j < thisMax; j++) { SEGMENT.addPixelColorXY(i, 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(sqrt16(cols*rows) > 100 ? 16 : 0); + SEGMENT.blur(16); return FRAMETIME; } // mode_2DWaverly() From da484b07f5bda6d0a955e389658971e4b18f18f1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 2 Jun 2024 21:30:44 +0200 Subject: [PATCH 05/18] Use transition style for palette and color change - as requested by @willmmiles & @tkadauke --- wled00/FX.h | 2 ++ wled00/FX_fcn.cpp | 61 +++++++++++++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index cb9cafb238..acca0b20db 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -457,6 +457,7 @@ typedef struct Segment { #else uint32_t _colorT[NUM_COLORS]; #endif + uint8_t _palTid; // previous palette uint8_t _briT; // temporary brightness uint8_t _cctT; // temporary CCT CRGBPalette16 _palT; // temporary palette @@ -594,6 +595,7 @@ typedef struct Segment { uint16_t progress(void); // transition progression between 0-65535 uint8_t currentBri(bool useCct = false); // current segment brightness/CCT (blended while in transition) uint8_t currentMode(void); // currently active effect/mode (while in transition) + uint8_t currentPalette(void); // currently active palette (while in transition) uint32_t currentColor(uint8_t slot); // currently active segment color (blended while in transition) CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); void setCurrentPalette(void); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 29302bfb2c..df06e56ad0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -291,6 +291,7 @@ void Segment::startTransition(uint16_t dur) { //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); loadPalette(_t->_palT, palette); + _t->_palTid = palette; _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND @@ -442,27 +443,43 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE && mode != _t->_modeT) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); #endif } +uint8_t IRAM_ATTR Segment::currentPalette() { + unsigned prog = progress(); + if (prog < 0xFFFFU) { +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE && _modeBlend) return _t->_palTid; +#else + return _t->_palTid; +#endif + } + return palette; +} + void Segment::setCurrentPalette() { loadPalette(_currentPalette, palette); unsigned prog = progress(); + if (prog < 0xFFFFU) { #ifndef WLED_DISABLE_MODE_BLEND - if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend && mode != _t->_modeT) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette - else + if (blendingStyle > BLEND_STYLE_FADE) { + //if (_modeBlend) loadPalette(_currentPalette, _t->_palTid); // not fade/blend transition, each effect uses its palette + if (_modeBlend) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + } else #endif - if (prog < 0xFFFFU) { - // blend palettes - // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) - // minimum blend time is 100ms maximum is 65535ms - unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); - _currentPalette = _t->_palT; // copy transitioning/temporary palette + { + // blend palettes + // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) + // minimum blend time is 100ms maximum is 65535ms + unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; + for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); + _currentPalette = _t->_palT; // copy transitioning/temporary palette + } } } @@ -719,7 +736,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { - bool invert = _clipStart > _clipStop; + bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { @@ -727,12 +744,13 @@ bool IRAM_ATTR Segment::isPixelClipped(int i) { if (len < 2) return false; unsigned shuffled = hashInt(i) % len; unsigned pos = (shuffled * 0xFFFFU) / len; - return progress() <= pos; + return (progress() <= pos) ^ _modeBlend; } const bool iInside = (i >= start && i < stop); - if (!invert && iInside) return _modeBlend; - if ( invert && !iInside) return _modeBlend; - return !_modeBlend; + //if (!invert && iInside) return _modeBlend; + //if ( invert && !iInside) return _modeBlend; + //return !_modeBlend; + return !iInside ^ invert ^ _modeBlend; // thanks @willmmiles (https://github.com/Aircoookie/WLED/pull/3877#discussion_r1554633876) } #endif return false; @@ -1220,7 +1238,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ uint32_t color = gamma32(currentColor(mcol)); // default palette or no RGB support on segment - if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); + if ((currentPalette() == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); unsigned paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); @@ -1313,6 +1331,7 @@ void WS2812FX::service() { now = nowUp + timebase; if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; bool doShow = false; + int pal = -1; // optimise palette loading _isServicing = true; _segment_index = 0; @@ -1339,7 +1358,8 @@ void WS2812FX::service() { _colors_t[0] = gamma32(seg.currentColor(0)); _colors_t[1] = gamma32(seg.currentColor(1)); _colors_t[2] = gamma32(seg.currentColor(2)); - seg.setCurrentPalette(); // load actual palette + if (seg.currentPalette() != pal) seg.setCurrentPalette(); // load actual palette + pal = seg.currentPalette(); // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio // when cctFromRgb is true we implicitly calculate WW and CW from RGB values if (cctFromRgb) BusManager::setSegmentCCT(-1); @@ -1350,10 +1370,10 @@ void WS2812FX::service() { // The blending will largely depend on the effect behaviour since actual output (LEDs) may be // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. - [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition #ifndef WLED_DISABLE_MODE_BLEND + uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition Segment::setClippingRect(0, 0); // disable clipping (just in case) - if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles + if (seg.isInTransition()) { // set clipping rectangle // new mode is run inside clipping area and old mode outside clipping area unsigned p = seg.progress(); @@ -1404,11 +1424,12 @@ void WS2812FX::service() { } } delay = (*_mode[seg.mode])(); // run new/current mode - if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles + if (seg.isInTransition()) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) + seg.setCurrentPalette(); // load actual palette unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay From bee75a450827ad571bc59241386b425a153981d1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 23 Jun 2024 14:14:26 +0200 Subject: [PATCH 06/18] Hide 2D blending styles on non-2D set-up --- wled00/data/index.htm | 16 ++++++++-------- wled00/data/index.js | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e91bc10ba5..d7ef3707ac 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -275,14 +275,14 @@ - - - - - - - - + + + + + + + +

diff --git a/wled00/data/index.js b/wled00/data/index.js index 4cb7079651..1f8a916168 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -677,8 +677,10 @@ function parseInfo(i) { isM = mw>0 && mh>0; if (!isM) { gId("filter2D").classList.add('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';}); } else { gId("filter2D").classList.remove('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';}); } // if (i.noaudio) { // gId("filterVol").classList.add("hide"); From 0275bd1d45660ae1630792c835e65fd5a34c40ca Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 5 Jul 2024 21:23:59 +0200 Subject: [PATCH 07/18] On/Off blending respected --- wled00/FX_fcn.cpp | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 3ebf0c4483..af9b3218c4 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -433,9 +433,17 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint8_t IRAM_ATTR Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); - if (prog < 0xFFFFU) return _t->_modeT; -#endif + if (prog == 0xFFFFU) return mode; + if (blendingStyle > BLEND_STYLE_FADE) { + // workaround for on/off transition to respect blending style + uint8_t modeT = (bri != briT) && bri ? FX_MODE_STATIC : _t->_modeT; // On/Off transition active (bri!=briT) and final bri>0 : old mode is STATIC + uint8_t modeS = (bri != briT) && !bri ? FX_MODE_STATIC : mode; // On/Off transition active (bri!=briT) and final bri==0 : new mode is STATIC + return _modeBlend ? modeT : modeS; + } + return _modeBlend ? _t->_modeT : mode; +#else return mode; +#endif } uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { @@ -443,7 +451,12 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE) { + // workaround for on/off transition to respect blending style + uint32_t colT = (bri != briT) && bri ? BLACK : _t->_segT._colorT[slot]; // On/Off transition active (bri!=briT) and final bri>0 : old color is BLACK + uint32_t colS = (bri != briT) && !bri ? BLACK : colors[slot]; // On/Off transition active (bri!=briT) and final bri==0 : new color is BLACK + return _modeBlend ? colT : colS; + } return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); @@ -559,6 +572,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black } + //DEBUG_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c); startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast @@ -572,6 +586,7 @@ void Segment::setCCT(uint16_t k) { k = (k - 1900) >> 5; } if (cct == k) return; + //DEBUG_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k); startTransition(strip.getTransition()); // start transition prior to change cct = k; stateChanged = true; // send UDP/WS broadcast @@ -579,6 +594,7 @@ void Segment::setCCT(uint16_t k) { void Segment::setOpacity(uint8_t o) { if (opacity == o) return; + //DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); startTransition(strip.getTransition()); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast @@ -599,6 +615,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx != mode) { #ifndef WLED_DISABLE_MODE_BLEND + //DEBUG_PRINTF_P(PSTR("- Starting effect transition: %d\n"), fx); startTransition(strip.getTransition()); // set effect transitions #endif mode = fx; @@ -630,6 +647,7 @@ void Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { + //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); startTransition(strip.getTransition()); palette = pal; stateChanged = true; // send UDP/WS broadcast @@ -1373,7 +1391,6 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. #ifndef WLED_DISABLE_MODE_BLEND - uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition Segment::setClippingRect(0, 0); // disable clipping (just in case) if (seg.isInTransition()) { // set clipping rectangle @@ -1425,14 +1442,20 @@ void WS2812FX::service() { break; } } - delay = (*_mode[seg.mode])(); // run new/current mode + delay = (*_mode[seg.currentMode()])(); // run new/current mode if (seg.isInTransition()) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) - seg.setCurrentPalette(); // load actual palette - unsigned d2 = (*_mode[tmpMode])(); // run old mode + _colors_t[0] = gamma32(seg.currentColor(0)); + _colors_t[1] = gamma32(seg.currentColor(1)); + _colors_t[2] = gamma32(seg.currentColor(2)); + if (seg.currentPalette() != pal) { + seg.setCurrentPalette(); // load actual palette + pal = seg.currentPalette(); + } + unsigned d2 = (*_mode[seg.currentMode()])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore From c03422ee3703dd604c909a58997442c0f2c532e4 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 30 Jul 2024 17:26:50 +0200 Subject: [PATCH 08/18] Push variants --- wled00/FX.h | 28 +++++++++++--------- wled00/FX_2Dfcn.cpp | 61 ++++++++++++++++++++++++++++++++++++++----- wled00/FX_fcn.cpp | 30 ++++++++++++++++++--- wled00/data/index.htm | 28 +++++++++++--------- 4 files changed, 113 insertions(+), 34 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 136f8e18bf..3dc69e987a 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -325,18 +325,22 @@ #define BLEND_STYLE_FAIRY_DUST 1 #define BLEND_STYLE_SWIPE_RIGHT 2 #define BLEND_STYLE_SWIPE_LEFT 3 -#define BLEND_STYLE_PINCH_OUT 4 -#define BLEND_STYLE_INSIDE_OUT 5 -#define BLEND_STYLE_SWIPE_UP 6 -#define BLEND_STYLE_SWIPE_DOWN 7 -#define BLEND_STYLE_OPEN_H 8 -#define BLEND_STYLE_OPEN_V 9 -#define BLEND_STYLE_PUSH_TL 10 -#define BLEND_STYLE_PUSH_TR 11 -#define BLEND_STYLE_PUSH_BR 12 -#define BLEND_STYLE_PUSH_BL 13 - -#define BLEND_STYLE_COUNT 14 +#define BLEND_STYLE_PUSH_RIGHT 4 +#define BLEND_STYLE_PUSH_LEFT 5 +#define BLEND_STYLE_PINCH_OUT 6 +#define BLEND_STYLE_INSIDE_OUT 7 +#define BLEND_STYLE_SWIPE_UP 8 +#define BLEND_STYLE_SWIPE_DOWN 9 +#define BLEND_STYLE_OPEN_H 10 +#define BLEND_STYLE_OPEN_V 11 +#define BLEND_STYLE_PUSH_UP 12 +#define BLEND_STYLE_PUSH_DOWN 13 +#define BLEND_STYLE_PUSH_TL 14 +#define BLEND_STYLE_PUSH_TR 15 +#define BLEND_STYLE_PUSH_BR 16 +#define BLEND_STYLE_PUSH_BL 17 + +#define BLEND_STYLE_COUNT 18 typedef enum mapping1D2D { diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index a62aa330ec..846c786758 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -202,15 +202,39 @@ bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit + + int vW = virtualWidth(); + int vH = virtualHeight(); + +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && + (blendingStyle == BLEND_STYLE_PUSH_RIGHT || + blendingStyle == BLEND_STYLE_PUSH_LEFT || + blendingStyle == BLEND_STYLE_PUSH_UP || + blendingStyle == BLEND_STYLE_PUSH_DOWN || + blendingStyle == BLEND_STYLE_PUSH_TL || + blendingStyle == BLEND_STYLE_PUSH_TR || + blendingStyle == BLEND_STYLE_PUSH_BR || + blendingStyle == BLEND_STYLE_PUSH_BL)) { + unsigned prog = 0xFFFF - progress(); + unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF; + unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX; + else x += dX; + if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY; + else y += dY; + } +#endif + + if (x >= vW || y >= vH || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); } - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels @@ -294,9 +318,34 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + + int vW = virtualWidth(); + int vH = virtualHeight(); + +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && + (blendingStyle == BLEND_STYLE_PUSH_RIGHT || + blendingStyle == BLEND_STYLE_PUSH_LEFT || + blendingStyle == BLEND_STYLE_PUSH_UP || + blendingStyle == BLEND_STYLE_PUSH_DOWN || + blendingStyle == BLEND_STYLE_PUSH_TL || + blendingStyle == BLEND_STYLE_PUSH_TR || + blendingStyle == BLEND_STYLE_PUSH_BR || + blendingStyle == BLEND_STYLE_PUSH_BL)) { + unsigned prog = 0xFFFF - progress(); + unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF; + unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX; + else x += dX; + if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY; + else y += dY; + } +#endif + + if (x >= vW || y >= vH || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit + + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 4af961764c..583496de01 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -782,7 +782,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #endif i &= 0xFFFF; - if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit + int vL = virtualLength(); + if (i >= vL || i < 0) return; // if pixel would fall out of segment just exit #ifndef WLED_DISABLE_2D if (is2D()) { @@ -890,7 +891,16 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif - if (isPixelClipped(i)) return; // handle clipping on 1D +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + unsigned prog = 0xFFFF - progress(); + unsigned dI = prog * vL / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; + else i += dI; + } +#endif + + if (i >= vL || i < 0 || isPixelClipped(i)) return; // handle clipping on 1D unsigned len = length(); uint8_t _bri_t = currentBri(); @@ -978,6 +988,9 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) #endif i &= 0xFFFF; + int vL = virtualLength(); + if (i >= vL || i < 0) return 0; + #ifndef WLED_DISABLE_2D if (is2D()) { unsigned vH = virtualHeight(); // segment height in logical pixels @@ -1029,9 +1042,18 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) } #endif - if (isPixelClipped(i)) return 0; // handle clipping on 1D +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + unsigned prog = 0xFFFF - progress(); + unsigned dI = prog * vL / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; + else i += dI; + } +#endif + + if (i >= vL || i < 0 || isPixelClipped(i)) return 0; // handle clipping on 1D - if (reverse) i = virtualLength() - i - 1; + if (reverse) i = vL - i - 1; i *= groupLength(); i += start; /* offset/phase */ diff --git a/wled00/data/index.htm b/wled00/data/index.htm index d7ef3707ac..df867d3a58 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -271,18 +271,22 @@

From 365c1987ed8f5f8070081c2619d1b784ff354a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Thu, 1 Aug 2024 10:24:40 +0200 Subject: [PATCH 09/18] Missing clipping fix - small speed optimisations --- wled00/FX.h | 6 +++--- wled00/FX_2Dfcn.cpp | 22 ++++++++++++---------- wled00/FX_fcn.cpp | 35 ++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 3dc69e987a..3abc811665 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -351,7 +351,7 @@ typedef enum mapping1D2D { M12_sPinwheel = 4 } mapping1D2D_t; -// segment, 80 bytes +// segment, 68 bytes typedef struct Segment { public: uint16_t start; // start index / start X coordinate 2D (left) @@ -639,7 +639,7 @@ typedef struct Segment { uint16_t virtualHeight(void) const; // segment height in virtual pixels (accounts for groupping and spacing) uint16_t nrOfVStrips(void) const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D - uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment + uint16_t XY(int x, int y); // support function to get relative index within segment void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } @@ -678,7 +678,7 @@ typedef struct Segment { inline void blur2d(fract8 blur_amount) { blur(blur_amount); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline uint16_t XY(uint16_t x, uint16_t y) { return x; } + inline uint16_t XY(int x, int y) { return x; } inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 846c786758..493cb8963a 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -161,8 +161,7 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) -{ +uint16_t IRAM_ATTR Segment::XY(int x, int y) { unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; @@ -207,7 +206,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) int vH = virtualHeight(); #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_UP || @@ -239,13 +238,16 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) x *= 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 + + int W = width(); + int H = height(); + if (x >= W || y >= H) return; // if pixel would fall out of segment just exit uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally 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 // if blending modes, blend with underlying pixel @@ -255,15 +257,15 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) strip.setPixelColorXY(start + xX, startY + yY, tmpCol); if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); + if (transpose) strip.setPixelColorXY(start + xX, startY + H - yY - 1, tmpCol); + else strip.setPixelColorXY(start + W - xX - 1, startY + yY, tmpCol); } if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); + if (transpose) strip.setPixelColorXY(start + W - xX - 1, startY + yY, tmpCol); + else strip.setPixelColorXY(start + xX, startY + H - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); + strip.setPixelColorXY(W - xX - 1, H - yY - 1, tmpCol); } } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 583496de01..dbbd24b7d3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -754,7 +754,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { - bool invert = _clipStart > _clipStop; // ineverted start & stop + bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { @@ -892,7 +892,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #endif #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + // if we blend using "push" style we need to "shift" new mode to left or right + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { unsigned prog = 0xFFFF - progress(); unsigned dI = prog * vL / 0xFFFF; if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; @@ -1043,7 +1044,7 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) #endif #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { unsigned prog = 0xFFFF - progress(); unsigned dI = prog * vL / 0xFFFF; if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; @@ -1425,43 +1426,47 @@ void WS2812FX::service() { unsigned dw = p * w / 0xFFFFU + 1; unsigned dh = p * h / 0xFFFFU + 1; switch (blendingStyle) { - case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, w, 0, h); break; case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + case BLEND_STYLE_PUSH_RIGHT: // left-to-right Segment::setClippingRect(0, dw, 0, h); break; - case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_PUSH_LEFT: // right-to-left Segment::setClippingRect(w - dw, w, 0, h); break; - case BLEND_STYLE_PINCH_OUT: // corners + case BLEND_STYLE_PINCH_OUT: // corners Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! break; - case BLEND_STYLE_INSIDE_OUT: // outward + case BLEND_STYLE_INSIDE_OUT: // outward Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); break; - case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D) Segment::setClippingRect(0, w, 0, dh); break; - case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D) Segment::setClippingRect(0, w, h - dh, h); break; - case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h); break; - case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2); break; - case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) Segment::setClippingRect(0, dw, 0, dh); break; - case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) Segment::setClippingRect(w - dw, w, 0, dh); break; - case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) Segment::setClippingRect(w - dw, w, h - dh, h); break; - case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) Segment::setClippingRect(0, dw, h - dh, h); break; } From 77723b615f5c482a63053c7a40705b073f072681 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 8 Aug 2024 21:10:27 +0200 Subject: [PATCH 10/18] Fix compiler warning --- wled00/FX_2Dfcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 36d2038b94..7aec73cadf 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -246,7 +246,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally - unsigned xX = (x+g), yY = (y+j); + int xX = (x+g), yY = (y+j); if (xX >= W || yY >= H) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND From 434ba3ffc7ac76b727fa0225bb27396c905fad80 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 28 Aug 2024 22:25:28 +0200 Subject: [PATCH 11/18] Added FX to copy a segment in 1D or 2D - copies the source segment - brightness of segment is relative to source segment - optionally shifts the color hue - invert, transpose, mirror work - if source or targets do not match in size, smallest size is copied - unused pixels fade to black (allows overlapping segments) - if invalid source ID is set, segment just fades to black - added a rgb2hsv conversion function as the fastled variant is inaccurate and buggy note: 1D to 2D and vice versa is not supported --- wled00/FX.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++- wled00/FX.h | 4 ++-- wled00/colors.cpp | 26 +++++++++++++++++++++ wled00/fcn_declare.h | 1 + 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 807594e430..ca5a40f721 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -82,6 +82,7 @@ static um_data_t* getAudioData() { return um_data; } + // effect functions /* @@ -93,7 +94,58 @@ uint16_t mode_static(void) { } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; - +/* + * Copy selected segment + */ +uint16_t mode_copy_segment(void) { + + uint32_t sourceid = SEGMENT.custom1; + SEGMENT.fadeToBlackBy(16); // fades out unused pixels, still allows overlay (also fades out if invalid ID is set) + if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) return FRAMETIME; // invalid source + CRGB sourcecolor; + if (strip._segments[sourceid].isActive()) { + // note: copying 1D to 2D as well as 2D to 1D is not supported + if(SEGMENT.is2D() && strip._segments[sourceid].is2D()) { // 2D setup + uint32_t cx, cy; // sizes to copy + cx = std::min(strip._segments[sourceid].virtualWidth(), SEGMENT.virtualWidth()); // get smaller width + cy = std::min(strip._segments[sourceid].virtualHeight(), SEGMENT.virtualHeight()); // get smaller height + for (unsigned x = 0; x < cx; x++) { + for (unsigned y = 0; y < cy; y++) { + sourcecolor = strip._segments[sourceid].getPixelColorXY(x, y); + if(SEGMENT.custom2 > 0) // color shifting enabled + { + CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV + pxHSV.h += SEGMENT.custom2; // shift hue + hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB + + } + SEGMENT.setPixelColorXY(x, y, sourcecolor); + //SEGMENT.setPixelColorXY(x, y, strip._segments[sourceid].getPixelColorXY(x, y)); //use this for no colorshift option + } + + } + } + else if(!SEGMENT.is2D() && !strip._segments[sourceid].is2D()) { // 1D strip + uint32_t cl; // length to copy + cl = std::min(strip._segments[sourceid].virtualLength(), SEGMENT.virtualLength()); // get smaller length + for (unsigned i = 0; i < cl; i++) { + sourcecolor = strip._segments[sourceid].getPixelColor(i); + if(SEGMENT.custom2 > 0) // color shifting enabled + { + CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV + pxHSV.h += SEGMENT.custom2; // shift hue + sourcecolor = (CRGB)pxHSV; // convert back to RGB + } + SEGMENT.setPixelColor(i, sourcecolor); + // SEGMENT.setPixelColor(i, strip._segments[sourceid].getPixelColor(i)); //use this for no colorshift option + } + } + } + return FRAMETIME; +} +//static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID;;;12;c1=0,c2=0"; +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID,Color shift;;;12;c1=0,c2=0"; + /* * Blink/strobe function * Alternate between color1 and color2 @@ -7835,6 +7887,7 @@ void WS2812FX::setupEffectData() { _modeData.push_back(_data_RESERVED); } // now replace all pre-allocated effects + addEffect(FX_MODE_COPY, &mode_copy_segment, _data_FX_MODE_COPY); // --- 1D non-audio effects --- addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK); addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH); diff --git a/wled00/FX.h b/wled00/FX.h index b1b1fbcb78..243f51c5a9 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -317,8 +317,8 @@ #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 - -#define MODE_COUNT 187 +#define FX_MODE_COPY 187 +#define MODE_COUNT 188 typedef enum mapping1D2D { M12_Pixels = 0, diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ebea7ea055..903b79a419 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -225,6 +225,32 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb } } +CHSV rgb2hsv(const CRGB& rgb) // convert rgb to hsv, more accurate and faster than fastled version +{ + int32_t r = rgb.r; + int32_t g = rgb.g; + int32_t b = rgb.b; + CHSV hsv = CHSV(0, 0, 0); + int32_t minval, maxval, delta; + minval = min(r, g); + minval = min(minval, b); + maxval = max(r, g); + maxval = max(maxval, b); + if (maxval == 0) return hsv; // black + hsv.v = maxval; + delta = maxval - minval; + hsv.s = (255 * delta) / maxval; + if (hsv.s == 0) return hsv; // gray value + int32_t h; //calculate hue + if (maxval == r) + h = (43 * (g - b)) / delta; + else if (maxval == g) h = 85 + (43 * (b - r)) / delta; + else h = 171 + (43 * (r - g)) / delta; + if(h < 0) h += 256; + hsv.h = h; + return hsv; +} + //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc { diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d95b8ef8e4..ead60d89d7 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -85,6 +85,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(void); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb +CHSV rgb2hsv(const CRGB& rgb); //rgb to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO From ebd8a10cefdfa83f289961de8d3d6ef5fa6e6da6 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 3 Sep 2024 17:20:16 +0200 Subject: [PATCH 12/18] Prevent styles on 1px segments --- wled00/FX_fcn.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a5e007e89d..f825cecd3b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1428,6 +1428,8 @@ void WS2812FX::service() { unsigned h = seg.virtualHeight(); unsigned dw = p * w / 0xFFFFU + 1; unsigned dh = p * h / 0xFFFFU + 1; + unsigned orgBS = blendingStyle; + if (w*h == 1) blendingStyle = BLEND_STYLE_FADE; // disable belending for single pixel segments (use fade instead) switch (blendingStyle) { case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, w, 0, h); @@ -1473,9 +1475,8 @@ void WS2812FX::service() { Segment::setClippingRect(0, dw, h - dh, h); break; } - } - delay = (*_mode[seg.currentMode()])(); // run new/current mode - if (seg.isInTransition()) { + delay = (*_mode[seg.currentMode()])(); // run new/current mode + // now run old/previous mode Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) @@ -1491,10 +1492,10 @@ void WS2812FX::service() { seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore - } -#else - delay = (*_mode[seg.mode])(); // run effect mode + blendingStyle = orgBS; // restore blending style if it was modified for single pixel segment + } else #endif + delay = (*_mode[seg.mode])(); // run effect mode (not in transition) seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments From 24df6bf5949ba3a70ea8888a16333171aa4d4a11 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 4 Sep 2024 21:44:52 +0200 Subject: [PATCH 13/18] Added copying source settings target segment now also copies the source settings (mirror, grouping, spacing, transpose) so the target looks exactly like the source. inverting can still be set independently. note: the bug in line 214 of FX_2Dfcn.cpp missing `start` needs to be fixed for it to work properly in 2D. --- wled00/FX.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ca5a40f721..985835175e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -98,14 +98,20 @@ static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; * Copy selected segment */ uint16_t mode_copy_segment(void) { - - uint32_t sourceid = SEGMENT.custom1; + uint32_t sourceid = SEGMENT.custom3; SEGMENT.fadeToBlackBy(16); // fades out unused pixels, still allows overlay (also fades out if invalid ID is set) if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) return FRAMETIME; // invalid source CRGB sourcecolor; + //copy source segment settings (reverse can be set independently) + SEGMENT.mirror = strip._segments[sourceid].mirror; + SEGMENT.grouping = strip._segments[sourceid].grouping; + SEGMENT.spacing = strip._segments[sourceid].spacing; if (strip._segments[sourceid].isActive()) { // note: copying 1D to 2D as well as 2D to 1D is not supported if(SEGMENT.is2D() && strip._segments[sourceid].is2D()) { // 2D setup + //copy 2D segment settings + SEGMENT.mirror_y = strip._segments[sourceid].mirror_y; + SEGMENT.transpose = strip._segments[sourceid].transpose; uint32_t cx, cy; // sizes to copy cx = std::min(strip._segments[sourceid].virtualWidth(), SEGMENT.virtualWidth()); // get smaller width cy = std::min(strip._segments[sourceid].virtualHeight(), SEGMENT.virtualHeight()); // get smaller height @@ -116,13 +122,10 @@ uint16_t mode_copy_segment(void) { { CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV pxHSV.h += SEGMENT.custom2; // shift hue - hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB - + hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB } - SEGMENT.setPixelColorXY(x, y, sourcecolor); - //SEGMENT.setPixelColorXY(x, y, strip._segments[sourceid].getPixelColorXY(x, y)); //use this for no colorshift option + SEGMENT.setPixelColorXY(x, y, sourcecolor); } - } } else if(!SEGMENT.is2D() && !strip._segments[sourceid].is2D()) { // 1D strip @@ -143,8 +146,7 @@ uint16_t mode_copy_segment(void) { } return FRAMETIME; } -//static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID;;;12;c1=0,c2=0"; -static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID,Color shift;;;12;c1=0,c2=0"; +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,,Color shift,ID;;;12;c2=0,c3=0"; /* * Blink/strobe function From 696f74b43a43fe68640ca53b66741244da485578 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 7 Sep 2024 11:05:09 +0200 Subject: [PATCH 14/18] Update integrating new ideas as discussed - added color adjust function with support for hue, lightness and brightness - colors can now be adjusted in hue, saturation and brightness - added `getRenderedPixelXY()` function (credit @willmmiles) - source is now accurately copied, even 1D->2D is possible - fix for segment offset not being zero in 2D in `getRenderedPixelXY()` - added checkmark to copy source grouping/spacing (may remove again) - tested many different scenarios, everything seems to work --- wled00/FX.cpp | 88 ++++++++++++++++++++++---------------------- wled00/colors.cpp | 15 ++++++++ wled00/fcn_declare.h | 1 + 3 files changed, 61 insertions(+), 43 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 985835175e..a17214a7d7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -94,60 +94,62 @@ uint16_t mode_static(void) { } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; + /* - * Copy selected segment + * Copy selected segment and perform (optional) color adjustments */ +// TODO: what is the correct place to put getRenderedPixelXY()? +static CRGB getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { + // We read pixels back following mirror/reverse/transpose but ignoring grouping + // For every group-length pixels, add spacing + x *= seg.groupLength(); // expand to physical pixels + y *= seg.groupLength(); // expand to physical pixels + if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black + uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not + return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); +} + uint16_t mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; - SEGMENT.fadeToBlackBy(16); // fades out unused pixels, still allows overlay (also fades out if invalid ID is set) - if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) return FRAMETIME; // invalid source - CRGB sourcecolor; - //copy source segment settings (reverse can be set independently) - SEGMENT.mirror = strip._segments[sourceid].mirror; - SEGMENT.grouping = strip._segments[sourceid].grouping; - SEGMENT.spacing = strip._segments[sourceid].spacing; - if (strip._segments[sourceid].isActive()) { - // note: copying 1D to 2D as well as 2D to 1D is not supported - if(SEGMENT.is2D() && strip._segments[sourceid].is2D()) { // 2D setup - //copy 2D segment settings - SEGMENT.mirror_y = strip._segments[sourceid].mirror_y; - SEGMENT.transpose = strip._segments[sourceid].transpose; - uint32_t cx, cy; // sizes to copy - cx = std::min(strip._segments[sourceid].virtualWidth(), SEGMENT.virtualWidth()); // get smaller width - cy = std::min(strip._segments[sourceid].virtualHeight(), SEGMENT.virtualHeight()); // get smaller height - for (unsigned x = 0; x < cx; x++) { - for (unsigned y = 0; y < cy; y++) { - sourcecolor = strip._segments[sourceid].getPixelColorXY(x, y); - if(SEGMENT.custom2 > 0) // color shifting enabled - { - CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV - pxHSV.h += SEGMENT.custom2; // shift hue - hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB - } - SEGMENT.setPixelColorXY(x, y, sourcecolor); - } - } - } - else if(!SEGMENT.is2D() && !strip._segments[sourceid].is2D()) { // 1D strip + if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) { // invalid source + SEGMENT.fadeToBlackBy(15); // fade out, clears pixels and allows overlapping segments + return FRAMETIME; + } + uint32_t spacing, grouping; + if(SEGMENT.check2) { // copy source segment spacing & grouping (preliminary for testing, may remove again) + spacing = SEGMENT.spacing; + grouping = SEGMENT.grouping; + SEGMENT.spacing = strip._segments[sourceid].spacing; + SEGMENT.grouping = strip._segments[sourceid].grouping; + } + if (strip._segments[sourceid].isActive()) { + if(!strip._segments[sourceid].is2D() || !SEGMENT.is2D()) { // 1D source or 1D target; source can be expanded into 2D uint32_t cl; // length to copy - cl = std::min(strip._segments[sourceid].virtualLength(), SEGMENT.virtualLength()); // get smaller length - for (unsigned i = 0; i < cl; i++) { - sourcecolor = strip._segments[sourceid].getPixelColor(i); - if(SEGMENT.custom2 > 0) // color shifting enabled - { - CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV - pxHSV.h += SEGMENT.custom2; // shift hue - sourcecolor = (CRGB)pxHSV; // convert back to RGB - } - SEGMENT.setPixelColor(i, sourcecolor); - // SEGMENT.setPixelColor(i, strip._segments[sourceid].getPixelColor(i)); //use this for no colorshift option + for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { + CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); + adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment + SEGMENT.setPixelColor(i, sourcecolor); + } + } else { // 2D source, note: 2D to 1D just copies the first row (y=0, x=0 to x=source.width) + for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { + for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { + CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); + adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment + SEGMENT.setPixelColorXY(x, y, sourcecolor); + } } } } + //restore settings -> if settings change during rendiring, this will reset them... not ideal but cannot be avoided + if(SEGMENT.check2) { + SEGMENT.spacing = spacing; + SEGMENT.grouping = grouping; + } return FRAMETIME; } -static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,,Color shift,ID;;;12;c2=0,c3=0"; +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Copy Grouping & Spacing;;;12;ix=0,c1=0,c2=0,c3=0,o2=0"; + /* * Blink/strobe function * Alternate between color1 and color2 diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 903b79a419..d42b7e68d7 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -88,6 +88,21 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) return scaledcolor; } +/* + * color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate! + shifts hue, increase brightness, decreases saturation (if not black) + note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 + */ +void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten) { + if(hueShift + lighten + brighten == 0) return; // no change + CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV + if(pxHSV.v == 0) return; // do not change black pixels + pxHSV.h += hueShift; // shift hue + pxHSV.s = max((int32_t)0, (int32_t)pxHSV.s - lighten); // desaturate + pxHSV.v = min((uint32_t)255, (uint32_t)pxHSV.v + brighten); // increase brightness + hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB +} + void setRandomColor(byte* rgb) { lastRandomIndex = get_random_wheel_index(lastRandomIndex); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ead60d89d7..c1f102ccdc 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -81,6 +81,7 @@ class NeoGammaWLEDMethod { uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); uint32_t color_add(uint32_t,uint32_t, bool fast=false); uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); +void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(void); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } From 8aa82abc7d0fd556eed709b1a58c01d4234462b1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 8 Sep 2024 08:02:28 +0200 Subject: [PATCH 15/18] fixes and optimizations - removed 'copy grouping/spacing' option - added 'Switch axis' instead (in 2D -> 1D copy this chooses x or y axis to be copied) - optimized for code size and speed by not using CRGB (where possible) and not returning struct in `rgb2hsv()` --- wled00/FX.cpp | 41 +++++++++++++++-------------------------- wled00/colors.cpp | 34 ++++++++++++++++++---------------- wled00/fcn_declare.h | 4 ++-- 3 files changed, 35 insertions(+), 44 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a17214a7d7..895826cfce 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -99,56 +99,45 @@ static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; * Copy selected segment and perform (optional) color adjustments */ // TODO: what is the correct place to put getRenderedPixelXY()? -static CRGB getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { +static uint32_t getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { // We read pixels back following mirror/reverse/transpose but ignoring grouping // For every group-length pixels, add spacing x *= seg.groupLength(); // expand to physical pixels y *= seg.groupLength(); // expand to physical pixels if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black - uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not + #warning this check can be removed once 2D offset is fixed + uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not TODO: fix the offset return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); } uint16_t mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) { // invalid source - SEGMENT.fadeToBlackBy(15); // fade out, clears pixels and allows overlapping segments + SEGMENT.fadeToBlackBy(5); // fade out, clears pixels and allows overlapping segments return FRAMETIME; } - uint32_t spacing, grouping; - if(SEGMENT.check2) { // copy source segment spacing & grouping (preliminary for testing, may remove again) - spacing = SEGMENT.spacing; - grouping = SEGMENT.grouping; - SEGMENT.spacing = strip._segments[sourceid].spacing; - SEGMENT.grouping = strip._segments[sourceid].grouping; - } if (strip._segments[sourceid].isActive()) { - if(!strip._segments[sourceid].is2D() || !SEGMENT.is2D()) { // 1D source or 1D target; source can be expanded into 2D + uint32_t sourcecolor; + if(!strip._segments[sourceid].is2D()) { // 1D source, source can be expanded into 2D uint32_t cl; // length to copy for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { - CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); - adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment - SEGMENT.setPixelColor(i, sourcecolor); + sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); + SEGMENT.setPixelColor(i, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } - } else { // 2D source, note: 2D to 1D just copies the first row (y=0, x=0 to x=source.width) + } else { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { - for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { - CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); - adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment - SEGMENT.setPixelColorXY(x, y, sourcecolor); + for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { + if(SEGMENT.check2) sourcecolor = getRenderedPixelXY(strip._segments[sourceid], y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') + else sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); + SEGMENT.setPixelColorXY(x, y, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } } } } - //restore settings -> if settings change during rendiring, this will reset them... not ideal but cannot be avoided - if(SEGMENT.check2) { - SEGMENT.spacing = spacing; - SEGMENT.grouping = grouping; - } return FRAMETIME; } -static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Copy Grouping & Spacing;;;12;ix=0,c1=0,c2=0,c3=0,o2=0"; - +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Switch axis(2D);;;1;ix=0,c1=0,c2=0,c3=0,o2=0"; + /* * Blink/strobe function diff --git a/wled00/colors.cpp b/wled00/colors.cpp index d42b7e68d7..59456d7821 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -93,14 +93,17 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) shifts hue, increase brightness, decreases saturation (if not black) note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ -void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten) { - if(hueShift + lighten + brighten == 0) return; // no change - CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV - if(pxHSV.v == 0) return; // do not change black pixels - pxHSV.h += hueShift; // shift hue - pxHSV.s = max((int32_t)0, (int32_t)pxHSV.s - lighten); // desaturate - pxHSV.v = min((uint32_t)255, (uint32_t)pxHSV.v + brighten); // increase brightness - hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB +uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { + if(hueShift + lighten + brighten == 0) return rgb; // no change + CHSV hsv; + rgb2hsv(rgb, hsv); //convert to HSV + if(hsv.v == 0) return rgb; // do not change black pixels + hsv.h += hueShift; // shift hue + hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate + hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness + CRGB adjusted; + hsv2rgb_spectrum(hsv, adjusted); // convert back to RGB + return RGBW32(adjusted.r,adjusted.g,adjusted.b,0); } void setRandomColor(byte* rgb) @@ -240,22 +243,22 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb } } -CHSV rgb2hsv(const CRGB& rgb) // convert rgb to hsv, more accurate and faster than fastled version +void rgb2hsv(const uint32_t rgb, CHSV& hsv) // convert rgb to hsv, more accurate and faster than fastled version { - int32_t r = rgb.r; - int32_t g = rgb.g; - int32_t b = rgb.b; - CHSV hsv = CHSV(0, 0, 0); + int32_t r = (rgb>>16)&0xFF; + int32_t g = (rgb>>8)&0xFF; + int32_t b = rgb&0xFF; + hsv = CHSV(0, 0, 0); int32_t minval, maxval, delta; minval = min(r, g); minval = min(minval, b); maxval = max(r, g); maxval = max(maxval, b); - if (maxval == 0) return hsv; // black + if (maxval == 0) return; // black hsv.v = maxval; delta = maxval - minval; hsv.s = (255 * delta) / maxval; - if (hsv.s == 0) return hsv; // gray value + if (hsv.s == 0) return; // gray value int32_t h; //calculate hue if (maxval == r) h = (43 * (g - b)) / delta; @@ -263,7 +266,6 @@ CHSV rgb2hsv(const CRGB& rgb) // convert rgb to hsv, more accurate and faster th else h = 171 + (43 * (r - g)) / delta; if(h < 0) h += 256; hsv.h = h; - return hsv; } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index c1f102ccdc..80e65fd257 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -81,12 +81,12 @@ class NeoGammaWLEDMethod { uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); uint32_t color_add(uint32_t,uint32_t, bool fast=false); uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); -void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten); +uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(void); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb -CHSV rgb2hsv(const CRGB& rgb); //rgb to hsv +void rgb2hsv(const uint32_t rgb, CHSV& hsv); //rgb to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO From dd8353a2f09d85b134f2d85b8b7dc6911f39da88 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 9 Sep 2024 08:27:06 +0200 Subject: [PATCH 16/18] moved getRenderedPixelXY() into SEGMENT class, plus little improvement --- wled00/FX.cpp | 21 ++++----------------- wled00/FX.h | 1 + wled00/FX_fcn.cpp | 12 ++++++++++++ wled00/colors.cpp | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 895826cfce..2d39fe42b3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -94,22 +94,9 @@ uint16_t mode_static(void) { } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; - /* - * Copy selected segment and perform (optional) color adjustments + * Copy a segment and perform (optional) color adjustments */ -// TODO: what is the correct place to put getRenderedPixelXY()? -static uint32_t getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { - // We read pixels back following mirror/reverse/transpose but ignoring grouping - // For every group-length pixels, add spacing - x *= seg.groupLength(); // expand to physical pixels - y *= seg.groupLength(); // expand to physical pixels - if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black - #warning this check can be removed once 2D offset is fixed - uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not TODO: fix the offset - return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); -} - uint16_t mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) { // invalid source @@ -121,14 +108,14 @@ uint16_t mode_copy_segment(void) { if(!strip._segments[sourceid].is2D()) { // 1D source, source can be expanded into 2D uint32_t cl; // length to copy for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { - sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); + sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], i); SEGMENT.setPixelColor(i, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } } else { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { - if(SEGMENT.check2) sourcecolor = getRenderedPixelXY(strip._segments[sourceid], y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') - else sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); + if(SEGMENT.check2) sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') + else sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], x, y); SEGMENT.setPixelColorXY(x, y, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } } diff --git a/wled00/FX.h b/wled00/FX.h index 243f51c5a9..32ab2286bb 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -588,6 +588,7 @@ typedef struct Segment { inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif uint32_t getPixelColor(int i) const; + uint32_t getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0); // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); void fill(uint32_t c); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7a6fa40b52..f9a4253834 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -967,6 +967,18 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const return strip.getPixelColor(i); } +/* + * Read rendered pixel back (following mirror/reverse/transpose but ignoring grouping) + */ +uint32_t Segment::getRenderedPixelXY(Segment& seg, unsigned x, unsigned y) { + // For every group-length pixels, add spacing + x *= seg.groupLength(); // expand to physical pixels + y *= seg.groupLength(); // expand to physical pixels + if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black + uint32_t offset = seg.is2D() ? 0 : seg.offset; //offset in 2D segments is undefined, set to zero + return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); +} + uint8_t Segment::differs(Segment& b) const { uint8_t d = 0; if (start != b.start) d |= SEG_DIFFERS_BOUNDS; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 59456d7821..824879660a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -94,7 +94,7 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { - if(hueShift + lighten + brighten == 0) return rgb; // no change + if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change CHSV hsv; rgb2hsv(rgb, hsv); //convert to HSV if(hsv.v == 0) return rgb; // do not change black pixels From df33a36fe4436814dcac668a558a0e96b249fafd Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 16:29:33 +0200 Subject: [PATCH 17/18] rgb2hsv now returns the CHSV value instead of using a reference added alias function to accept CRGB value. this is more versatile for future uses. also removed some whitespaces --- wled00/colors.cpp | 25 ++++++++++++------------- wled00/fcn_declare.h | 3 ++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 3dfbe01ee9..0bfdaff141 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -91,19 +91,18 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) return scaledcolor; } -/* +/* * color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate! shifts hue, increase brightness, decreases saturation (if not black) note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change - CHSV hsv; - rgb2hsv(rgb, hsv); //convert to HSV + CHSV hsv = rgb2hsv(rgb); //convert to HSV if(hsv.v == 0) return rgb; // do not change black pixels hsv.h += hueShift; // shift hue hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate - hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness + hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness CRGB adjusted; hsv2rgb_spectrum(hsv, adjusted); // convert back to RGB return RGBW32(adjusted.r,adjusted.g,adjusted.b,0); @@ -123,7 +122,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) { CHSV palettecolors[4]; //array of colors for the new palette uint8_t keepcolorposition = random8(4); //color position of current random palette to keep - palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette + palettecolors[keepcolorposition] = rgb2hsv(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color //generate 4 saturation and brightness value numbers //only one saturation is allowed to be below 200 creating mostly vibrant colors @@ -246,27 +245,27 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb } } -void rgb2hsv(const uint32_t rgb, CHSV& hsv) // convert rgb to hsv, more accurate and faster than fastled version +CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster than fastled version { + CHSV hsv = CHSV(0, 0, 0); int32_t r = (rgb>>16)&0xFF; int32_t g = (rgb>>8)&0xFF; int32_t b = rgb&0xFF; - hsv = CHSV(0, 0, 0); int32_t minval, maxval, delta; minval = min(r, g); - minval = min(minval, b); + minval = min(minval, b); maxval = max(r, g); maxval = max(maxval, b); if (maxval == 0) return; // black hsv.v = maxval; delta = maxval - minval; hsv.s = (255 * delta) / maxval; - if (hsv.s == 0) return; // gray value + if (hsv.s == 0) return; // gray value int32_t h; //calculate hue - if (maxval == r) - h = (43 * (g - b)) / delta; - else if (maxval == g) h = 85 + (43 * (b - r)) / delta; - else h = 171 + (43 * (r - g)) / delta; + if (maxval == r) + h = (43 * (g - b)) / delta; + else if (maxval == g) h = 85 + (43 * (b - r)) / delta; + else h = 171 + (43 * (r - g)) / delta; if(h < 0) h += 256; hsv.h = h; } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 55e19346b3..a7ee4e08b4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -86,7 +86,8 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb -void rgb2hsv(const uint32_t rgb, CHSV& hsv); //rgb to hsv +CHSV rgb2hsv(const uint32_t rgb); // rgb to hsv +inline CHSV rgb2hsv(const CRGB c) { return rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b))))); } // CRGB to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO From 7db1adccb7a8faf3e08d11adc9f03d25166c1e0b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 16:53:22 +0200 Subject: [PATCH 18/18] added forgotten returnvalue --- wled00/colors.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 0bfdaff141..8b04d4c07a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -256,11 +256,11 @@ CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster minval = min(minval, b); maxval = max(r, g); maxval = max(maxval, b); - if (maxval == 0) return; // black + if (maxval == 0) return hsv; // black hsv.v = maxval; delta = maxval - minval; hsv.s = (255 * delta) / maxval; - if (hsv.s == 0) return; // gray value + if (hsv.s == 0) return hsv; // gray value int32_t h; //calculate hue if (maxval == r) h = (43 * (g - b)) / delta; @@ -268,6 +268,7 @@ CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster else h = 171 + (43 * (r - g)) / delta; if(h < 0) h += 256; hsv.h = h; + return hsv; } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)