Skip to content

Commit

Permalink
Mode blending styles
Browse files Browse the repository at this point in the history
- alternative to #3669
  • Loading branch information
blazoncek committed Apr 3, 2024
1 parent 24c5935 commit 0c8d9d5
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 15 deletions.
28 changes: 28 additions & 0 deletions wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)); }
Expand Down Expand Up @@ -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); }
Expand Down
33 changes: 32 additions & 1 deletion wled00/FX_2Dfcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
123 changes: 111 additions & 12 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
20 changes: 19 additions & 1 deletion wled00/data/index.htm
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,25 @@
<div id="segutil2">
<button class="btn btn-s" id="rsbtn" onclick="rSegs()">Reset segments</button>
</div>
<p>Transition: <input id="tt" type="number" min="0" max="65.5" step="0.1" value="0.7">&nbsp;s</p>
<p>Transition: <input id="tt" type="number" min="0" max="65.5" step="0.1" value="0.7" onchange="parseFloat(this.value)===0?gId('bsp').classList.add('hide'):gId('bsp').classList.remove('hide');">&nbsp;s</p>
<p id="bsp">Blend:
<select id="bs" class="sel-sg" onchange="requestJson({'bs':parseInt(this.value)})">
<option value="0">Fade</option>
<option value="1">Fairy Dust</option>
<option value="2">Swipe left</option>
<option value="3">Swipe right</option>
<option value="4">Pinch-out</option>
<option value="5">Inside-out</option>
<option value="6">Swipe up (2D)</option>
<option value="7">Swipe down (2D)</option>
<option value="8">Open V (2D)</option>
<option value="9">Open H (2D)</option>
<option value="10">Push TL (2D)</option>
<option value="11">Push TR (2D)</option>
<option value="12">Push BR (2D)</option>
<option value="13">Push BL (2D)</option>
</select>
</p>
<p id="ledmap" class="hide"></p>
</div>

Expand Down
4 changes: 4 additions & 0 deletions wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 0c8d9d5

Please sign in to comment.