From db1a81f3e87705cc10b3ed7e13f296b9a8548d1f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 30 Aug 2025 18:46:27 +0200 Subject: [PATCH 1/8] Optimisatins in blendSegment() - better performance at the cost of about 700 bytes of flash --- wled00/FX_fcn.cpp | 84 +++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 2f8d5515fd..86366562c5 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1317,23 +1317,43 @@ static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); } static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); } +__attribute__((optimize("O2"))) // use O2 to trade 1.3k of flash for a ~5% FPS boost (ESP32) void WS2812FX::blendSegment(const Segment &topSegment) const { - typedef uint8_t(*FuncType)(uint8_t, uint8_t); - FuncType funcs[] = { - _top, _bottom, - _add, _subtract, _difference, _average, - _multiply, _divide, _lighten, _darken, _screen, _overlay, - _hardlight, _softlight, _dodge, _burn - }; + const auto segblend = [&](uint32_t tcol, uint32_t bcol, uint32_t bgcol, uint8_t blendMode) { + uint8_t rt = R(tcol), gt = G(tcol), bt = B(tcol), wt = W(tcol); + uint8_t rb = R(bcol), gb = G(bcol), bb = B(bcol), wb = W(bcol); + uint8_t r, g, b, w; + + switch (blendMode) { + case 0: return tcol; // Top + case 1: return bcol; // Bottom + case 2: r = _add(rt, rb); g = _add(gt, gb); b = _add(bt, bb); w = _add(wt, wb); break; // Add + case 3: r = _subtract(rt, rb); g = _subtract(gt, gb); b = _subtract(bt, bb); w = _subtract(wt, wb); break; // Subtract + case 4: r = _difference(rt, rb); g = _difference(gt, gb); b = _difference(bt, bb); w = _difference(wt, wb); break; // Difference + case 5: r = _average(rt, rb); g = _average(gt, gb); b = _average(bt, bb); w = _average(wt, wb); break; // Average + case 6: r = _multiply(rt, rb); g = _multiply(gt, gb); b = _multiply(bt, bb); w = _multiply(wt, wb); break; // Multiply + case 7: r = _divide(rt, rb); g = _divide(gt, gb); b = _divide(bt, bb); w = _divide(wt, wb); break; // Divide + case 8: r = _lighten(rt, rb); g = _lighten(gt, gb); b = _lighten(bt, bb); w = _lighten(wt, wb); break; // Lighten + case 9: r = _darken(rt, rb); g = _darken(gt, gb); b = _darken(bt, bb); w = _darken(wt, wb); break; // Darken + case 10: r = _screen(rt, rb); g = _screen(gt, gb); b = _screen(bt, bb); w = _screen(wt, wb); break; // Screen + case 11: r = _overlay(rt, rb); g = _overlay(gt, gb); b = _overlay(bt, bb); w = _overlay(wt, wb); break; // Overlay + case 12: r = _hardlight(rt, rb); g = _hardlight(gt, gb); b = _hardlight(bt, bb); w = _hardlight(wt, wb); break; // Hardlight + case 13: r = _softlight(rt, rb); g = _softlight(gt, gb); b = _softlight(bt, bb); w = _softlight(wt, wb); break; // Softlight + case 14: r = _dodge(rt, rb); g = _dodge(gt, gb); b = _dodge(bt, bb); w = _dodge(wt, wb); break; // Dodge + case 15: r = _burn(rt, rb); g = _burn(gt, gb); b = _burn(bt, bb); w = _burn(wt, wb); break; // Burn + case 32: return tcol == bgcol ? bcol : tcol; // stencil: backgroundcolor = transparent, use top color otherwise + default: return tcol; // fallback to Top + } - const size_t blendMode = topSegment.blendMode < (sizeof(funcs) / sizeof(FuncType)) ? topSegment.blendMode : 0; - const auto func = funcs[blendMode]; // blendMode % (sizeof(funcs) / sizeof(FuncType)) - const auto blend = [&](uint32_t top, uint32_t bottom){ return RGBW32(func(R(top),R(bottom)), func(G(top),G(bottom)), func(B(top),B(bottom)), func(W(top),W(bottom))); }; + return RGBW32(r, g, b, w); +}; + const uint8_t blendMode = topSegment.blendMode; const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment) const int width = topSegment.width(); const int height = topSegment.height(); + const uint32_t bgColor = topSegment.colors[1]; // background color for blend modes that need it (e.g. stencil) const auto XY = [](int x, int y){ return x + y*Segment::maxWidth; }; const size_t matrixSize = Segment::maxWidth * Segment::maxHeight; const size_t startIndx = XY(topSegment.start, topSegment.startY); @@ -1408,23 +1428,24 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const Segment *segO = topSegment.getOldSegment(); const int oCols = segO ? segO->virtualWidth() : nCols; const int oRows = segO ? segO->virtualHeight() : nRows; + bool applyMirror = topSegment.mirror || topSegment.mirror_y; const auto setMirroredPixel = [&](int x, int y, uint32_t c, uint8_t o) { const int baseX = topSegment.start + x; const int baseY = topSegment.startY + y; size_t indx = XY(baseX, baseY); // absolute address on strip - _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx], bgColor, blendMode), o); if (_pixelCCT) _pixelCCT[indx] = cct; // Apply mirroring - if (topSegment.mirror || topSegment.mirror_y) { + if (applyMirror) { const int mirrorX = topSegment.start + width - x - 1; const int mirrorY = topSegment.startY + height - y - 1; const size_t idxMX = XY(topSegment.transpose ? baseX : mirrorX, topSegment.transpose ? mirrorY : baseY); const size_t idxMY = XY(topSegment.transpose ? mirrorX : baseX, topSegment.transpose ? baseY : mirrorY); const size_t idxMM = XY(mirrorX, mirrorY); - if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], blend(c, _pixels[idxMX]), o); - if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], blend(c, _pixels[idxMY]), o); - if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], blend(c, _pixels[idxMM]), o); + if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], segblend(c, _pixels[idxMX], bgColor, blendMode), o); + if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], segblend(c, _pixels[idxMY], bgColor, blendMode), o); + if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], segblend(c, _pixels[idxMM], bgColor, blendMode), o); if (_pixelCCT) { if (topSegment.mirror) _pixelCCT[idxMX] = cct; if (topSegment.mirror_y) _pixelCCT[idxMY] = cct; @@ -1436,7 +1457,16 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { // if we blend using "push" style we need to "shift" canvas to left/right/up/down unsigned offsetX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : progInv * nCols / 0xFFFFU; unsigned offsetY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU; - + const unsigned groupLen = topSegment.groupLength(); + bool applyReverse = topSegment.reverse || topSegment.reverse_y || topSegment.transpose; + int pushOffsetX = 0, pushOffsetY = 0; + // if we blend using "push" style we need to "shift" canvas to left/right/up/down + switch (blendingStyle) { + case BLEND_STYLE_PUSH_RIGHT: pushOffsetX = offsetX; break; + case BLEND_STYLE_PUSH_LEFT: pushOffsetX = -offsetX + nCols; break; + case BLEND_STYLE_PUSH_DOWN: pushOffsetY = offsetY; break; + case BLEND_STYLE_PUSH_UP: pushOffsetY = -offsetY + nRows; break; + } // we only traverse new segment, not old one for (int r = 0; r < nRows; r++) for (int c = 0; c < nCols; c++) { const bool clipped = topSegment.isPixelXYClipped(c, r); @@ -1446,13 +1476,8 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { int vRows = seg == segO ? oRows : nRows; // old segment may have different dimensions int x = c; int y = r; - // if we blend using "push" style we need to "shift" canvas to left/right/up/down - switch (blendingStyle) { - case BLEND_STYLE_PUSH_RIGHT: x = (x + offsetX) % nCols; break; - case BLEND_STYLE_PUSH_LEFT: x = (x - offsetX + nCols) % nCols; break; - case BLEND_STYLE_PUSH_DOWN: y = (y + offsetY) % nRows; break; - case BLEND_STYLE_PUSH_UP: y = (y - offsetY + nRows) % nRows; break; - } + if (pushOffsetX != 0) x = (x + pushOffsetX) % nCols; + if (pushOffsetY != 0) y = (y + pushOffsetY) % nRows; uint32_t c_a = BLACK; if (x < vCols && y < vRows) c_a = seg->getPixelColorRaw(x + y*vCols); // will get clipped pixel from old segment or unclipped pixel from new segment if (segO && blendingStyle == BLEND_STYLE_FADE @@ -1469,11 +1494,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { // map it into frame buffer x = c; // restore coordiates if we were PUSHing y = r; - if (topSegment.reverse ) x = nCols - x - 1; - if (topSegment.reverse_y) y = nRows - y - 1; - if (topSegment.transpose) std::swap(x,y); // swap X & Y if segment transposed + if (applyReverse) { + if (topSegment.reverse ) x = nCols - x - 1; + if (topSegment.reverse_y) y = nRows - y - 1; + if (topSegment.transpose) std::swap(x,y); // swap X & Y if segment transposed + } // expand pixel - const unsigned groupLen = topSegment.groupLength(); if (groupLen == 1) { setMirroredPixel(x, y, c_a, opacity); } else { @@ -1502,12 +1528,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { unsigned indxM = topSegment.stop - i - 1; indxM += topSegment.offset; // offset/phase if (indxM >= topSegment.stop) indxM -= length; // wrap - _pixels[indxM] = color_blend(_pixels[indxM], blend(c, _pixels[indxM]), o); + _pixels[indxM] = color_blend(_pixels[indxM], segblend(c, _pixels[indxM], bgColor, blendMode), o); if (_pixelCCT) _pixelCCT[indxM] = cct; } indx += topSegment.offset; // offset/phase if (indx >= topSegment.stop) indx -= length; // wrap - _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx], bgColor, blendMode), o); if (_pixelCCT) _pixelCCT[indx] = cct; }; From 9c6ab89c0f58afcecec2b8f59a46ee43470844bb Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 30 Aug 2025 21:37:11 +0200 Subject: [PATCH 2/8] adding new blend mode: "Stencil" and some optimizations - Stencil overlay adds an FX on top but leaves the backgroundcolor transparent, this is useful for classic text overlays --- wled00/FX.h | 3 +-- wled00/FX_fcn.cpp | 60 ++++++++++++++++++++------------------------ wled00/data/index.js | 3 ++- wled00/json.cpp | 4 +-- 4 files changed, 31 insertions(+), 39 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 097c857caf..8ee9ccda6b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -455,9 +455,8 @@ class Segment { bool check1 : 1; // checkmark 1 bool check2 : 1; // checkmark 2 bool check3 : 1; // checkmark 3 - //uint8_t blendMode : 4; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn }; - uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn + uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn, stencil char *name; // segment name // runtime data diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 86366562c5..1f379a120d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1292,9 +1292,6 @@ void WS2812FX::service() { } // https://en.wikipedia.org/wiki/Blend_modes but using a for top layer & b for bottom layer -static uint8_t _top (uint8_t a, uint8_t b) { return a; } -static uint8_t _bottom (uint8_t a, uint8_t b) { return b; } -static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; } static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); } static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; } @@ -1317,38 +1314,35 @@ static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); } static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); } -__attribute__((optimize("O2"))) // use O2 to trade 1.3k of flash for a ~5% FPS boost (ESP32) -void WS2812FX::blendSegment(const Segment &topSegment) const { - - const auto segblend = [&](uint32_t tcol, uint32_t bcol, uint32_t bgcol, uint8_t blendMode) { - uint8_t rt = R(tcol), gt = G(tcol), bt = B(tcol), wt = W(tcol); - uint8_t rb = R(bcol), gb = G(bcol), bb = B(bcol), wb = W(bcol); - uint8_t r, g, b, w; - - switch (blendMode) { - case 0: return tcol; // Top - case 1: return bcol; // Bottom - case 2: r = _add(rt, rb); g = _add(gt, gb); b = _add(bt, bb); w = _add(wt, wb); break; // Add - case 3: r = _subtract(rt, rb); g = _subtract(gt, gb); b = _subtract(bt, bb); w = _subtract(wt, wb); break; // Subtract - case 4: r = _difference(rt, rb); g = _difference(gt, gb); b = _difference(bt, bb); w = _difference(wt, wb); break; // Difference - case 5: r = _average(rt, rb); g = _average(gt, gb); b = _average(bt, bb); w = _average(wt, wb); break; // Average - case 6: r = _multiply(rt, rb); g = _multiply(gt, gb); b = _multiply(bt, bb); w = _multiply(wt, wb); break; // Multiply - case 7: r = _divide(rt, rb); g = _divide(gt, gb); b = _divide(bt, bb); w = _divide(wt, wb); break; // Divide - case 8: r = _lighten(rt, rb); g = _lighten(gt, gb); b = _lighten(bt, bb); w = _lighten(wt, wb); break; // Lighten - case 9: r = _darken(rt, rb); g = _darken(gt, gb); b = _darken(bt, bb); w = _darken(wt, wb); break; // Darken - case 10: r = _screen(rt, rb); g = _screen(gt, gb); b = _screen(bt, bb); w = _screen(wt, wb); break; // Screen - case 11: r = _overlay(rt, rb); g = _overlay(gt, gb); b = _overlay(bt, bb); w = _overlay(wt, wb); break; // Overlay - case 12: r = _hardlight(rt, rb); g = _hardlight(gt, gb); b = _hardlight(bt, bb); w = _hardlight(wt, wb); break; // Hardlight - case 13: r = _softlight(rt, rb); g = _softlight(gt, gb); b = _softlight(bt, bb); w = _softlight(wt, wb); break; // Softlight - case 14: r = _dodge(rt, rb); g = _dodge(gt, gb); b = _dodge(bt, bb); w = _dodge(wt, wb); break; // Dodge - case 15: r = _burn(rt, rb); g = _burn(gt, gb); b = _burn(bt, bb); w = _burn(wt, wb); break; // Burn - case 32: return tcol == bgcol ? bcol : tcol; // stencil: backgroundcolor = transparent, use top color otherwise - default: return tcol; // fallback to Top - } - - return RGBW32(r, g, b, w); +static uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) { + CRGBW tC(tcol); CRGBW bC(bcol); CRGBW c; // note: using aliases shrinks code size for some weird compiler reasons, no speed difference in tests + // note2: using CRGBW instead of uint32_t improves speed as well as code size + switch (blendMode) { + case 0: return tcol.color32; // Top + case 1: return bcol.color32; // Bottom + case 2: return color_add(tcol, bcol, false); // Add + case 3: c.r = _subtract(tC.r, bC.r); c.g = _subtract(tC.g, tC.g); c.b = _subtract(tC.b, bC.b); c.w = _subtract(tC.w, bC.w); break; // Subtract + case 4: c.r = _difference(tC.r, bC.r); c.g = _difference(tC.g, bC.g); c.b = _difference(tC.b, bC.b); c.w = _difference(tC.w, bC.w); break; // Difference + case 5: c.r = _average(tC.r, bC.r); c.g = _average(tC.g, bC.g); c.b = _average(tC.b, bC.b); c.w = _average(tC.w, bC.w); break; // Average + case 6: c.r = _multiply(tC.r, bC.r); c.g = _multiply(tC.g, bC.g); c.b = _multiply(tC.b, bC.b); c.w = _multiply(tC.w, bC.w); break; // Multiply + case 7: c.r = _divide(tC.r, bC.r); c.g = _divide(tC.g, bC.g); c.b = _divide(tC.b, bC.b); c.w = _divide(tC.w, bC.w); break; // Divide + case 8: c.r = _lighten(tC.r, bC.r); c.g = _lighten(tC.g, bC.g); c.b = _lighten(tC.b, bC.b); c.w = _lighten(tC.w, bC.w); break; // Lighten + case 9: c.r = _darken(tC.r, bC.r); c.g = _darken(tC.g, bC.g); c.b = _darken(tC.b, bC.b); c.w = _darken(tC.w, bC.w); break; // Darken + case 10: c.r = _screen(tC.r, bC.r); c.g = _screen(tC.g, bC.g); c.b = _screen(tC.b, bC.b); c.w = _screen(tC.w, bC.w); break; // Screen + case 11: c.r = _overlay(tC.r, bC.r); c.g = _overlay(tC.g, bC.g); c.b = _overlay(tC.b, bC.b); c.w = _overlay(tC.w, bC.w); break; // Overlay + case 12: c.r = _hardlight(tC.r, bC.r); c.g = _hardlight(tC.g, bC.g); c.b = _hardlight(tC.b, bC.b); c.w = _hardlight(tC.w, bC.w); break; // Hardlight + case 13: c.r = _softlight(tC.r, bC.r); c.g = _softlight(tC.g, bC.g); c.b = _softlight(tC.b, bC.b); c.w = _softlight(tC.w, bC.w); break; // Softlight + case 14: c.r = _dodge(tC.r, bC.r); c.g = _dodge(tC.g, bC.g); c.b = _dodge(tC.b, bC.b); c.w = _dodge(tC.w, bC.w); break; // Dodge + case 15: c.r = _burn(tC.r, bC.r); c.g = _burn(tC.g, bC.g); c.b = _burn(tC.b, bC.b); c.w = _burn(tC.w, bC.w); break; // Burn + // note: stencil mode is a special case: it works only on full color comparison and wont work correctly on a colorchannel base + // enumerate to 32 to allow future additions above in case a function array will be used again + case 32: return tcol.color32 == bgcol.color32 ? bcol.color32 : tcol.color32; // Stencil: backgroundcolor -> transparent, use top color otherwise + default: return tcol.color32; // fallback to Top + } + return c.color32; }; +void WS2812FX::blendSegment(const Segment &topSegment) const { const uint8_t blendMode = topSegment.blendMode; const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment) const int width = topSegment.width(); diff --git a/wled00/data/index.js b/wled00/data/index.js index fe154783d0..140802f049 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -825,6 +825,7 @@ function populateSegments(s) ``+ ``+ ``+ + ``+ ``+ ``; let sndSim = `
Sound sim
`+ @@ -2358,7 +2359,7 @@ function setSi(s) function setBm(s) { - var value = gId(`seg${s}bm`).selectedIndex; + var value = gId(`seg${s}bm`).value; var obj = {"seg": {"id": s, "bm": value}}; requestJson(obj); } diff --git a/wled00/json.cpp b/wled00/json.cpp index e8ebaaba29..5acda0afcc 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -302,9 +302,7 @@ static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0) seg.check2 = getBoolVal(elem["o2"], seg.check2); seg.check3 = getBoolVal(elem["o3"], seg.check3); - uint8_t blend = seg.blendMode; - getVal(elem["bm"], blend, 0, 15); // we can't pass reference to bitfield - seg.blendMode = constrain(blend, 0, 15); + getVal(elem["bm"], seg.blendMode); JsonArray iarr = elem[F("i")]; //set individual LEDs if (!iarr.isNull()) { From bfc9c5eb905ae1c892fa2874bd5f1ed2565428f4 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 30 Aug 2025 22:31:50 +0200 Subject: [PATCH 3/8] cleanup --- wled00/FX_fcn.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 1f379a120d..7dab6e2e1a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -369,7 +369,7 @@ void Segment::beginDraw(uint16_t prog) { // minimum blend time is 100ms maximum is 65535ms #ifndef WLED_SAVE_RAM unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - if(noOfBlends > 255) noOfBlends = 255; // safety check + if (noOfBlends > 255) noOfBlends = 255; // safety check for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, Segment::_currentPalette, 48); Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette #else @@ -1321,7 +1321,7 @@ static uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) case 0: return tcol.color32; // Top case 1: return bcol.color32; // Bottom case 2: return color_add(tcol, bcol, false); // Add - case 3: c.r = _subtract(tC.r, bC.r); c.g = _subtract(tC.g, tC.g); c.b = _subtract(tC.b, bC.b); c.w = _subtract(tC.w, bC.w); break; // Subtract + case 3: c.r = _subtract(tC.r, bC.r); c.g = _subtract(tC.g, bC.g); c.b = _subtract(tC.b, bC.b); c.w = _subtract(tC.w, bC.w); break; // Subtract case 4: c.r = _difference(tC.r, bC.r); c.g = _difference(tC.g, bC.g); c.b = _difference(tC.b, bC.b); c.w = _difference(tC.w, bC.w); break; // Difference case 5: c.r = _average(tC.r, bC.r); c.g = _average(tC.g, bC.g); c.b = _average(tC.b, bC.b); c.w = _average(tC.w, bC.w); break; // Average case 6: c.r = _multiply(tC.r, bC.r); c.g = _multiply(tC.g, bC.g); c.b = _multiply(tC.b, bC.b); c.w = _multiply(tC.w, bC.w); break; // Multiply @@ -1610,7 +1610,7 @@ void WS2812FX::show() { } uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32) - if(c > 0 && !(realtimeMode && arlsDisableGammaCorrection)) + if (c > 0 && !(realtimeMode && arlsDisableGammaCorrection)) c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss BusManager::setPixelColor(getMappedPixelIndex(i), c); } From ae76ae80dfad30762ef2ff30eadc9512d24124e3 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 31 Aug 2025 16:26:59 +0200 Subject: [PATCH 4/8] cleaner code with a macro, added unused functions back --- wled00/FX_fcn.cpp | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7dab6e2e1a..213c4abbab 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1292,6 +1292,9 @@ void WS2812FX::service() { } // https://en.wikipedia.org/wiki/Blend_modes but using a for top layer & b for bottom layer +static uint8_t _top (uint8_t a, uint8_t b) { return a; } // function currently unused +static uint8_t _bottom (uint8_t a, uint8_t b) { return b; } // function currently unused +static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } // function currently unused static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; } static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); } static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; } @@ -1314,6 +1317,8 @@ static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); } static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); } +#define APPLY_BLEND(op) c.r = op(tC.r, bC.r); c.g = op(tC.g, bC.g); c.b = op(tC.b, bC.b); c.w = op(tC.w, bC.w) + static uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) { CRGBW tC(tcol); CRGBW bC(bcol); CRGBW c; // note: using aliases shrinks code size for some weird compiler reasons, no speed difference in tests // note2: using CRGBW instead of uint32_t improves speed as well as code size @@ -1321,22 +1326,22 @@ static uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) case 0: return tcol.color32; // Top case 1: return bcol.color32; // Bottom case 2: return color_add(tcol, bcol, false); // Add - case 3: c.r = _subtract(tC.r, bC.r); c.g = _subtract(tC.g, bC.g); c.b = _subtract(tC.b, bC.b); c.w = _subtract(tC.w, bC.w); break; // Subtract - case 4: c.r = _difference(tC.r, bC.r); c.g = _difference(tC.g, bC.g); c.b = _difference(tC.b, bC.b); c.w = _difference(tC.w, bC.w); break; // Difference - case 5: c.r = _average(tC.r, bC.r); c.g = _average(tC.g, bC.g); c.b = _average(tC.b, bC.b); c.w = _average(tC.w, bC.w); break; // Average - case 6: c.r = _multiply(tC.r, bC.r); c.g = _multiply(tC.g, bC.g); c.b = _multiply(tC.b, bC.b); c.w = _multiply(tC.w, bC.w); break; // Multiply - case 7: c.r = _divide(tC.r, bC.r); c.g = _divide(tC.g, bC.g); c.b = _divide(tC.b, bC.b); c.w = _divide(tC.w, bC.w); break; // Divide - case 8: c.r = _lighten(tC.r, bC.r); c.g = _lighten(tC.g, bC.g); c.b = _lighten(tC.b, bC.b); c.w = _lighten(tC.w, bC.w); break; // Lighten - case 9: c.r = _darken(tC.r, bC.r); c.g = _darken(tC.g, bC.g); c.b = _darken(tC.b, bC.b); c.w = _darken(tC.w, bC.w); break; // Darken - case 10: c.r = _screen(tC.r, bC.r); c.g = _screen(tC.g, bC.g); c.b = _screen(tC.b, bC.b); c.w = _screen(tC.w, bC.w); break; // Screen - case 11: c.r = _overlay(tC.r, bC.r); c.g = _overlay(tC.g, bC.g); c.b = _overlay(tC.b, bC.b); c.w = _overlay(tC.w, bC.w); break; // Overlay - case 12: c.r = _hardlight(tC.r, bC.r); c.g = _hardlight(tC.g, bC.g); c.b = _hardlight(tC.b, bC.b); c.w = _hardlight(tC.w, bC.w); break; // Hardlight - case 13: c.r = _softlight(tC.r, bC.r); c.g = _softlight(tC.g, bC.g); c.b = _softlight(tC.b, bC.b); c.w = _softlight(tC.w, bC.w); break; // Softlight - case 14: c.r = _dodge(tC.r, bC.r); c.g = _dodge(tC.g, bC.g); c.b = _dodge(tC.b, bC.b); c.w = _dodge(tC.w, bC.w); break; // Dodge - case 15: c.r = _burn(tC.r, bC.r); c.g = _burn(tC.g, bC.g); c.b = _burn(tC.b, bC.b); c.w = _burn(tC.w, bC.w); break; // Burn + case 3: APPLY_BLEND(_subtract); break; // Subtract + case 4: APPLY_BLEND(_difference); break; // Difference + case 5: APPLY_BLEND(_average); break; // Average + case 6: APPLY_BLEND(_multiply); break; // Multiply + case 7: APPLY_BLEND(_divide); break; // Divide + case 8: APPLY_BLEND(_lighten); break; // Lighten + case 9: APPLY_BLEND(_darken); break; // Darken + case 10: APPLY_BLEND(_screen); break; // Screen + case 11: APPLY_BLEND(_overlay); break; // Overlay + case 12: APPLY_BLEND(_hardlight); break; // Hardlight + case 13: APPLY_BLEND(_softlight); break; // Softlight + case 14: APPLY_BLEND(_dodge); break; // Dodge + case 15: APPLY_BLEND(_burn); break; // Burn + case 32: return tcol.color32 == bgcol.color32 ? bcol.color32 : tcol.color32; // Stencil: backgroundcolor -> transparent, use top color otherwise // note: stencil mode is a special case: it works only on full color comparison and wont work correctly on a colorchannel base // enumerate to 32 to allow future additions above in case a function array will be used again - case 32: return tcol.color32 == bgcol.color32 ? bcol.color32 : tcol.color32; // Stencil: backgroundcolor -> transparent, use top color otherwise default: return tcol.color32; // fallback to Top } return c.color32; From f0da4b352d66f430820379b2c6ea581193133ad1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 21 Mar 2026 16:54:51 +0100 Subject: [PATCH 5/8] remove alias --- wled00/FX_fcn.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 213c4abbab..fee5d5a192 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1427,7 +1427,6 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const Segment *segO = topSegment.getOldSegment(); const int oCols = segO ? segO->virtualWidth() : nCols; const int oRows = segO ? segO->virtualHeight() : nRows; - bool applyMirror = topSegment.mirror || topSegment.mirror_y; const auto setMirroredPixel = [&](int x, int y, uint32_t c, uint8_t o) { const int baseX = topSegment.start + x; @@ -1435,8 +1434,8 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { size_t indx = XY(baseX, baseY); // absolute address on strip _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx], bgColor, blendMode), o); if (_pixelCCT) _pixelCCT[indx] = cct; - // Apply mirroring - if (applyMirror) { + // Apply mirroring if enabled + if (topSegment.mirror || topSegment.mirror_y) { const int mirrorX = topSegment.start + width - x - 1; const int mirrorY = topSegment.startY + height - y - 1; const size_t idxMX = XY(topSegment.transpose ? baseX : mirrorX, topSegment.transpose ? mirrorY : baseY); From 828b6eaf0205ff4da749b3e83d3410545a54c536 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 21 Mar 2026 18:33:43 +0100 Subject: [PATCH 6/8] revert some changes, mix between direct calls and lambda function pointer --- wled00/FX_fcn.cpp | 76 +++++++++++++++++++++----------------------- wled00/data/index.js | 2 +- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e32887f87e..f2c260da04 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1337,9 +1337,9 @@ void WS2812FX::service() { } // https://en.wikipedia.org/wiki/Blend_modes but using a for top layer & b for bottom layer -static uint8_t _top (uint8_t a, uint8_t b) { return a; } // function currently unused -static uint8_t _bottom (uint8_t a, uint8_t b) { return b; } // function currently unused -static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } // function currently unused +static uint8_t _top (uint8_t a, uint8_t b) { return a; } // function unused +static uint8_t _bottom (uint8_t a, uint8_t b) { return b; } // function unused +static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } // function unused static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; } static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); } static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; } @@ -1361,39 +1361,37 @@ static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) #endif static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); } static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); } +static uint8_t _stencil (uint8_t a, uint8_t b) { return a ? a : b; } // function unused +static uint8_t _dummy (uint8_t a, uint8_t b) { return a; } // dummy (same as _top) to fill the function list and make it safe from OOB access -#define APPLY_BLEND(op) c.r = op(tC.r, bC.r); c.g = op(tC.g, bC.g); c.b = op(tC.b, bC.b); c.w = op(tC.w, bC.w) - -static uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) { - CRGBW tC(tcol); CRGBW bC(bcol); CRGBW c; // note: using aliases shrinks code size for some weird compiler reasons, no speed difference in tests - // note2: using CRGBW instead of uint32_t improves speed as well as code size - switch (blendMode) { - case 0: return tcol.color32; // Top - case 1: return bcol.color32; // Bottom - case 2: return color_add(tcol, bcol, false); // Add - case 3: APPLY_BLEND(_subtract); break; // Subtract - case 4: APPLY_BLEND(_difference); break; // Difference - case 5: APPLY_BLEND(_average); break; // Average - case 6: APPLY_BLEND(_multiply); break; // Multiply - case 7: APPLY_BLEND(_divide); break; // Divide - case 8: APPLY_BLEND(_lighten); break; // Lighten - case 9: APPLY_BLEND(_darken); break; // Darken - case 10: APPLY_BLEND(_screen); break; // Screen - case 11: APPLY_BLEND(_overlay); break; // Overlay - case 12: APPLY_BLEND(_hardlight); break; // Hardlight - case 13: APPLY_BLEND(_softlight); break; // Softlight - case 14: APPLY_BLEND(_dodge); break; // Dodge - case 15: APPLY_BLEND(_burn); break; // Burn - case 32: return tcol.color32 == bgcol.color32 ? bcol.color32 : tcol.color32; // Stencil: backgroundcolor -> transparent, use top color otherwise - // note: stencil mode is a special case: it works only on full color comparison and wont work correctly on a colorchannel base - // enumerate to 32 to allow future additions above in case a function array will be used again - default: return tcol.color32; // fallback to Top - } - return c.color32; -}; +#define BLENDMODES 17 // number of blend modes must match "bm" in index.js, all cases must be handled in segblend() @ blendSegment() void WS2812FX::blendSegment(const Segment &topSegment) const { - const uint8_t blendMode = topSegment.blendMode; + typedef uint8_t(*FuncType)(uint8_t, uint8_t); + // function pointer array: fill with _dummy if using special case: avoid OOB access and always provide a valid path + FuncType funcs[] = { + _dummy, _dummy, _dummy, _subtract, + _difference, _average, _dummy, _divide, + _lighten, _darken, _screen, _overlay, + _hardlight, _softlight, _dodge, _burn, + _dummy + }; + + const size_t blendMode = topSegment.blendMode < BLENDMODES ? topSegment.blendMode : 0; // default to top if unsupported mode + const auto segblend = [&](uint32_t t, uint32_t b){ + // use direct calculations/returns for simple/frequent modes (faster) + switch (blendMode) { + case 0 : return t; // top + case 1 : return b; // bottom + case 2 : return color_add(t,b,true); // add with preserve color ratio to avoid color clipping + case 6 : return RGBW32(_multiply(R(t),R(b)), _multiply(G(t),G(b)), _multiply(B(t),B(b)), _multiply(W(t),W(b))); // multiply (7% faster than lambda at 100bytes flash cost) + case 16: return t ? t : b; // stencil (use top layer if not black, else bottom) + } + // default: use function pointer from array + const auto func = funcs[blendMode]; + return RGBW32(func(R(t),R(b)), func(G(t),G(b)), func(B(t),B(b)), func(W(t),W(b))); + }; + const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment) const int width = topSegment.width(); const int height = topSegment.height(); @@ -1478,7 +1476,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const int baseX = topSegment.start + x; const int baseY = topSegment.startY + y; size_t indx = XY(baseX, baseY); // absolute address on strip - _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx], bgColor, blendMode), o); + _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx]), o); if (_pixelCCT) _pixelCCT[indx] = cct; // Apply mirroring if enabled if (topSegment.mirror || topSegment.mirror_y) { @@ -1487,9 +1485,9 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const size_t idxMX = XY(topSegment.transpose ? baseX : mirrorX, topSegment.transpose ? mirrorY : baseY); const size_t idxMY = XY(topSegment.transpose ? mirrorX : baseX, topSegment.transpose ? baseY : mirrorY); const size_t idxMM = XY(mirrorX, mirrorY); - if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], segblend(c, _pixels[idxMX], bgColor, blendMode), o); - if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], segblend(c, _pixels[idxMY], bgColor, blendMode), o); - if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], segblend(c, _pixels[idxMM], bgColor, blendMode), o); + if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], segblend(c, _pixels[idxMX]), o); + if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], segblend(c, _pixels[idxMY]), o); + if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], segblend(c, _pixels[idxMM]), o); if (_pixelCCT) { if (topSegment.mirror) _pixelCCT[idxMX] = cct; if (topSegment.mirror_y) _pixelCCT[idxMY] = cct; @@ -1573,12 +1571,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { unsigned indxM = topSegment.stop - i - 1; indxM += topSegment.offset; // offset/phase if (indxM >= topSegment.stop) indxM -= length; // wrap - _pixels[indxM] = color_blend(_pixels[indxM], segblend(c, _pixels[indxM], bgColor, blendMode), o); + _pixels[indxM] = color_blend(_pixels[indxM], segblend(c, _pixels[indxM]), o); if (_pixelCCT) _pixelCCT[indxM] = cct; } indx += topSegment.offset; // offset/phase if (indx >= topSegment.stop) indx -= length; // wrap - _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx], bgColor, blendMode), o); + _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx]), o); if (_pixelCCT) _pixelCCT[indx] = cct; }; diff --git a/wled00/data/index.js b/wled00/data/index.js index 00ee755e7e..cccc9103b8 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -798,7 +798,7 @@ function populateSegments(s) ``+ ``+ ``+ - ``+ + ``+ `
`+ ``; let sndSim = `
Sound sim
`+ From 49014279f2a9a6ebdcf112e57deec92d5ab1747b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Mar 2026 08:00:09 +0100 Subject: [PATCH 7/8] fix rabbit suggestion, rename BLEND_STYLE to TRANSITION --- wled00/FX.h | 56 ++++++++++++------------- wled00/FX_2Dfcn.cpp | 10 ++--- wled00/FX_fcn.cpp | 100 +++++++++++++++++++++++--------------------- 3 files changed, 85 insertions(+), 81 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index e720e1113c..0564e5b40c 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -383,33 +383,33 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define MODE_COUNT 219 -#define BLEND_STYLE_FADE 0x00 // universal -#define BLEND_STYLE_FAIRY_DUST 0x01 // universal -#define BLEND_STYLE_SWIPE_RIGHT 0x02 // 1D or 2D -#define BLEND_STYLE_SWIPE_LEFT 0x03 // 1D or 2D -#define BLEND_STYLE_OUTSIDE_IN 0x04 // 1D or 2D -#define BLEND_STYLE_INSIDE_OUT 0x05 // 1D or 2D -#define BLEND_STYLE_SWIPE_UP 0x06 // 2D -#define BLEND_STYLE_SWIPE_DOWN 0x07 // 2D -#define BLEND_STYLE_OPEN_H 0x08 // 2D -#define BLEND_STYLE_OPEN_V 0x09 // 2D -#define BLEND_STYLE_SWIPE_TL 0x0A // 2D -#define BLEND_STYLE_SWIPE_TR 0x0B // 2D -#define BLEND_STYLE_SWIPE_BR 0x0C // 2D -#define BLEND_STYLE_SWIPE_BL 0x0D // 2D -#define BLEND_STYLE_CIRCULAR_OUT 0x0E // 2D -#define BLEND_STYLE_CIRCULAR_IN 0x0F // 2D +#define TRANSITION_FADE 0x00 // universal +#define TRANSITION_FAIRY_DUST 0x01 // universal +#define TRANSITION_SWIPE_RIGHT 0x02 // 1D or 2D +#define TRANSITION_SWIPE_LEFT 0x03 // 1D or 2D +#define TRANSITION_OUTSIDE_IN 0x04 // 1D or 2D +#define TRANSITION_INSIDE_OUT 0x05 // 1D or 2D +#define TRANSITION_SWIPE_UP 0x06 // 2D +#define TRANSITION_SWIPE_DOWN 0x07 // 2D +#define TRANSITION_OPEN_H 0x08 // 2D +#define TRANSITION_OPEN_V 0x09 // 2D +#define TRANSITION_SWIPE_TL 0x0A // 2D +#define TRANSITION_SWIPE_TR 0x0B // 2D +#define TRANSITION_SWIPE_BR 0x0C // 2D +#define TRANSITION_SWIPE_BL 0x0D // 2D +#define TRANSITION_CIRCULAR_OUT 0x0E // 2D +#define TRANSITION_CIRCULAR_IN 0x0F // 2D // as there are many push variants to optimise if statements they are groupped together -#define BLEND_STYLE_PUSH_RIGHT 0x10 // 1D or 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_LEFT 0x11 // 1D or 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_UP 0x12 // 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_DOWN 0x13 // 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_TL 0x14 // 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_TR 0x15 // 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_BR 0x16 // 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_BL 0x17 // 2D (& 0b00010000) -#define BLEND_STYLE_PUSH_MASK 0x10 -#define BLEND_STYLE_COUNT 18 +#define TRANSITION_PUSH_RIGHT 0x10 // 1D or 2D (& 0b00010000) +#define TRANSITION_PUSH_LEFT 0x11 // 1D or 2D (& 0b00010000) +#define TRANSITION_PUSH_UP 0x12 // 2D (& 0b00010000) +#define TRANSITION_PUSH_DOWN 0x13 // 2D (& 0b00010000) +#define TRANSITION_PUSH_TL 0x14 // 2D (& 0b00010000) +#define TRANSITION_PUSH_TR 0x15 // 2D (& 0b00010000) +#define TRANSITION_PUSH_BR 0x16 // 2D (& 0b00010000) +#define TRANSITION_PUSH_BL 0x17 // 2D (& 0b00010000) +#define TRANSITION_PUSH_MASK 0x10 +#define TRANSITION_COUNT 18 typedef enum mapping1D2D { @@ -462,7 +462,7 @@ class Segment { bool check2 : 1; // checkmark 2 bool check3 : 1; // checkmark 3 }; - uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn, stencil + uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, average, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn, stencil char *name; // segment name // runtime data @@ -561,7 +561,7 @@ class Segment { inline static void modeBlend(bool blend) { Segment::_modeBlend = blend; } inline static void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; - inline static bool isPreviousMode() { return Segment::_modeBlend; } // needed for determining CCT/opacity during non-BLEND_STYLE_FADE transition + inline static bool isPreviousMode() { return Segment::_modeBlend; } // needed for determining CCT/opacity during non-TRANSITION_FADE transition static void handleRandomPalette(); diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 77e466588a..673e0c6406 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -149,14 +149,14 @@ void WS2812FX::setUpMatrix() { // pixel is clipped if it falls outside clipping range // if clipping start > stop the clipping range is inverted bool Segment::isPixelXYClipped(int x, int y) const { - if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { + if (blendingStyle != TRANSITION_FADE && isInTransition() && _clipStart != _clipStop) { const bool invertX = _clipStart > _clipStop; const bool invertY = _clipStartY > _clipStopY; const int cStartX = invertX ? _clipStop : _clipStart; const int cStopX = invertX ? _clipStart : _clipStop; const int cStartY = invertY ? _clipStopY : _clipStartY; const int cStopY = invertY ? _clipStartY : _clipStopY; - if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + if (blendingStyle == TRANSITION_FAIRY_DUST) { const unsigned width = cStopX - cStartX; // assumes full segment width (faster than virtualWidth()) const unsigned len = width * (cStopY - cStartY); // assumes full segment height (faster than virtualHeight()) if (len < 2) return false; @@ -164,10 +164,10 @@ bool Segment::isPixelXYClipped(int x, int y) const { const unsigned pos = (shuffled * 0xFFFFU) / len; return progress() <= pos; } - if (blendingStyle == BLEND_STYLE_CIRCULAR_IN || blendingStyle == BLEND_STYLE_CIRCULAR_OUT) { + if (blendingStyle == TRANSITION_CIRCULAR_IN || blendingStyle == TRANSITION_CIRCULAR_OUT) { const int cx = (cStopX-cStartX+1) / 2; const int cy = (cStopY-cStartY+1) / 2; - const bool out = (blendingStyle == BLEND_STYLE_CIRCULAR_OUT); + const bool out = (blendingStyle == TRANSITION_CIRCULAR_OUT); const unsigned prog = out ? progress() : 0xFFFFU - progress(); int radius2 = max(cx, cy) * prog / 0xFFFF; radius2 = 2 * radius2 * radius2; @@ -179,7 +179,7 @@ bool Segment::isPixelXYClipped(int x, int y) const { } bool xInside = (x >= cStartX && x < cStopX); if (invertX) xInside = !xInside; bool yInside = (y >= cStartY && y < cStopY); if (invertY) yInside = !yInside; - const bool clip = blendingStyle == BLEND_STYLE_OUTSIDE_IN ? xInside || yInside : xInside && yInside; + const bool clip = blendingStyle == TRANSITION_OUTSIDE_IN ? xInside || yInside : xInside && yInside; return !clip; } return false; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f2c260da04..4d60dd0bc5 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -343,7 +343,7 @@ void Segment::updateTransitionProgress() const { uint8_t Segment::currentCCT() const { unsigned prog = progress(); if (prog < 0xFFFFU) { - if (blendingStyle == BLEND_STYLE_FADE) return (cct * prog + (_t->_cct * (0xFFFFU - prog))) / 0xFFFFU; + if (blendingStyle == TRANSITION_FADE) return (cct * prog + (_t->_cct * (0xFFFFU - prog))) / 0xFFFFU; //else return Segment::isPreviousMode() ? _t->_cct : cct; } return cct; @@ -355,7 +355,7 @@ uint8_t Segment::currentBri() const { unsigned curBri = on ? opacity : 0; if (prog < 0xFFFFU) { // this will blend opacity in new mode if style is FADE (single effect call) - if (blendingStyle == BLEND_STYLE_FADE) curBri = (prog * curBri + _t->_bri * (0xFFFFU - prog)) / 0xFFFFU; + if (blendingStyle == TRANSITION_FADE) curBri = (prog * curBri + _t->_bri * (0xFFFFU - prog)) / 0xFFFFU; else curBri = Segment::isPreviousMode() ? _t->_bri : curBri; } return curBri; @@ -371,7 +371,7 @@ void Segment::beginDraw(uint16_t prog) { for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = colors[i]; // load palette into _currentPalette loadPalette(Segment::_currentPalette, palette); - if (isInTransition() && prog < 0xFFFFU && blendingStyle == BLEND_STYLE_FADE) { + if (isInTransition() && prog < 0xFFFFU && blendingStyle == TRANSITION_FADE) { // blend colors for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = color_blend16(_t->_colors[i], colors[i], prog); // blend palettes @@ -506,7 +506,7 @@ Segment &Segment::setColor(uint8_t slot, uint32_t c) { if (slot == 1 && c != BLACK) return *this; // 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(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change + startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast return *this; @@ -530,7 +530,7 @@ Segment &Segment::setCCT(uint16_t k) { Segment &Segment::setOpacity(uint8_t o) { if (opacity != o) { //DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); - startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change + startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast } @@ -541,7 +541,7 @@ Segment &Segment::setOption(uint8_t n, bool val) { bool prev = (options >> n) & 0x01; if (val == prev) return *this; //DEBUG_PRINTF_P(PSTR("- Starting option transition: %d\n"), n); - if (n == SEG_OPTION_ON) startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change + if (n == SEG_OPTION_ON) startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); stateChanged = true; // send UDP/WS broadcast @@ -588,7 +588,7 @@ Segment &Segment::setPalette(uint8_t pal) { if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette if (pal != palette) { //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); - startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment) + startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change (no need to copy segment) palette = pal; stateChanged = true; // send UDP/WS broadcast } @@ -692,11 +692,11 @@ uint16_t Segment::maxMappingLength() const { // pixel is clipped if it falls outside clipping range // if clipping start > stop the clipping range is inverted bool Segment::isPixelClipped(int i) const { - if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { + if (blendingStyle != TRANSITION_FADE && isInTransition() && _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) { + if (blendingStyle == TRANSITION_FAIRY_DUST) { unsigned len = stop - start; if (len < 2) return false; unsigned shuffled = hashInt(i) % len; @@ -1304,7 +1304,7 @@ void WS2812FX::service() { // if segment is in transition and no old segment exists we don't need to run the old mode // (blendSegments() takes care of On/Off transitions and clipping) Segment *segO = seg.getOldSegment(); - if (segO && segO->isActive() && (seg.mode != segO->mode || blendingStyle != BLEND_STYLE_FADE || + if (segO && segO->isActive() && (seg.mode != segO->mode || blendingStyle != TRANSITION_FADE || (segO->name != seg.name && segO->name && seg.name && strncmp(segO->name, seg.name, WLED_MAX_SEGNAME_LEN) != 0))) { Segment::modeBlend(true); // set semaphore for beginDraw() to blend colors and palette segO->beginDraw(prog); // set up palette & colors (also sets draw dimensions), parent segment has transition progress @@ -1408,58 +1408,58 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { Segment::setClippingRect(0, 0); // disable clipping by default - const unsigned dw = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1; - const unsigned dh = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * height / 0xFFFFU + 1; + const unsigned dw = (blendingStyle==TRANSITION_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1; + const unsigned dh = (blendingStyle==TRANSITION_OUTSIDE_IN ? progInv : progress) * height / 0xFFFFU + 1; const unsigned orgBS = blendingStyle; - if (width*height == 1) blendingStyle = BLEND_STYLE_FADE; // disable style for single pixel segments (use fade instead) + if (width*height == 1) blendingStyle = TRANSITION_FADE; // disable style for single pixel segments (use fade instead) switch (blendingStyle) { - case BLEND_STYLE_CIRCULAR_IN: // (must set entire segment, see isPixelXYClipped()) - case BLEND_STYLE_CIRCULAR_OUT:// (must set entire segment, see isPixelXYClipped()) - case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + case TRANSITION_CIRCULAR_IN: // (must set entire segment, see isPixelXYClipped()) + case TRANSITION_CIRCULAR_OUT:// (must set entire segment, see isPixelXYClipped()) + case TRANSITION_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, width, 0, height); break; - case BLEND_STYLE_SWIPE_RIGHT: // left-to-right - case BLEND_STYLE_PUSH_RIGHT: // left-to-right + case TRANSITION_SWIPE_RIGHT: // left-to-right + case TRANSITION_PUSH_RIGHT: // left-to-right Segment::setClippingRect(0, dw, 0, height); break; - case BLEND_STYLE_SWIPE_LEFT: // right-to-left - case BLEND_STYLE_PUSH_LEFT: // right-to-left + case TRANSITION_SWIPE_LEFT: // right-to-left + case TRANSITION_PUSH_LEFT: // right-to-left Segment::setClippingRect(width - dw, width, 0, height); break; - case BLEND_STYLE_OUTSIDE_IN: // corners + case TRANSITION_OUTSIDE_IN: // corners Segment::setClippingRect((width + dw)/2, (width - dw)/2, (height + dh)/2, (height - dh)/2); // inverted!! break; - case BLEND_STYLE_INSIDE_OUT: // outward + case TRANSITION_INSIDE_OUT: // outward Segment::setClippingRect((width - dw)/2, (width + dw)/2, (height - dh)/2, (height + dh)/2); break; - case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) - case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D) + case TRANSITION_SWIPE_DOWN: // top-to-bottom (2D) + case TRANSITION_PUSH_DOWN: // top-to-bottom (2D) Segment::setClippingRect(0, width, 0, dh); break; - case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) - case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D) + case TRANSITION_SWIPE_UP: // bottom-to-top (2D) + case TRANSITION_PUSH_UP: // bottom-to-top (2D) Segment::setClippingRect(0, width, height - dh, height); break; - case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + case TRANSITION_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D Segment::setClippingRect((width - dw)/2, (width + dw)/2, 0, height); break; - case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + case TRANSITION_OPEN_V: // vertical-outward (2D) Segment::setClippingRect(0, width, (height - dh)/2, (height + dh)/2); break; - case BLEND_STYLE_SWIPE_TL: // TL-to-BR (2D) - case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + case TRANSITION_SWIPE_TL: // TL-to-BR (2D) + case TRANSITION_PUSH_TL: // TL-to-BR (2D) Segment::setClippingRect(0, dw, 0, dh); break; - case BLEND_STYLE_SWIPE_TR: // TR-to-BL (2D) - case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + case TRANSITION_SWIPE_TR: // TR-to-BL (2D) + case TRANSITION_PUSH_TR: // TR-to-BL (2D) Segment::setClippingRect(width - dw, width, 0, dh); break; - case BLEND_STYLE_SWIPE_BR: // BR-to-TL (2D) - case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + case TRANSITION_SWIPE_BR: // BR-to-TL (2D) + case TRANSITION_PUSH_BR: // BR-to-TL (2D) Segment::setClippingRect(width - dw, width, height - dh, height); break; - case BLEND_STYLE_SWIPE_BL: // BL-to-TR (2D) - case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + case TRANSITION_SWIPE_BL: // BL-to-TR (2D) + case TRANSITION_PUSH_BL: // BL-to-TR (2D) Segment::setClippingRect(0, dw, height - dh, height); break; } @@ -1497,17 +1497,21 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { }; // if we blend using "push" style we need to "shift" canvas to left/right/up/down - unsigned offsetX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : progInv * nCols / 0xFFFFU; - unsigned offsetY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU; + unsigned offsetX = (blendingStyle == TRANSITION_PUSH_UP || blendingStyle == TRANSITION_PUSH_DOWN) ? 0 : progInv * nCols / 0xFFFFU; + unsigned offsetY = (blendingStyle == TRANSITION_PUSH_LEFT || blendingStyle == TRANSITION_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU; const unsigned groupLen = topSegment.groupLength(); bool applyReverse = topSegment.reverse || topSegment.reverse_y || topSegment.transpose; int pushOffsetX = 0, pushOffsetY = 0; // if we blend using "push" style we need to "shift" canvas to left/right/up/down switch (blendingStyle) { - case BLEND_STYLE_PUSH_RIGHT: pushOffsetX = offsetX; break; - case BLEND_STYLE_PUSH_LEFT: pushOffsetX = -offsetX + nCols; break; - case BLEND_STYLE_PUSH_DOWN: pushOffsetY = offsetY; break; - case BLEND_STYLE_PUSH_UP: pushOffsetY = -offsetY + nRows; break; + case TRANSITION_PUSH_RIGHT: pushOffsetX = offsetX; break; + case TRANSITION_PUSH_LEFT: pushOffsetX = -offsetX + nCols; break; + case TRANSITION_PUSH_DOWN: pushOffsetY = offsetY; break; + case TRANSITION_PUSH_UP: pushOffsetY = -offsetY + nRows; break; + case TRANSITION_PUSH_TL: pushOffsetX = offsetX; pushOffsetY = offsetY; break; // unused + case TRANSITION_PUSH_TR: pushOffsetX = -offsetX + nCols; pushOffsetY = offsetY; break; // unused + case TRANSITION_PUSH_BR: pushOffsetX = -offsetX + nCols; pushOffsetY = -offsetY + nRows; break; // unused + case TRANSITION_PUSH_BL: pushOffsetX = offsetX; pushOffsetY = -offsetY + nRows; break; // unused } // we only traverse new segment, not old one for (int r = 0; r < nRows; r++) for (int c = 0; c < nCols; c++) { @@ -1522,12 +1526,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { if (pushOffsetY != 0) y = (y + pushOffsetY) % nRows; uint32_t c_a = BLACK; if (x < vCols && y < vRows) c_a = seg->getPixelColorRaw(x + y*vCols); // will get clipped pixel from old segment or unclipped pixel from new segment - if (segO && blendingStyle == BLEND_STYLE_FADE + if (segO && blendingStyle == TRANSITION_FADE && (topSegment.mode != segO->mode || (segO->name != topSegment.name && segO->name && topSegment.name && strncmp(segO->name, topSegment.name, WLED_MAX_SEGNAME_LEN) != 0)) && x < oCols && y < oRows) { // we need to blend old segment using fade as pixels are not clipped c_a = color_blend16(c_a, segO->getPixelColorRaw(x + y*oCols), progInv); - } else if (blendingStyle != BLEND_STYLE_FADE) { + } else if (blendingStyle != TRANSITION_FADE) { // if we have global brightness change (not On/Off change) we will ignore transition style and just fade brightness (see led.cpp) // workaround for On/Off transition // (bri != briT) && !bri => from On to Off @@ -1591,15 +1595,15 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { int i = k; // if we blend using "push" style we need to "shift" canvas to left or right switch (blendingStyle) { - case BLEND_STYLE_PUSH_RIGHT: i = (i + offsetI) % nLen; break; - case BLEND_STYLE_PUSH_LEFT: i = (i - offsetI + nLen) % nLen; break; + case TRANSITION_PUSH_RIGHT: i = (i + offsetI) % nLen; break; + case TRANSITION_PUSH_LEFT: i = (i - offsetI + nLen) % nLen; break; } uint32_t c_a = BLACK; if (i < vLen) c_a = seg->getPixelColorRaw(i); // will get clipped pixel from old segment or unclipped pixel from new segment - if (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && i < oLen) { + if (segO && blendingStyle == TRANSITION_FADE && topSegment.mode != segO->mode && i < oLen) { // we need to blend old segment using fade as pixels are not clipped c_a = color_blend16(c_a, segO->getPixelColorRaw(i), progInv); - } else if (blendingStyle != BLEND_STYLE_FADE) { + } else if (blendingStyle != TRANSITION_FADE) { // if we have global brightness change (not On/Off change) we will ignore transition style and just fade brightness (see led.cpp) // workaround for On/Off transition // (bri != briT) && !bri => from On to Off From 4c524d5bc51dd95b90970d3ce493a3a7add2f53d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Mar 2026 11:22:05 +0100 Subject: [PATCH 8/8] comment out unused bg color --- wled00/FX_fcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 4d60dd0bc5..415d4631dd 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1395,7 +1395,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment) const int width = topSegment.width(); const int height = topSegment.height(); - const uint32_t bgColor = topSegment.colors[1]; // background color for blend modes that need it (e.g. stencil) + //const uint32_t bgColor = topSegment.colors[1]; // background color (unused, could add it to stencil mode if requested) const auto XY = [](int x, int y){ return x + y*Segment::maxWidth; }; const size_t matrixSize = Segment::maxWidth * Segment::maxHeight; const size_t startIndx = XY(topSegment.start, topSegment.startY);