From c16462a0ce956b5a40bdfeb5db6c227c6e2a7cd7 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Fri, 30 Jun 2023 23:59:29 +0200 Subject: [PATCH 1/5] Experimental GIF support --- platformio.ini | 2 + wled00/FX.cpp | 12 +++++ wled00/FX.h | 2 +- wled00/fcn_declare.h | 11 ++++- wled00/image_loader.cpp | 104 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 wled00/image_loader.cpp diff --git a/platformio.ini b/platformio.ini index d3b71d3c4..1fd4d1aa2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -240,6 +240,8 @@ default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + bitbank2/AnimatedGIF@^1.4.7 + pixelmatix/GifDecoder@^1.1.0 ${env.lib_deps} diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f5fefd5b4..2db036e8c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4189,6 +4189,16 @@ uint16_t mode_washing_machine(void) { static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,!;;!"; +/* + Image effect + Draws a .gif image from filesystem on the matrix/strip +*/ +uint16_t mode_image(void) { + renderImageToSegment(SEGMENT); + return FRAMETIME; +} +static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@;;;12"; + /* Blends random colors across palette Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e @@ -7734,6 +7744,8 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); + addEffect(FX_MODE_IMAGE, &mode_image, _data_FX_MODE_IMAGE); + addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); diff --git a/wled00/FX.h b/wled00/FX.h index 19b1fc4ac..045e3fc18 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -242,7 +242,7 @@ #define FX_MODE_CHUNCHUN 111 #define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_WASHING_MACHINE 113 -// #define FX_MODE_CANDY_CANE 114 // removed in 0.14! +#define FX_MODE_IMAGE 114 // was Candy Cane #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 // candidate for removal (check3 in dynamic) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index c67fdbf38..72d589d49 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -5,6 +5,8 @@ * All globally accessible functions are declared here */ +#include "FX.h" + //alexa.cpp #ifndef WLED_DISABLE_ALEXA void onAlexaChange(EspalexaDevice* dev); @@ -103,6 +105,14 @@ void onHueConnect(void* arg, AsyncClient* client); void sendHuePoll(); void onHueData(void* arg, AsyncClient* client, void *data, size_t len); +//image_loader.cpp +bool fileSeekCallback(unsigned long position); +unsigned long filePositionCallback(void); +int fileReadCallback(void); +int fileReadBlockCallback(void * buffer, int numberOfBytes); +int fileSizeCallback(void); +bool renderImageToSegment(Segment &seg); + //improv.cpp enum ImprovRPCType { Command_Wifi = 0x01, @@ -140,7 +150,6 @@ void handleIR(); #include "ESPAsyncWebServer.h" #include "src/dependencies/json/ArduinoJson-v6.h" #include "src/dependencies/json/AsyncJson-v6.h" -#include "FX.h" bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0); bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0); diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp new file mode 100644 index 000000000..b26f6fe1d --- /dev/null +++ b/wled00/image_loader.cpp @@ -0,0 +1,104 @@ +#include "GifDecoder.h" +#include "wled.h" + +File file; +char lastFilename[34] = "/"; +GifDecoder<64, 64, 12, true> decoder; +bool gifDecodeFailed = false; +long lastFrameDisplayTime = 0, currentFrameDelay = 0; + +bool fileSeekCallback(unsigned long position) { + return file.seek(position); +} + +unsigned long filePositionCallback(void) { + return file.position(); +} + +int fileReadCallback(void) { + return file.read(); +} + +int fileReadBlockCallback(void * buffer, int numberOfBytes) { + return file.read((uint8_t*)buffer, numberOfBytes); +} + +int fileSizeCallback(void) { + return file.size(); +} + +bool openGif(const char *filename) { + file = WLED_FS.open(filename, "r"); + + if (!file) return false; + return true; +} + +Segment* activeSeg; +uint16_t gifWidth, gifHeight; +uint16_t fillPixX, fillPixY; + +void screenClearCallback(void) { + activeSeg->fill(0); +} + +void updateScreenCallback(void) {} + +void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) { + // simple nearest-neighbor scaling + int16_t outY = y * activeSeg->height() / gifHeight; + int16_t outX = x * activeSeg->width() / gifWidth; + // set multiple pixels if upscaling + for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) { + for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) { + activeSeg->setPixelColorXY(outX + i, outY + j, red, green, blue); + } + } +} + +// renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment +bool renderImageToSegment(Segment &seg) { + if (!seg.name) return false; + activeSeg = &seg; + + if (strncmp(lastFilename +1, seg.name, 32) != 0) { + Serial.println("segname changed"); + strncpy(lastFilename +1, seg.name, 32); + gifDecodeFailed = false; + if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) { + //DEBUG_PRINTLN(F("Image file not found or not a .gif")); + gifDecodeFailed = true; + return false; + } + if (file) file.close(); + Serial.print("opening gif: "); + Serial.println(openGif(lastFilename)); + if (!file) { gifDecodeFailed = true; return false; } + decoder.setScreenClearCallback(screenClearCallback); + decoder.setUpdateScreenCallback(updateScreenCallback); + decoder.setDrawPixelCallback(drawPixelCallback); + decoder.setFileSeekCallback(fileSeekCallback); + decoder.setFilePositionCallback(filePositionCallback); + decoder.setFileReadCallback(fileReadCallback); + decoder.setFileReadBlockCallback(fileReadBlockCallback); + decoder.setFileSizeCallback(fileSizeCallback); + Serial.println("Starting decoding"); + if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return false; } + Serial.println("Decoding started"); + } + + if (gifDecodeFailed) return false; + if (!file) { gifDecodeFailed = true; return false; } + + if((millis() - lastFrameDisplayTime) > currentFrameDelay) { + decoder.getSize(&gifWidth, &gifHeight); + fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; + fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; + Serial.println("decoding frame"); + int result = decoder.decodeFrame(false); + if (result < 0) { gifDecodeFailed = true; return false; } + currentFrameDelay = decoder.getFrameDelay_ms(); + lastFrameDisplayTime = millis(); + } + return true; +} \ No newline at end of file From 7119999df8055d6bbca9d0bba459ecb1aea57346 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Sat, 1 Jul 2023 13:20:10 +0200 Subject: [PATCH 2/5] Gamma and speed control --- wled00/FX.cpp | 7 ++++++- wled00/image_loader.cpp | 19 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2db036e8c..abdbe5da8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4194,10 +4194,15 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! Draws a .gif image from filesystem on the matrix/strip */ uint16_t mode_image(void) { + SEGMENT.setUpLeds(); // temporary per-segment buffering renderImageToSegment(SEGMENT); + for (uint16_t i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i)); // temporary, refresh all LEDs for lossy ABL + } + return FRAMETIME; } -static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@;;;12"; +static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128"; /* Blends random colors across palette diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index b26f6fe1d..ad4ca0753 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -3,7 +3,7 @@ File file; char lastFilename[34] = "/"; -GifDecoder<64, 64, 12, true> decoder; +GifDecoder<64,64,12,true> decoder; bool gifDecodeFailed = false; long lastFrameDisplayTime = 0, currentFrameDelay = 0; @@ -51,7 +51,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t // set multiple pixels if upscaling for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) { for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) { - activeSeg->setPixelColorXY(outX + i, outY + j, red, green, blue); + activeSeg->setPixelColorXY(outX + i, outY + j, gamma8(red), gamma8(green), gamma8(blue)); } } } @@ -62,7 +62,7 @@ bool renderImageToSegment(Segment &seg) { activeSeg = &seg; if (strncmp(lastFilename +1, seg.name, 32) != 0) { - Serial.println("segname changed"); + //Serial.println("segname changed"); strncpy(lastFilename +1, seg.name, 32); gifDecodeFailed = false; if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) { @@ -71,9 +71,11 @@ bool renderImageToSegment(Segment &seg) { return false; } if (file) file.close(); - Serial.print("opening gif: "); - Serial.println(openGif(lastFilename)); + //Serial.print("opening gif: "); + //Serial.println(openGif(lastFilename)); + openGif(lastFilename); if (!file) { gifDecodeFailed = true; return false; } + //decoder = new GifDecoder<64,64,12,true>(); decoder.setScreenClearCallback(screenClearCallback); decoder.setUpdateScreenCallback(updateScreenCallback); decoder.setDrawPixelCallback(drawPixelCallback); @@ -90,11 +92,14 @@ bool renderImageToSegment(Segment &seg) { if (gifDecodeFailed) return false; if (!file) { gifDecodeFailed = true; return false; } - if((millis() - lastFrameDisplayTime) > currentFrameDelay) { + // speed 0 = half speed, 128 = normal, 255 = as fast as possible + // TODO: 0 = 4x slow, 64 = 2x slow, 128 = normal, 192 = 2x fast, 255 = 4x fast + uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128; + + if((millis() - lastFrameDisplayTime) > wait) { decoder.getSize(&gifWidth, &gifHeight); fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; - Serial.println("decoding frame"); int result = decoder.decodeFrame(false); if (result < 0) { gifDecodeFailed = true; return false; } currentFrameDelay = decoder.getFrameDelay_ms(); From ed69692f08d25b3334df7eea5bab07d17a2c5e55 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Sat, 1 Jul 2023 15:36:41 +0200 Subject: [PATCH 3/5] Dynamic --- wled00/image_loader.cpp | 44 +++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index ad4ca0753..af9075485 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -3,7 +3,7 @@ File file; char lastFilename[34] = "/"; -GifDecoder<64,64,12,true> decoder; +GifDecoder<320,320,12,true>* decoder; bool gifDecodeFailed = false; long lastFrameDisplayTime = 0, currentFrameDelay = 0; @@ -75,35 +75,45 @@ bool renderImageToSegment(Segment &seg) { //Serial.println(openGif(lastFilename)); openGif(lastFilename); if (!file) { gifDecodeFailed = true; return false; } - //decoder = new GifDecoder<64,64,12,true>(); - decoder.setScreenClearCallback(screenClearCallback); - decoder.setUpdateScreenCallback(updateScreenCallback); - decoder.setDrawPixelCallback(drawPixelCallback); - decoder.setFileSeekCallback(fileSeekCallback); - decoder.setFilePositionCallback(filePositionCallback); - decoder.setFileReadCallback(fileReadCallback); - decoder.setFileReadBlockCallback(fileReadBlockCallback); - decoder.setFileSizeCallback(fileSizeCallback); + if (!decoder) decoder = new GifDecoder<320,320,12,true>(); + decoder->setScreenClearCallback(screenClearCallback); + decoder->setUpdateScreenCallback(updateScreenCallback); + decoder->setDrawPixelCallback(drawPixelCallback); + decoder->setFileSeekCallback(fileSeekCallback); + decoder->setFilePositionCallback(filePositionCallback); + decoder->setFileReadCallback(fileReadCallback); + decoder->setFileReadBlockCallback(fileReadBlockCallback); + decoder->setFileSizeCallback(fileSizeCallback); Serial.println("Starting decoding"); - if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return false; } + if(decoder->startDecoding() < 0) { gifDecodeFailed = true; return false; } Serial.println("Decoding started"); } if (gifDecodeFailed) return false; - if (!file) { gifDecodeFailed = true; return false; } + if (!file || !decoder) { gifDecodeFailed = true; return false; } - // speed 0 = half speed, 128 = normal, 255 = as fast as possible + // speed 0 = half speed, 128 = normal, 255 = full FX FPS // TODO: 0 = 4x slow, 64 = 2x slow, 128 = normal, 192 = 2x fast, 255 = 4x fast uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128; - if((millis() - lastFrameDisplayTime) > wait) { - decoder.getSize(&gifWidth, &gifHeight); + if((millis() - lastFrameDisplayTime) >= wait) { + decoder->getSize(&gifWidth, &gifHeight); fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; - int result = decoder.decodeFrame(false); + int result = decoder->decodeFrame(false); if (result < 0) { gifDecodeFailed = true; return false; } - currentFrameDelay = decoder.getFrameDelay_ms(); + long lastFrameDelay = currentFrameDelay; + currentFrameDelay = decoder->getFrameDelay_ms(); + long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate + currentFrameDelay -= tooSlowBy; + //currentFrameDelay -= LASTFRAMEDELAY; lastFrameDisplayTime = millis(); } return true; +} + +void endPlayback() { + if (file) file.close(); + delete decoder; + gifDecodeFailed = false; } \ No newline at end of file From 2f9c126d34ecc2f1c69c56824aa96000c37f30d4 Mon Sep 17 00:00:00 2001 From: Aircoookie <21045690+Aircoookie@users.noreply.github.com> Date: Mon, 31 Jul 2023 18:13:03 +0200 Subject: [PATCH 4/5] GIF Error codes --- wled00/fcn_declare.h | 4 +++- wled00/image_loader.cpp | 45 ++++++++++++++++++++++++++--------------- wled00/wled.h | 2 ++ 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 72d589d49..df793a5a7 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -106,12 +106,14 @@ void sendHuePoll(); void onHueData(void* arg, AsyncClient* client, void *data, size_t len); //image_loader.cpp +#ifndef WLED_DISABLE_GIF bool fileSeekCallback(unsigned long position); unsigned long filePositionCallback(void); int fileReadCallback(void); int fileReadBlockCallback(void * buffer, int numberOfBytes); int fileSizeCallback(void); -bool renderImageToSegment(Segment &seg); +byte renderImageToSegment(Segment &seg); +#endif //improv.cpp enum ImprovRPCType { diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index af9075485..038a7d0b3 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -1,3 +1,5 @@ +#ifndef WLED_DISABLE_GIF + #include "GifDecoder.h" #include "wled.h" @@ -56,26 +58,34 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t } } +#define IMAGE_ERROR_NONE 0 +#define IMAGE_ERROR_NO_NAME 1 +#define IMAGE_ERROR_SEG_LIMIT 2 +#define IMAGE_ERROR_UNSUPPORTED_FORMAT 3 +#define IMAGE_ERROR_FILE_MISSING 4 +#define IMAGE_ERROR_DECODER_ALLOC 5 +#define IMAGE_ERROR_GIF_DECODE 6 +#define IMAGE_ERROR_FRAME_DECODE 7 +#define IMAGE_ERROR_PREV 255 + // renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment -bool renderImageToSegment(Segment &seg) { - if (!seg.name) return false; +byte renderImageToSegment(Segment &seg) { + if (!seg.name) return IMAGE_ERROR_NO_NAME; + if (activeSeg && activeSeg != &seg) return IMAGE_ERROR_SEG_LIMIT; // only one segment at a time activeSeg = &seg; - if (strncmp(lastFilename +1, seg.name, 32) != 0) { - //Serial.println("segname changed"); + if (strncmp(lastFilename +1, seg.name, 32) != 0) { // segment name changed, load new image strncpy(lastFilename +1, seg.name, 32); gifDecodeFailed = false; if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) { - //DEBUG_PRINTLN(F("Image file not found or not a .gif")); gifDecodeFailed = true; - return false; + return IMAGE_ERROR_UNSUPPORTED_FORMAT; } if (file) file.close(); - //Serial.print("opening gif: "); - //Serial.println(openGif(lastFilename)); openGif(lastFilename); - if (!file) { gifDecodeFailed = true; return false; } + if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } if (!decoder) decoder = new GifDecoder<320,320,12,true>(); + if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } decoder->setScreenClearCallback(screenClearCallback); decoder->setUpdateScreenCallback(updateScreenCallback); decoder->setDrawPixelCallback(drawPixelCallback); @@ -85,12 +95,13 @@ bool renderImageToSegment(Segment &seg) { decoder->setFileReadBlockCallback(fileReadBlockCallback); decoder->setFileSizeCallback(fileSizeCallback); Serial.println("Starting decoding"); - if(decoder->startDecoding() < 0) { gifDecodeFailed = true; return false; } + if(decoder->startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } Serial.println("Decoding started"); } - if (gifDecodeFailed) return false; - if (!file || !decoder) { gifDecodeFailed = true; return false; } + if (gifDecodeFailed) return IMAGE_ERROR_PREV; + if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } + if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } // speed 0 = half speed, 128 = normal, 255 = full FX FPS // TODO: 0 = 4x slow, 64 = 2x slow, 128 = normal, 192 = 2x fast, 255 = 4x fast @@ -101,19 +112,21 @@ bool renderImageToSegment(Segment &seg) { fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; int result = decoder->decodeFrame(false); - if (result < 0) { gifDecodeFailed = true; return false; } + if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; } long lastFrameDelay = currentFrameDelay; currentFrameDelay = decoder->getFrameDelay_ms(); long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate currentFrameDelay -= tooSlowBy; - //currentFrameDelay -= LASTFRAMEDELAY; lastFrameDisplayTime = millis(); } return true; } -void endPlayback() { +void endImagePlayback() { if (file) file.close(); delete decoder; gifDecodeFailed = false; -} \ No newline at end of file + activeSeg = nullptr; +} + +#endif \ No newline at end of file diff --git a/wled00/wled.h b/wled00/wled.h index 152110b2a..74360e00a 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -78,6 +78,8 @@ #ifndef WLED_DISABLE_ESPNOW #include #endif + #undef WLED_DISABLE_GIF + #define WLED_DISABLE_GIF #else // ESP32 #include // ensure we have the correct "Serial" on new MCUs (depends on ARDUINO_USB_MODE and ARDUINO_USB_CDC_ON_BOOT) #include From 6be5360bdd44f13d73d235afc3e1944bd5d4cde8 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Tue, 1 Aug 2023 00:48:18 +0200 Subject: [PATCH 5/5] Remove temp workaround --- wled00/FX.cpp | 5 ----- wled00/image_loader.cpp | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index db7c4037e..58eb73ec8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4192,12 +4192,7 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! Draws a .gif image from filesystem on the matrix/strip */ uint16_t mode_image(void) { - SEGMENT.setUpLeds(); // temporary per-segment buffering renderImageToSegment(SEGMENT); - for (uint16_t i = 0; i < SEGLEN; i++) { - SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i)); // temporary, refresh all LEDs for lossy ABL - } - return FRAMETIME; } static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128"; diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 038a7d0b3..500fbc2dd 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -127,6 +127,7 @@ void endImagePlayback() { delete decoder; gifDecodeFailed = false; activeSeg = nullptr; + lastFilename[0] = '\0'; } #endif \ No newline at end of file