diff --git a/wled00/FX.h b/wled00/FX.h index ddfa3c8777..568cd9078e 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -454,6 +454,8 @@ typedef struct Segment { uint16_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows, but we cannot be sure. uint16_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows, but we cannot be sure. char *name = nullptr; // WLEDMM initialize to nullptr + uint8_t maskId = 0; // WLEDMM segment mask ID (0 = none) + bool maskInvert = false; // WLEDMM invert mask bits (useful for "outside" masks) // runtime data unsigned long next_time; // millis() of next update @@ -469,6 +471,12 @@ typedef struct Segment { void *jMap = nullptr; //WLEDMM jMap private: + uint8_t *_mask = nullptr; // WLEDMM bit-packed mask, 1 bit per virtual pixel + uint16_t _maskW = 0; // WLEDMM mask width in virtual pixels + uint16_t _maskH = 0; // WLEDMM mask height in virtual pixels + size_t _maskLen = 0; // WLEDMM total bits (w*h), not bytes + bool _maskValid = false; // WLEDMM cached dimension check vs virtual size + union { uint8_t _capabilities; struct { @@ -563,6 +571,8 @@ typedef struct Segment { startY(0), stopY(1), name(nullptr), + maskId(0), // WLEDMM + maskInvert(false), // WLEDMM next_time(0), step(0), call(0), @@ -571,6 +581,11 @@ typedef struct Segment { data(nullptr), ledsrgb(nullptr), ledsrgbSize(0), //WLEDMM + _mask(nullptr), // WLEDMM + _maskW(0), // WLEDMM + _maskH(0), // WLEDMM + _maskLen(0), // WLEDMM + _maskValid(false), // WLEDMM _capabilities(0), _dataLen(0), _t(nullptr) @@ -612,6 +627,7 @@ typedef struct Segment { if ((Segment::_globalLeds == nullptr) && !strip_uses_global_leds() && (ledsrgb != nullptr)) {free(ledsrgb); ledsrgb = nullptr;} // WLEDMM we need "!strip_uses_global_leds()" to avoid crashes (#104) if (name) { delete[] name; name = nullptr; } if (_t) { transitional = false; delete _t; _t = nullptr; } + clearMask(); // WLEDMM deallocateData(); } @@ -666,6 +682,8 @@ typedef struct Segment { inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true) inline void markForBlank(void) { needsBlank = true; } // WLEDMM serialize "blank" requests, avoid parallel drawing from different task void setUpLeds(void); // set up leds[] array for loseless getPixelColor() + bool setMask(uint8_t id); // WLEDMM + void clearMask(); // WLEDMM // transition functions void startTransition(uint16_t dur); // transition has to start before actual segment values change @@ -704,6 +722,23 @@ typedef struct Segment { #else inline uint16_t virtualLength(void) const {return _virtuallength;} #endif + [[gnu::hot]] inline bool hasMask(void) const { return _mask != nullptr; } // WLEDMM + [[gnu::hot]] inline bool maskAllows(uint16_t i) const { // WLEDMM + if (!_mask || !_maskValid) return true; + if (size_t(i) >= _maskLen) return false; + // WLEDMM: bit-packed mask (LSB-first): byte = i>>3, bit = i&7 + bool bit = (_mask[i >> 3] >> (i & 7)) & 0x01; + return maskInvert ? !bit : bit; + } + [[gnu::hot]] inline bool maskAllowsXY(int x, int y) const { // WLEDMM + if (!_mask || !_maskValid) return true; + if (x < 0 || y < 0) return false; + size_t idx = size_t(x) + (size_t(y) * _maskW); + if (idx >= _maskLen) return false; + // WLEDMM: row-major (x + y*w), bit-packed mask (LSB-first in each byte) + bool bit = (_mask[idx >> 3] >> (idx & 7)) & 0x01; + return maskInvert ? !bit : bit; + } void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline inline void setPixelColor(int n, CRGB c) { setPixelColor(n, uint32_t(c) & 0x00FFFFFF); } // automatically inline @@ -1044,7 +1079,8 @@ class WS2812FX { // 96 bytes fixInvalidSegments(), show(void), setTargetFps(uint8_t fps), - enumerateLedmaps(); //WLEDMM (from fcn_declare) + enumerateLedmaps(), //WLEDMM (from fcn_declare) + enumerateSegmasks(); // WLEDMM 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)); } void fill(uint32_t c) { for (int i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index d12153d5ff..20b454b854 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -234,6 +234,15 @@ void Segment::startFrame(void) { _2dWidth = _isValid2D ? calc_virtualWidth() : calc_virtualLength(); _virtuallength = calc_virtualLength(); #endif + + // WLEDMM validate mask dimensions against current virtual size + if (_mask) { + uint16_t vW = calc_virtualWidth(); + uint16_t vH = calc_virtualHeight(); + _maskValid = (_maskW == vW && _maskH == vH); + } else { + _maskValid = false; + } } // WLEDMM end @@ -245,6 +254,7 @@ void Segment::startFrame(void) { // * expects scaled color (final brightness) as additional input parameter, plus segment virtualWidth() and virtualHeight() void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_t scaled_col, int cols, int rows) const //WLEDMM { + if (!maskAllowsXY(x, y)) return; // WLEDMM mask gate for 2D pixels unsigned i = UINT_MAX; bool sameColor = false; if (ledsrgb) { // WLEDMM small optimization @@ -301,6 +311,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: const int_fast16_t rows = virtualHeight(); if (x<0 || y<0 || x >= cols || y >= rows) return; // if pixel would fall out of virtual segment just exit + if (!maskAllowsXY(x, y)) return; // WLEDMM mask gate for 2D pixels unsigned i = UINT_MAX; bool sameColor = false; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 996bb57b60..a5dbc3d1ae 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -59,7 +59,7 @@ WLED_create_spinlock(ledsrgb_mux); // to protect deleting Segment::_globalLeds a // WLEDMM experimental . this is a "C style" wrapper for strip.waitUntilIdle(); // This workaround is just needed for the segment class, that does't know about "strip" void strip_wait_until_idle(String whoCalledMe) { -#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE) // WLEDMM experimental +#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE) // WLEDMM experimental if (strip.isServicing() && (strncmp(pcTaskGetTaskName(NULL), "loopTask", 8) != 0)) { // if we are in looptask (arduino loop), its safe to proceed without waiting DEBUG_PRINTLN(whoCalledMe + String(": strip is still drawing effects.")); strip.waitUntilIdle(); @@ -95,6 +95,11 @@ Segment::Segment(const Segment &orig) { data = nullptr; _dataLen = 0; _t = nullptr; + _mask = nullptr; // WLEDMM + _maskLen = 0; // WLEDMM + _maskW = 0; // WLEDMM + _maskH = 0; // WLEDMM + _maskValid = false; // WLEDMM if (ledsrgb && !Segment::_globalLeds) {ledsrgb = nullptr; ledsrgbSize = 0;} // WLEDMM if (orig.name) { name = new(std::nothrow) char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen, true)) memcpy(data, orig.data, orig._dataLen); } @@ -142,6 +147,149 @@ void Segment::allocLeds() { } } +void Segment::clearMask() { // WLEDMM + uint8_t* oldMask = nullptr; + strip_wait_until_idle("Segment::clearMask"); // WLEDMM avoid swapping while renderer is active + if (esp32SemTake(segmentMux, 2100) == pdTRUE) { // WLEDMM serialize mask pointer changes with renderer + oldMask = _mask; + _mask = nullptr; + _maskLen = 0; + _maskW = 0; + _maskH = 0; + _maskValid = false; + maskId = 0; // WLEDMM keep id in sync with buffer + esp32SemGive(segmentMux); + } else { + DEBUG_PRINTLN(F("Segment::clearMask: Failed to acquire segmentMux, skipping clear.")); + return; + } + if (oldMask) free(oldMask); +} + +bool Segment::setMask(uint8_t id) { // WLEDMM + clearMask(); + if (id >= WLED_MAX_SEGMASKS) { + return false; + } + if (id == 0) return true; + + char fileName[24] = {'\0'}; + snprintf_P(fileName, sizeof(fileName), PSTR("/segmask%d.json"), id); + if (!WLED_FS.exists(fileName)) { + USER_PRINTF("Segment mask missing: %s\n", fileName); + maskId = 0; // WLEDMM avoid repeated reload attempts + return false; + } + + File f = WLED_FS.open(fileName, "r"); + if (!f) { + maskId = 0; // WLEDMM avoid repeated reload attempts + return false; + } + + uint16_t w = 0; + uint16_t h = 0; + size_t bitLen = 0; // WLEDMM total mask pixels (w*h) + uint8_t* bits = nullptr; // WLEDMM bit-packed mask, 1 bit per pixel + bool inv = maskInvert; // WLEDMM default to current invert setting + bool ok = false; + + do { + char buf[32] = { '\0' }; + if (!f.find("\"w\":")) break; + f.readBytesUntil('\n', buf, sizeof(buf)-1); + w = atoi(cleanUpName(buf)); + + f.seek(0); + if (!f.find("\"h\":")) break; + memset(buf, 0, sizeof(buf)); + f.readBytesUntil('\n', buf, sizeof(buf)-1); + h = atoi(cleanUpName(buf)); + + if (w == 0 || h == 0) break; + bitLen = size_t(w) * size_t(h); + if (bitLen == 0 || bitLen > (size_t(Segment::maxWidth) * size_t(Segment::maxHeight))) break; + + // WLEDMM pack 8 pixels per byte, LSB-first (bit 0 = pixel 0) + size_t byteLen = (bitLen + 7) / 8; + bits = (uint8_t*)calloc(byteLen, 1); + if (!bits) { + errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag + break; + } + + f.seek(0); + // WLEDMM strict "inv" boolean parsing (true/false only) + if (f.find("\"inv\":")) { + String entry = f.readStringUntil(','); + int end = entry.indexOf('}'); + if (end >= 0) entry.remove(end); + entry.trim(); + if (entry == "true") inv = true; + else if (entry == "false") inv = false; + else break; + } + + f.seek(0); + // WLEDMM strict 0/1 mask array, use streaming parse (ledmap-style) + if (!f.find("\"mask\":")) break; + f.readBytesUntil('[', buf, sizeof(buf)-1); + + size_t i = 0; + bool endOfArray = false; + bool parsedOk = true; + do { + String entry = f.readStringUntil(','); + int bracket = entry.indexOf(']'); + if (bracket >= 0) { + entry.remove(bracket); + endOfArray = true; + } + entry.trim(); + if (entry.length() == 0) { parsedOk = false; break; } + if (i >= bitLen) { parsedOk = false; break; } // WLEDMM guard against overflow + if (entry == "1") bits[i >> 3] |= (0x01U << (i & 7)); // WLEDMM set bit (pixel i) in packed mask + else if (entry != "0") { parsedOk = false; break; } + i++; + if (i > bitLen) { parsedOk = false; break; } + } while (f.available() && !endOfArray); + + if (!parsedOk || !endOfArray || i != bitLen) break; + + ok = true; + } while (false); + + if (ok) { + strip_wait_until_idle("Segment::setMask"); // WLEDMM avoid swapping while renderer is active + // WLEDMM clear segment before enabling mask to avoid stale pixels outside the mask + if (esp32SemTake(busDrawMux, 250) == pdTRUE) { + fill(BLACK); + esp32SemGive(busDrawMux); + } else { + DEBUG_PRINTLN(F("Segment::setMask: Failed to acquire busDrawMux, skipping pre-mask clear.")); + } + if (esp32SemTake(segmentMux, 2100) == pdTRUE) { // WLEDMM serialize mask pointer changes with renderer + _mask = bits; + bits = nullptr; + _maskW = w; + _maskH = h; + _maskLen = bitLen; + maskInvert = inv; + _maskValid = (_maskW == calc_virtualWidth() && _maskH == calc_virtualHeight()); + maskId = id; // WLEDMM commit mask id only on success + esp32SemGive(segmentMux); + } else { + DEBUG_PRINTLN(F("Segment::setMask: Failed to acquire segmentMux, skipping mask update.")); + ok = false; + } + } + + if (!ok && bits) free(bits); + if (!ok) maskId = 0; // WLEDMM avoid repeated reload attempts + f.close(); + return ok; +} + // move constructor --> moves everything (including buffer) from orig to this Segment::Segment(Segment &&orig) noexcept { DEBUG_PRINTLN(F("-- Move segment constructor --")); @@ -163,6 +311,11 @@ Segment::Segment(Segment &&orig) noexcept { orig.ledsrgb = nullptr; //WLEDMM orig.ledsrgbSize = 0; // WLEDMM orig.jMap = nullptr; //WLEDMM jMap + orig._mask = nullptr; // WLEDMM + orig._maskLen = 0; // WLEDMM + orig._maskW = 0; // WLEDMM + orig._maskH = 0; // WLEDMM + orig._maskValid = false; // WLEDMM } // copy assignment --> overwrite segment with orig - deletes old buffers in "this", but does not change orig! @@ -173,6 +326,7 @@ Segment& Segment::operator= (const Segment &orig) { transitional = false; // copied segment cannot be in transition if (name) delete[] name; if (_t) delete _t; + clearMask(); // WLEDMM CRGB* oldLeds = ledsrgb; size_t oldLedsSize = ledsrgbSize; if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); @@ -191,6 +345,11 @@ Segment& Segment::operator= (const Segment &orig) { data = nullptr; _dataLen = 0; _t = nullptr; + _mask = nullptr; // WLEDMM + _maskLen = 0; // WLEDMM + _maskW = 0; // WLEDMM + _maskH = 0; // WLEDMM + _maskValid = false; // WLEDMM //if (!Segment::_globalLeds) {ledsrgb = oldLeds; ledsrgbSize = oldLedsSize;}; // WLEDMM reuse leds instead of ledsrgb = nullptr; if (!Segment::_globalLeds) {ledsrgb = nullptr; ledsrgbSize = 0;}; // WLEDMM copy has no buffers (yet) // copy source data @@ -211,6 +370,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept { transitional = false; // just temporary if (name) { delete[] name; name = nullptr; } // free old name deallocateData(); // free old runtime data + clearMask(); // WLEDMM if (_t) { delete _t; _t = nullptr; } if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); //WLEDMM: not needed anymore as we will use leds from copy. no need to nullify ledsrgb as it gets new value in memcpy @@ -230,6 +390,11 @@ Segment& Segment::operator= (Segment &&orig) noexcept { orig.ledsrgb = nullptr; //WLEDMM: do not free as moved to here orig.ledsrgbSize = 0; //WLEDMM orig.jMap = nullptr; //WLEDMM jMap + orig._mask = nullptr; // WLEDMM + orig._maskLen = 0; // WLEDMM + orig._maskW = 0; // WLEDMM + orig._maskH = 0; // WLEDMM + orig._maskValid = false; // WLEDMM } return *this; } @@ -269,7 +434,7 @@ bool Segment::allocateData(size_t len, bool allowOverdraft) { // WLEDMM allowOv if (!data) { _dataLen = 0; // WLEDMM reset dataLen if ((errorFlag != ERR_LOW_MEM) && (errorFlag != ERR_LOW_SEG_MEM)) { // spam filter - USER_PRINT(F("Segment::allocateData: FAILED to allocate ")); + USER_PRINT(F("Segment::allocateData: FAILED to allocate ")); USER_PRINT(len); USER_PRINTLN(F(" bytes.")); } errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag @@ -457,7 +622,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) c case 71: //WLEDMM netmindz ar palette +1 case 72: //WLEDMM netmindz ar palette +2 case 73: //WLEDMM netmindz ar palette +3 - targetPalette.loadDynamicGradientPalette(getAudioPalette(pal)); break; + targetPalette.loadDynamicGradientPalette(getAudioPalette(pal)); break; default: //progmem palettes if (pal>245) { targetPalette = strip.customPalettes[255-pal]; // we checked bounds above @@ -587,6 +752,14 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t spacing = spc; } if (ofs < UINT16_MAX) offset = ofs; + // WLEDMM keep mask validity aligned with current virtual geometry + if (_mask) { + uint16_t vW = calc_virtualWidth(); + uint16_t vH = calc_virtualHeight(); + _maskValid = (_maskW == vW && _maskH == vH); + } else { + _maskValid = false; + } esp32SemGive(segmentMux); } else { DEBUG_PRINTLN(F("Segment::setUp: Failed to acquire segmentMux, skipping bounds update.")); @@ -767,7 +940,7 @@ class JMapC { return 0; } private: - std::vector jVectorMap; + std::vector jVectorMap; StaticJsonDocument<4096> docChunk; //must fit forks with about 32 points each uint8_t scale=1; @@ -795,7 +968,7 @@ class JMapC { DeserializationError err = deserializeJson(docChunk, jMapFile); // serializeJson(docChunk, Serial); USER_PRINTLN(); // USER_PRINTF("docChunk %u / %u%% (%u %u %u) %u\n", (unsigned int)docChunk.memoryUsage(), 100 * docChunk.memoryUsage() / docChunk.capacity(), (unsigned int)docChunk.size(), docChunk.overflowed(), (unsigned int)docChunk.nesting(), jMapFile.size()); - if (err) + if (err) { USER_PRINTF("deserializeJson() of parseTree failed with code %s\n", err.c_str()); USER_FLUSH(); @@ -948,7 +1121,7 @@ uint16_t Segment::calc_virtualLength() const { case M12_sBlock: //WLEDMM if (nrOfVStrips()>1) vLen = max(vW,vH) * 4;//0.5; // get the longest dimension - else + else vLen = max(vW,vH) * 0.5f; // get the longest dimension break; case M12_sPinwheel: @@ -988,7 +1161,7 @@ static void xyFromBlock(uint16_t &x,uint16_t &y, uint16_t i, uint16_t vW, uint16 y = vH / 2 + vStrip - i2 * vStrip * 2; } // softhack007 not sure if clamping is necessary - //x = min(x, uint16_t(vW-1)); // clamp x at vW-1 + //x = min(x, uint16_t(vW-1)); // clamp x at vW-1 //y = min(y, uint16_t(vH-1)); // clamp y at vH-1 } @@ -1001,6 +1174,8 @@ void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(int i i &= 0xFFFF; if (unsigned(i) >= virtualLength()) return; // if pixel would fall out of segment just exit //WLEDMM unsigned(i)>SEGLEN also catches "i<0" + if (!is2D() && !maskAllows(i)) return; // WLEDMM mask gate for 1D segments + #ifndef WLED_DISABLE_2D if (is2D()) { uint16_t vH = virtualHeight(); // segment height in logical pixels @@ -1021,17 +1196,17 @@ void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(int i setPixelColorXY(0, 0, col); else { if (i == virtualLength() - 1) setPixelColorXY(vW-1, vH-1, col); // Last i always fill corner - if (!_isSuperSimpleSegment) { + if (!_isSuperSimpleSegment) { // WLEDMM: drawArc() is faster if it's NOT "super simple" as the regular M12_pArc // can do "useSymmetry" to speed things along, but a more complicated segment likey // uses mirroring which generates a symmetry speed-up, or other things which mean // less pixels are calculated. - drawArc(0, 0, i, col); + drawArc(0, 0, i, col); } else { //WLEDMM: some optimizations for the drawing loop // pre-calculate loop limits, exploit symmetry at 45deg float radius = float(i); - + // float step = HALF_PI / (2.85f * radius); // upstream uses this float step = HALF_PI / (M_PI * radius); // WLEDMM we use the correct circumference bool useSymmetry = (max(vH, vW) > 20); // for segments wider than 20 pixels, we exploit symmetry @@ -1146,7 +1321,7 @@ void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(int i // Odd rays start further from center if prevRay started at center. static int prevRay = INT_MIN; // previous ray number if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { - int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel + int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel posx += inc_x * jump; posy += inc_y * jump; } @@ -1160,7 +1335,7 @@ void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(int i // set pixel if (x != lastX || y != lastY) { // only paint if pixel position is different if (simpleSegment) setPixelColorXY_fast(x, y, col, scaled_col, vW, vH); - else setPixelColorXY_slow(x, y, col); + else setPixelColorXY_slow(x, y, col); } lastX = x; lastY = y; @@ -1287,7 +1462,7 @@ uint32_t WLED_O2_ATTR __attribute__((hot)) Segment::getPixelColor(int i) const break; case M12_pCorner: case M12_pArc: { - if (i < max(vW, vH)) { + if (i < max(vW, vH)) { return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); // Corner and Arc break; } @@ -1301,7 +1476,7 @@ uint32_t WLED_O2_ATTR __attribute__((hot)) Segment::getPixelColor(int i) const int newX2 = x * x; for (int y = startY; y < vH; y++) { int newY2 = y * y; - if (newX2 + newY2 >= minradius2) return getPixelColorXY(x, y); + if (newX2 + newY2 >= minradius2) return getPixelColorXY(x, y); } } return getPixelColorXY(vW-1, vH-1); // Last pixel @@ -1397,6 +1572,8 @@ uint8_t Segment::differs(Segment& b) const { if (check3 != b.check3) d |= SEG_DIFFERS_FX; if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; + if (maskId != b.maskId) d |= SEG_DIFFERS_OPT; // WLEDMM + if (maskInvert != b.maskInvert) d |= SEG_DIFFERS_OPT; // WLEDMM //bit pattern: (msb first) set:2, sound:1, mapping:3, transposed, mirrorY, reverseY, [transitional, reset,] paused, mirrored, on, reverse, [selected] if ((options & 0b1111111110011110U) != (b.options & 0b1111111110011110U)) d |= SEG_DIFFERS_OPT; @@ -1487,7 +1664,7 @@ void __attribute__((hot)) Segment::fill(uint32_t c) { // Blends the specified color with the existing pixel color. void Segment::blendPixelColor(int n, uint32_t color, uint8_t blend) { - if (blend == UINT8_MAX) setPixelColor(n, color); + if (blend == UINT8_MAX) setPixelColor(n, color); else setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } @@ -1663,7 +1840,7 @@ uint8_t Segment::get_random_wheel_index(uint8_t pos) const { // WLEDMM use fast //WLEDMM netmindz ar palette uint8_t * Segment::getAudioPalette(int pal) const { // https://forum.makerforums.info/t/hi-is-it-possible-to-define-a-gradient-palette-at-runtime-the-define-gradient-palette-uses-the/63339 - + um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); @@ -1677,19 +1854,19 @@ uint8_t * Segment::getAudioPalette(int pal) const { xyz[1] = 0; xyz[2] = 0; xyz[3] = 0; - + CRGB rgb = getCRGBForBand(1, fftResult, pal); xyz[4] = 1; // anchor of first color xyz[5] = rgb.r; xyz[6] = rgb.g; xyz[7] = rgb.b; - + rgb = getCRGBForBand(128, fftResult, pal); xyz[8] = 128; xyz[9] = rgb.r; xyz[10] = rgb.g; xyz[11] = rgb.b; - + rgb = getCRGBForBand(255, fftResult, pal); xyz[12] = 255; // anchor of last color - must be 255 xyz[13] = rgb.r; @@ -1738,7 +1915,7 @@ void WS2812FX::enumerateLedmaps() { if (len > 0 && len < (sizeof(name)-1)) { (void) cleanUpName(name); len = strlen(name); - ledmapNames[i-1] = new(std::nothrow) char[len+1]; // +1 to include terminating \0 + ledmapNames[i-1] = new(std::nothrow) char[len+1]; // +1 to include terminating \0 if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, len+1); } if (!ledmapNames[i-1]) { @@ -1791,6 +1968,16 @@ void WS2812FX::enumerateLedmaps() { } } +// WLEDMM enumerate all segmaskX.json files on FS +void WS2812FX::enumerateSegmasks() { + segMasks = 0; + for (int i = 1; i < WLED_MAX_SEGMASKS; i++) { + char fileName[24] = {'\0'}; + snprintf_P(fileName, sizeof(fileName), PSTR("/segmask%d.json"), i); + if (WLED_FS.exists(fileName)) segMasks |= 1 << i; + } +} + //do not call this method from system context (network callback) void WS2812FX::finalizeInit(void) @@ -1806,6 +1993,7 @@ void WS2812FX::finalizeInit(void) // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs // unfortunately this means we do not get updates after uploads enumerateLedmaps(); + enumerateSegmasks(); // WLEDMM _hasWhiteChannel = _isOffRefreshRequired = false; @@ -1860,7 +2048,7 @@ void WS2812FX::finalizeInit(void) if (Segment::_globalLeds) { // DONG - Valkyrie is about to die [Gauntlet, 1985] // this is a critical section that will be removed with PR #278 which removes _globalLeds - // problem: suspendStripService provides interlocking, but there’s a window before service() observes it, + // problem: suspendStripService provides interlocking, but there’s a window before service() observes it, // and ESP32 is dual-core. A critical section closes that window so the pointer swap is atomic across cores. CRGB* oldGLeds = Segment::_globalLeds; portENTER_CRITICAL(&ledsrgb_mux); @@ -1916,7 +2104,7 @@ void WS2812FX::waitUntilIdle(unsigned timeout) { void WS2812FX::service() { unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days // WLEDMM avoid losing precision if (OTAisRunning) return; // WLEDMM avoid flickering during OTA - + //#ifdef ARDUINO_ARCH_ESP32 //if ((_isServicing == true) && (strncmp(pcTaskGetTaskName(NULL), "loopTask", 8) != 0)) return; // WLEDMM experimental: not in looptask context - avoid self-blocking (DDP over webSockets) //#endif @@ -1979,7 +2167,7 @@ void WS2812FX::service() { if (frameDelay < speedLimit) frameDelay = FRAMETIME; // WLEDMM limit effects that want to go faster than target FPS if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; - if (seg.transitional && frameDelay > max(int(FRAMETIME), int(FRAMETIME_FIXED))) + if (seg.transitional && frameDelay > max(int(FRAMETIME), int(FRAMETIME_FIXED))) frameDelay = max(int(FRAMETIME), int(FRAMETIME_FIXED)); // force faster updates during transition // WLEDMM only if effect requested very slow updates seg.lastBri = seg.currentBri(seg.on ? seg.opacity:0); // WLEDMM remember for next time @@ -2708,7 +2896,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { if ((size > 0) && (customMappingTable == nullptr)) { // second try DEBUG_PRINTLN("deserializeMap: trying to get fresh memory block."); customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t)); - if (customMappingTable == nullptr) { + if (customMappingTable == nullptr) { DEBUG_PRINTLN("deserializeMap: alloc failed!"); errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag } @@ -2740,7 +2928,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { loadedLedmap = n; f.close(); - if ((customMappingTable != nullptr) && (customMappingSize>0)) { + if ((customMappingTable != nullptr) && (customMappingSize>0)) { USER_PRINTF(PSTR("Ledmap #%d read. Size=%d (%d x %d); %d items found.\n"), loadedLedmap, customMappingSize, Segment::maxWidth, Segment::maxHeight, i); } #ifdef WLED_DEBUG_MAPS diff --git a/wled00/const.h b/wled00/const.h index c81854dad0..7b04172292 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -91,6 +91,18 @@ #endif #endif +// WLEDMM segment mask slots (filesystem segmaskX.json) +#if defined(WLED_MAX_SEGMASKS) && (WLED_MAX_SEGMASKS > 32 || WLED_MAX_SEGMASKS < 4) + #undef WLED_MAX_SEGMASKS +#endif +#ifndef WLED_MAX_SEGMASKS + #ifdef ESP8266 + #define WLED_MAX_SEGMASKS 10 + #else + #define WLED_MAX_SEGMASKS 16 + #endif +#endif + #ifndef WLED_MAX_SEGNAME_LEN #ifdef ESP8266 #define WLED_MAX_SEGNAME_LEN 32 diff --git a/wled00/data/index.js b/wled00/data/index.js index d1e367dd55..5b898b150d 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -26,7 +26,7 @@ var ws, cpick, ranges; var cfg = { theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, - labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, segexp:true, + labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, segexp:true, css:true, hdays:false, fxdef:true, fxdef2:false} //WLEDMM segexp true as default, fxdef2 added }; var hol = [ @@ -722,13 +722,13 @@ ${inforow("Average FPS",i.leds.fps)} ${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} ${inforow("MAC address",i.mac)} ${inforow("Uptime",getRuntimeStr(i.uptime))} - +
${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB, " +Math.round(i.fs.u*100/i.fs.t) + "%")} ${theap>0?inforow("Heap ☾",((i.totalheap-i.freeheap)/1000).toFixed(0)+"/"+theap.toFixed(0)+" kB",", "+Math.round((i.totalheap-i.freeheap)/(10*theap))+"%"):inforow("Free heap",heap," kB")} -${i.minfreeheap?inforow("Max used heap ☾",((i.totalheap-i.minfreeheap)/1000).toFixed(0)+" kB",", "+Math.round((i.totalheap-i.minfreeheap)/(10*theap))+"%"):""} -${i.psram?inforow("PSRAM ☾",((i.tpsram-i.psram)/1024).toFixed(0)+"/"+(i.tpsram/1024).toFixed(0)+" kB",", "+((i.tpsram-i.psram)*100.0/i.tpsram).toFixed(1)+"%"):""} -${i.psusedram?inforow("Max used PSRAM ☾",((i.tpsram-i.psusedram)/1024).toFixed(0)+" kB",", "+((i.tpsram-i.psusedram)*100.0/i.tpsram).toFixed(1)+"%"):""} +${i.minfreeheap?inforow("Max used heap ☾",((i.totalheap-i.minfreeheap)/1000).toFixed(0)+" kB",", "+Math.round((i.totalheap-i.minfreeheap)/(10*theap))+"%"):""} +${i.psram?inforow("PSRAM ☾",((i.tpsram-i.psram)/1024).toFixed(0)+"/"+(i.tpsram/1024).toFixed(0)+" kB",", "+((i.tpsram-i.psram)*100.0/i.tpsram).toFixed(1)+"%"):""} +${i.psusedram?inforow("Max used PSRAM ☾",((i.tpsram-i.psusedram)/1024).toFixed(0)+" kB",", "+((i.tpsram-i.psusedram)*100.0/i.tpsram).toFixed(1)+"%"):""} ${i.freestack?inforow("Free stack ☾",(i.freestack/1000).toFixed(3)," kB"):""}
${i.tpsram?inforow("PSRAM " + (i.psrmode?"("+i.psrmode+" mode) ":"") + " ☾",(i.tpsram/1024/1024).toFixed(0)," MB"):inforow("NO PSRAM found.", "")} @@ -740,7 +740,7 @@ ${i.repo?inforow("Github",i.repo):""} ${i.e32code?inforow("Last ESP Restart ☾",i.e32code+" "+i.e32text):""} ${i.e32core0code?inforow("Core0 rst reason ☾",i.e32core0code, " "+i.e32core0text):""} ${i.e32core1code?inforow("Core1 rst reason ☾",i.e32core1code, " "+i.e32core1text):""} - + `; gId('kv').innerHTML = cn; // update all sliders in Info @@ -791,6 +791,10 @@ function populateSegments(s) let staY = inst.startY; let stoY = inst.stopY; let isMSeg = isM && staX 0); // WLEDMM let rvXck = ``; let miXck = ``; let rvYck = "", miYck =""; @@ -817,6 +821,35 @@ function populateSegments(s) ``+ ``+ ``; + // WLEDMM begin: segment mask UI + let maskSel = ""; + let maskInvSel = ""; + let maskInfo = ""; + let maskList = Array.isArray(li.masks) ? li.masks : []; + if (maskList.length > 0 || maskId > 0) { + let opts = ``; + let hasMaskId = false; + for (const m of maskList) { + if (m.id == maskId) hasMaskId = true; + opts += ``; + } + if (maskId > 0 && !hasMaskId) { + opts += ``; // WLEDMM keep unknown mask visible + } + maskSel = `
Mask ☾
`+ + `
`+ + `
`; + if (maskActive) { + maskInvSel = ``; + maskInfo = `
Mask clips output ☾; bounds and mapping still apply.
`; // WLEDMM + } + } + // WLEDMM end: segment mask UI //WLEDMM ARTIFX let fxName = eJson.find((o)=>{return o.id==selectedFx}).name; let cusEff = `
`; @@ -870,6 +903,7 @@ function populateSegments(s) `
`+ (!isMSeg ? rvXck : '') + (isMSeg&&stoY-staY>1&&stoX-staX>1 ? map2D : '') + + maskSel + maskInvSel + maskInfo + // WLEDMM (s.AudioReactive && s.AudioReactive.on ? "" : sndSim) + (s.ARTIFX && s.ARTIFX.on && fxName.includes("ARTI-FX") ? cusEff : "") + // `