From 3ad56ea103d794082b3475fd22591a5a7787e63d Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 10 Mar 2024 14:34:15 +0100 Subject: [PATCH 1/9] GIF testing --- wled00/fcn_declare.h | 3 ++- wled00/image_loader.cpp | 58 +++++++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 59be676249..72918d1a26 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -5,7 +5,6 @@ * All globally accessible functions are declared here */ -#include "FX.h" //alexa.cpp #ifndef WLED_DISABLE_ALEXA @@ -127,6 +126,8 @@ void onHueConnect(void* arg, AsyncClient* client); void sendHuePoll(); void onHueData(void* arg, AsyncClient* client, void *data, size_t len); +#include "FX.h" // must be below colors.cpp declarations (potentially due to duplicate declarations of e.g. color_blend) + //image_loader.cpp #ifndef WLED_DISABLE_GIF bool fileSeekCallback(unsigned long position); diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 500fbc2dde..4a8052400d 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -5,7 +5,7 @@ File file; char lastFilename[34] = "/"; -GifDecoder<320,320,12,true>* decoder; +GifDecoder<32,32,12> decoder; bool gifDecodeFailed = false; long lastFrameDisplayTime = 0, currentFrameDelay = 0; @@ -38,7 +38,7 @@ bool openGif(const char *filename) { Segment* activeSeg; uint16_t gifWidth, gifHeight; -uint16_t fillPixX, fillPixY; +//uint16_t fillPixX, fillPixY; void screenClearCallback(void) { activeSeg->fill(0); @@ -66,6 +66,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t #define IMAGE_ERROR_DECODER_ALLOC 5 #define IMAGE_ERROR_GIF_DECODE 6 #define IMAGE_ERROR_FRAME_DECODE 7 +#define IMAGE_ERROR_WAITING 254 #define IMAGE_ERROR_PREV 255 // renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment @@ -84,47 +85,48 @@ byte renderImageToSegment(Segment &seg) { if (file) file.close(); openGif(lastFilename); 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); - decoder->setFileSeekCallback(fileSeekCallback); - decoder->setFilePositionCallback(filePositionCallback); - decoder->setFileReadCallback(fileReadCallback); - decoder->setFileReadBlockCallback(fileReadBlockCallback); - decoder->setFileSizeCallback(fileSizeCallback); + //if (!decoder) decoder = new GifDecoder<32,32,12>(); + //if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } + 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 IMAGE_ERROR_GIF_DECODE; } + if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } Serial.println("Decoding started"); } if (gifDecodeFailed) return IMAGE_ERROR_PREV; if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } - if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } + //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 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; - int result = decoder->decodeFrame(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; - lastFrameDisplayTime = millis(); - } - return true; + if((millis() - lastFrameDisplayTime) < wait) return IMAGE_ERROR_WAITING; + + decoder.getSize(&gifWidth, &gifHeight); + //fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; + //fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; + int result = decoder.decodeFrame(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; // TODO this is broken + lastFrameDisplayTime = millis(); + + return IMAGE_ERROR_NONE; } void endImagePlayback() { if (file) file.close(); - delete decoder; + //delete decoder; gifDecodeFailed = false; activeSeg = nullptr; lastFilename[0] = '\0'; From b2afac891415a65c5267755a351844903aba8e9d Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 10 Mar 2024 21:36:13 +0100 Subject: [PATCH 2/9] GIFs work again in principle --- platformio.ini | 2 +- wled00/FX.cpp | 11 +++++++---- wled00/FX_fcn.cpp | 1 + wled00/fcn_declare.h | 1 + wled00/image_loader.cpp | 13 +++++++++---- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/platformio.ini b/platformio.ini index 66bd09957a..73d82c3a7b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -221,7 +221,7 @@ 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 + https://github.com/Aircoookie/GifDecoder#e76f58f ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 681f3d1070..e8c59babee 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4470,7 +4470,12 @@ 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) { - renderImageToSegment(SEGMENT); + //Serial.println(renderImageToSegment(SEGMENT)); + int status = renderImageToSegment(SEGMENT); + if (status != 0 && status != 254 && status != 255) { + Serial.print("GIF renderer return: "); + Serial.println(status); + } return FRAMETIME; } static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128"; @@ -7973,7 +7978,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE); addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL); - + addEffect(FX_MODE_IMAGE, &mode_image, _data_FX_MODE_IMAGE); addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE); addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE); addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE); @@ -8035,8 +8040,6 @@ 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_fcn.cpp b/wled00/FX_fcn.cpp index 42e98452fb..94fdb30060 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -199,6 +199,7 @@ void Segment::resetIfRequired() { if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; + endImagePlayback(this); } CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 72918d1a26..4619e640a8 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -136,6 +136,7 @@ int fileReadCallback(void); int fileReadBlockCallback(void * buffer, int numberOfBytes); int fileSizeCallback(void); byte renderImageToSegment(Segment &seg); +void endImagePlayback(Segment* seg); #endif //improv.cpp diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 4a8052400d..44fb7f7c8b 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -5,7 +5,7 @@ File file; char lastFilename[34] = "/"; -GifDecoder<32,32,12> decoder; +GifDecoder<32,32,12,true> decoder; bool gifDecodeFailed = false; long lastFrameDisplayTime = 0, currentFrameDelay = 0; @@ -85,7 +85,7 @@ byte renderImageToSegment(Segment &seg) { if (file) file.close(); openGif(lastFilename); if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } - //if (!decoder) decoder = new GifDecoder<32,32,12>(); + //if (!decoder) decoder = new GifDecoder<32,32,12,true>(); //if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } decoder.setScreenClearCallback(screenClearCallback); decoder.setUpdateScreenCallback(updateScreenCallback); @@ -95,6 +95,7 @@ byte renderImageToSegment(Segment &seg) { decoder.setFileReadCallback(fileReadCallback); decoder.setFileReadBlockCallback(fileReadBlockCallback); decoder.setFileSizeCallback(fileSizeCallback); + decoder.alloc(); // TODO only if not already allocated Serial.println("Starting decoding"); if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } Serial.println("Decoding started"); @@ -124,12 +125,16 @@ byte renderImageToSegment(Segment &seg) { return IMAGE_ERROR_NONE; } -void endImagePlayback() { +void endImagePlayback(Segment *seg) { + Serial.println("Image playback end called"); + if (!activeSeg || activeSeg != seg) return; if (file) file.close(); //delete decoder; + decoder.dealloc(); gifDecodeFailed = false; activeSeg = nullptr; - lastFilename[0] = '\0'; + lastFilename[1] = '\0'; + Serial.println("Image playback ended"); } #endif \ No newline at end of file From 3e60d3d96e9ebbea6232fca0b0e10fe714f9b008 Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 17 Mar 2024 22:24:55 +0100 Subject: [PATCH 3/9] Working GIF support --- wled00/FX.cpp | 11 +++++------ wled00/image_loader.cpp | 28 +++++++++++++++------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e8c59babee..48866224ee 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4470,12 +4470,11 @@ 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) { - //Serial.println(renderImageToSegment(SEGMENT)); - int status = renderImageToSegment(SEGMENT); - if (status != 0 && status != 254 && status != 255) { - Serial.print("GIF renderer return: "); - Serial.println(status); - } + renderImageToSegment(SEGMENT); + // if (status != 0 && status != 254 && status != 255) { + // Serial.print("GIF renderer return: "); + // Serial.println(status); + // } 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 44fb7f7c8b..9ad4662e60 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -3,11 +3,15 @@ #include "GifDecoder.h" #include "wled.h" +/* + * Functions to render images from filesystem to segments, used by the "Image" effect + */ + File file; char lastFilename[34] = "/"; -GifDecoder<32,32,12,true> decoder; +GifDecoder<320,320,12,true> decoder; bool gifDecodeFailed = false; -long lastFrameDisplayTime = 0, currentFrameDelay = 0; +unsigned long lastFrameDisplayTime = 0, currentFrameDelay = 0; bool fileSeekCallback(unsigned long position) { return file.seek(position); @@ -38,7 +42,6 @@ bool openGif(const char *filename) { Segment* activeSeg; uint16_t gifWidth, gifHeight; -//uint16_t fillPixX, fillPixY; void screenClearCallback(void) { activeSeg->fill(0); @@ -72,6 +75,8 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t // renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment byte renderImageToSegment(Segment &seg) { if (!seg.name) return IMAGE_ERROR_NO_NAME; + // disable during effect transition, causes flickering, multiple allocations and depending on image, part of old FX remaining + if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING; if (activeSeg && activeSeg != &seg) return IMAGE_ERROR_SEG_LIMIT; // only one segment at a time activeSeg = &seg; @@ -85,8 +90,6 @@ byte renderImageToSegment(Segment &seg) { if (file) file.close(); openGif(lastFilename); if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } - //if (!decoder) decoder = new GifDecoder<32,32,12,true>(); - //if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } decoder.setScreenClearCallback(screenClearCallback); decoder.setUpdateScreenCallback(updateScreenCallback); decoder.setDrawPixelCallback(drawPixelCallback); @@ -95,7 +98,7 @@ byte renderImageToSegment(Segment &seg) { decoder.setFileReadCallback(fileReadCallback); decoder.setFileReadBlockCallback(fileReadBlockCallback); decoder.setFileSizeCallback(fileSizeCallback); - decoder.alloc(); // TODO only if not already allocated + decoder.alloc(); Serial.println("Starting decoding"); if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } Serial.println("Decoding started"); @@ -109,17 +112,17 @@ byte renderImageToSegment(Segment &seg) { // 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) return IMAGE_ERROR_WAITING; + // TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions + if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING; decoder.getSize(&gifWidth, &gifHeight); - //fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; - //fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; + int result = decoder.decodeFrame(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; // TODO this is broken + unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate + currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy; lastFrameDisplayTime = millis(); return IMAGE_ERROR_NONE; @@ -129,7 +132,6 @@ void endImagePlayback(Segment *seg) { Serial.println("Image playback end called"); if (!activeSeg || activeSeg != seg) return; if (file) file.close(); - //delete decoder; decoder.dealloc(); gifDecodeFailed = false; activeSeg = nullptr; From 247de600afd3d06918c7c9873e63a756e970ad2b Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 17 Mar 2024 22:57:15 +0100 Subject: [PATCH 4/9] Fix missing GIF enable macros --- platformio.ini | 2 +- wled00/FX.cpp | 6 +++++- wled00/FX_fcn.cpp | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 73d82c3a7b..c08c632a4c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -221,7 +221,7 @@ lib_deps = https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 bitbank2/AnimatedGIF@^1.4.7 - https://github.com/Aircoookie/GifDecoder#e76f58f + https://github.com/Aircoookie/GifDecoder#bc3af18 ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 48866224ee..5543b54baa 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4470,12 +4470,16 @@ 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) { + #ifdef WLED_DISABLE_GIF + return mode_static(); + #else renderImageToSegment(SEGMENT); + return FRAMETIME; + #endif // if (status != 0 && status != 254 && status != 255) { // Serial.print("GIF renderer return: "); // Serial.println(status); // } - return FRAMETIME; } static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128"; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 94fdb30060..7a2c005742 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -199,7 +199,9 @@ void Segment::resetIfRequired() { if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; + #ifndef WLED_DISABLE_GIF endImagePlayback(this); + #endif } CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { From d4ba603cf745de2f88a39e7cf13a8967073ab64b Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Tue, 19 Mar 2024 22:39:33 +0100 Subject: [PATCH 5/9] Update pio version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 666122aa28..ee70cd689f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile From 7c03f716a8eb2b167ae337e3263ed088827c7661 Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Tue, 19 Mar 2024 22:53:43 +0100 Subject: [PATCH 6/9] Include gif library for all esp32 variants --- platformio.ini | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/platformio.ini b/platformio.ini index 2c7d1442af..00df6666c9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -238,6 +238,11 @@ lib_deps_compat = https://github.com/blazoncek/QuickESPNow.git#optional-debug https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 +[esp32_all_variants] +lib_deps = + https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + bitbank2/AnimatedGIF@^1.4.7 + https://github.com/Aircoookie/GifDecoder#bc3af18 [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip @@ -259,9 +264,7 @@ large_partitions = tools/WLED_ESP32_8MB.csv extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv lib_deps = https://github.com/lorol/LITTLEFS.git - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 - bitbank2/AnimatedGIF@^1.4.7 - https://github.com/Aircoookie/GifDecoder#bc3af18 + ${esp32_all_variants.lib_deps} ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE @@ -284,7 +287,7 @@ build_flags = -g -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs @@ -304,7 +307,7 @@ build_flags = -g ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs @@ -323,7 +326,7 @@ build_flags = -g ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs @@ -343,7 +346,7 @@ build_flags = -g ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${esp32_all_variants.lib_deps} ${env.lib_deps} board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs From e852df3179641ef9fe411608d69090aa1c2a5f4a Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sat, 13 Apr 2024 20:06:33 +0200 Subject: [PATCH 7/9] Proper debug statements --- wled00/image_loader.cpp | 144 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 wled00/image_loader.cpp diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp new file mode 100644 index 0000000000..31a84e6601 --- /dev/null +++ b/wled00/image_loader.cpp @@ -0,0 +1,144 @@ +#include "wled.h" + +#ifndef WLED_DISABLE_GIF + +#include "GifDecoder.h" + + +/* + * Functions to render images from filesystem to segments, used by the "Image" effect + */ + +File file; +char lastFilename[34] = "/"; +GifDecoder<320,320,12,true> decoder; +bool gifDecodeFailed = false; +unsigned 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; + +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, gamma8(red), gamma8(green), gamma8(blue)); + } + } +} + +#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_WAITING 254 +#define IMAGE_ERROR_PREV 255 + +// renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment +byte renderImageToSegment(Segment &seg) { + if (!seg.name) return IMAGE_ERROR_NO_NAME; + // disable during effect transition, causes flickering, multiple allocations and depending on image, part of old FX remaining + if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING; + 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) { // segment name changed, load new image + strncpy(lastFilename +1, seg.name, 32); + gifDecodeFailed = false; + if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) { + gifDecodeFailed = true; + return IMAGE_ERROR_UNSUPPORTED_FORMAT; + } + if (file) file.close(); + openGif(lastFilename); + if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } + decoder.setScreenClearCallback(screenClearCallback); + decoder.setUpdateScreenCallback(updateScreenCallback); + decoder.setDrawPixelCallback(drawPixelCallback); + decoder.setFileSeekCallback(fileSeekCallback); + decoder.setFilePositionCallback(filePositionCallback); + decoder.setFileReadCallback(fileReadCallback); + decoder.setFileReadBlockCallback(fileReadBlockCallback); + decoder.setFileSizeCallback(fileSizeCallback); + decoder.alloc(); + DEBUG_PRINTLN(F("Starting decoding")); + if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } + DEBUG_PRINTLN(F("Decoding started")); + } + + 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 + uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128; + + // TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions + if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING; + + decoder.getSize(&gifWidth, &gifHeight); + + int result = decoder.decodeFrame(false); + if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; } + + currentFrameDelay = decoder.getFrameDelay_ms(); + unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate + currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy; + lastFrameDisplayTime = millis(); + + return IMAGE_ERROR_NONE; +} + +void endImagePlayback(Segment *seg) { + DEBUG_PRINTLN(F("Image playback end called")); + if (!activeSeg || activeSeg != seg) return; + if (file) file.close(); + decoder.dealloc(); + gifDecodeFailed = false; + activeSeg = nullptr; + lastFilename[1] = '\0'; + DEBUG_PRINTLN(F("Image playback ended")); +} + +#endif \ No newline at end of file From 56b8af86d70b1de414794e70c4875028fdc71967 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 18:40:41 +0000 Subject: [PATCH 8/9] Swap to WLED_ENABLE_GIF --- wled00/FX.cpp | 4 +++- wled00/FX_fcn.cpp | 2 +- wled00/fcn_declare.h | 2 +- wled00/image_loader.cpp | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 081009f4e9..22c33356b0 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4484,7 +4484,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) { - #ifdef WLED_DISABLE_GIF + #ifndef WLED_ENABLE_GIF return mode_static(); #else renderImageToSegment(SEGMENT); @@ -7755,7 +7755,9 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE); addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL); + #ifdef WLED_ENABLE_GIF addEffect(FX_MODE_IMAGE, &mode_image, _data_FX_MODE_IMAGE); + #endif addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE); addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE); addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index af277718d7..97bb99c9a2 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -195,7 +195,7 @@ void Segment::resetIfRequired() { if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; - #ifndef WLED_DISABLE_GIF + #ifdef WLED_ENABLE_GIF endImagePlayback(this); #endif } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index bd45a2f305..cab5941e81 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -217,7 +217,7 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len); #include "FX.h" // must be below colors.cpp declarations (potentially due to duplicate declarations of e.g. color_blend) //image_loader.cpp -#ifndef WLED_DISABLE_GIF +#ifdef WLED_ENABLE_GIF bool fileSeekCallback(unsigned long position); unsigned long filePositionCallback(void); int fileReadCallback(void); diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 31a84e6601..9665057942 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -1,6 +1,6 @@ #include "wled.h" -#ifndef WLED_DISABLE_GIF +#ifdef WLED_ENABLE_GIF #include "GifDecoder.h" From 53c1856038a273906fe5d3b253600f3cf2596250 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 14 Jan 2025 18:47:06 +0000 Subject: [PATCH 9/9] Enable GIF support for all esp32 builds --- platformio.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/platformio.ini b/platformio.ini index 00df6666c9..b11343b167 100644 --- a/platformio.ini +++ b/platformio.ini @@ -243,6 +243,8 @@ lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 bitbank2/AnimatedGIF@^1.4.7 https://github.com/Aircoookie/GifDecoder#bc3af18 +build_flags = + -D WLED_ENABLE_GIF [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip @@ -256,6 +258,8 @@ build_flags = -g #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x -D LOROL_LITTLEFS ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + ${esp32_all_variants.build_flags} + tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv @@ -286,6 +290,7 @@ build_flags = -g -DARDUINO_ARCH_ESP32 -DESP32 -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 + ${esp32_all_variants.build_flags} lib_deps = ${esp32_all_variants.lib_deps} ${env.lib_deps} @@ -306,6 +311,7 @@ build_flags = -g -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT + ${esp32_all_variants.build_flags} lib_deps = ${esp32_all_variants.lib_deps} ${env.lib_deps} @@ -325,6 +331,7 @@ build_flags = -g -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT + ${esp32_all_variants.build_flags} lib_deps = ${esp32_all_variants.lib_deps} ${env.lib_deps} @@ -345,6 +352,7 @@ build_flags = -g -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT + ${esp32_all_variants.build_flags} lib_deps = ${esp32_all_variants.lib_deps} ${env.lib_deps}