Skip to content

Commit 248e1a4

Browse files
committed
Merge branch 'gif-MM' into gifplayer-html-MM
2 parents 88dd808 + c5a28c2 commit 248e1a4

6 files changed

Lines changed: 185 additions & 2 deletions

File tree

platformio.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ lib_deps =
261261
IRremoteESP8266 @ 2.8.2
262262
;;makuna/NeoPixelBus @ 2.7.5 ;; WLEDMM will be added in board specific sections
263263
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
264+
bitbank2/AnimatedGIF@^1.4.7
265+
https://github.com/Aircoookie/GifDecoder#bc3af18
264266
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
265267
#TFT_eSPI
266268
#For compatible OLED display uncomment following
@@ -1108,6 +1110,7 @@ build_flags_S =
11081110
; -D USERMOD_ARTIFX ;; WLEDMM usermod - temporarily moved into "_M", due to problems in "_S" when compiling with -O2
11091111
-D WLEDMM_FASTPATH ;; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions.
11101112
; -D WLED_DEBUG_HEAP ;; WLEDMM enable heap debugging
1113+
-D WLED_ENABLE_GIF
11111114

11121115
lib_deps_S =
11131116
;; https://github.com/kosme/arduinoFFT#develop @ 1.9.2+sha.419d7b0 ;; used for USERMOD_AUDIOREACTIVE - using "known working" hash

wled00/FX.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4650,6 +4650,24 @@ uint16_t mode_washing_machine(void) {
46504650
static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,!;;!";
46514651

46524652

4653+
/*
4654+
Image effect
4655+
Draws a .gif image from filesystem on the matrix/strip
4656+
*/
4657+
uint16_t mode_image(void) {
4658+
#ifndef WLED_ENABLE_GIF
4659+
return mode_static();
4660+
#else
4661+
renderImageToSegment(SEGMENT);
4662+
return FRAMETIME;
4663+
#endif
4664+
// if (status != 0 && status != 254 && status != 255) {
4665+
// Serial.print("GIF renderer return: ");
4666+
// Serial.println(status);
4667+
// }
4668+
}
4669+
static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128";
4670+
46534671
/*
46544672
Blends random colors across palette
46554673
Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e
@@ -9071,7 +9089,9 @@ void WS2812FX::setupEffectData() {
90719089
addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS);
90729090
addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE);
90739091
addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL);
9074-
9092+
#ifdef WLED_ENABLE_GIF
9093+
addEffect(FX_MODE_IMAGE, &mode_image, _data_FX_MODE_IMAGE);
9094+
#endif
90759095
addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE);
90769096
addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE);
90779097
addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE);

wled00/FX.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented
186186
#define FX_MODE_TWO_DOTS 50
187187
#define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity)
188188
#define FX_MODE_RUNNING_DUAL 52
189-
// #define FX_MODE_HALLOWEEN 53 // removed in 0.14!
189+
#define FX_MODE_IMAGE 53
190190
#define FX_MODE_TRICOLOR_CHASE 54
191191
#define FX_MODE_TRICOLOR_WIPE 55
192192
#define FX_MODE_TRICOLOR_FADE 56

wled00/FX_fcn.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ void Segment::resetIfRequired() {
278278
reset = false; // setOption(SEG_OPTION_RESET, false);
279279
startFrame(); // WLEDMM update cached propoerties
280280
if (isActive() && !freeze) { fill(BLACK); needsBlank = false; } // WLEDMM start clean
281+
#ifdef WLED_ENABLE_GIF
282+
endImagePlayback(this);
283+
#endif
281284
DEBUG_PRINTLN("Segment reset");
282285
} else if (needsBlank) {
283286
startFrame(); // WLEDMM update cached propoerties

wled00/fcn_declare.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,19 @@ void onHueConnect(void* arg, AsyncClient* client);
109109
void sendHuePoll();
110110
void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
111111

112+
#include "FX.h" // must be below colors.cpp declarations (potentially due to duplicate declarations of e.g. color_blend)
113+
114+
//image_loader.cpp
115+
#ifdef WLED_ENABLE_GIF
116+
bool fileSeekCallback(unsigned long position);
117+
unsigned long filePositionCallback(void);
118+
int fileReadCallback(void);
119+
int fileReadBlockCallback(void * buffer, int numberOfBytes);
120+
int fileSizeCallback(void);
121+
byte renderImageToSegment(Segment &seg);
122+
void endImagePlayback(Segment* seg);
123+
#endif
124+
112125
//improv.cpp
113126
enum ImprovRPCType {
114127
Command_Wifi = 0x01,

wled00/image_loader.cpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#include "wled.h"
2+
3+
#ifdef WLED_ENABLE_GIF
4+
5+
#include "GifDecoder.h"
6+
7+
8+
/*
9+
* Functions to render images from filesystem to segments, used by the "Image" effect
10+
*/
11+
12+
File file;
13+
char lastFilename[34] = "/";
14+
GifDecoder<320,320,12,true> decoder;
15+
bool gifDecodeFailed = false;
16+
unsigned long lastFrameDisplayTime = 0, currentFrameDelay = 0;
17+
18+
bool fileSeekCallback(unsigned long position) {
19+
return file.seek(position);
20+
}
21+
22+
unsigned long filePositionCallback(void) {
23+
return file.position();
24+
}
25+
26+
int fileReadCallback(void) {
27+
return file.read();
28+
}
29+
30+
int fileReadBlockCallback(void * buffer, int numberOfBytes) {
31+
return file.read((uint8_t*)buffer, numberOfBytes);
32+
}
33+
34+
int fileSizeCallback(void) {
35+
return file.size();
36+
}
37+
38+
bool openGif(const char *filename) {
39+
file = WLED_FS.open(filename, "r");
40+
41+
if (!file) return false;
42+
return true;
43+
}
44+
45+
Segment* activeSeg;
46+
uint16_t gifWidth, gifHeight;
47+
48+
void screenClearCallback(void) {
49+
activeSeg->fill(0);
50+
}
51+
52+
void updateScreenCallback(void) {}
53+
54+
void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
55+
// simple nearest-neighbor scaling
56+
int16_t outY = y * activeSeg->height() / gifHeight;
57+
int16_t outX = x * activeSeg->width() / gifWidth;
58+
// set multiple pixels if upscaling
59+
for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) {
60+
for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) {
61+
activeSeg->setPixelColorXY(outX + i, outY + j, gamma8(red), gamma8(green), gamma8(blue));
62+
}
63+
}
64+
}
65+
66+
#define IMAGE_ERROR_NONE 0
67+
#define IMAGE_ERROR_NO_NAME 1
68+
#define IMAGE_ERROR_SEG_LIMIT 2
69+
#define IMAGE_ERROR_UNSUPPORTED_FORMAT 3
70+
#define IMAGE_ERROR_FILE_MISSING 4
71+
#define IMAGE_ERROR_DECODER_ALLOC 5
72+
#define IMAGE_ERROR_GIF_DECODE 6
73+
#define IMAGE_ERROR_FRAME_DECODE 7
74+
#define IMAGE_ERROR_WAITING 254
75+
#define IMAGE_ERROR_PREV 255
76+
77+
// renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment
78+
byte renderImageToSegment(Segment &seg) {
79+
if (!seg.name) return IMAGE_ERROR_NO_NAME;
80+
// disable during effect transition, causes flickering, multiple allocations and depending on image, part of old FX remaining
81+
// TODO: if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING;
82+
if (activeSeg && activeSeg != &seg) return IMAGE_ERROR_SEG_LIMIT; // only one segment at a time
83+
activeSeg = &seg;
84+
85+
if (strncmp(lastFilename +1, seg.name, 32) != 0) { // segment name changed, load new image
86+
strncpy(lastFilename +1, seg.name, 32);
87+
gifDecodeFailed = false;
88+
if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) {
89+
gifDecodeFailed = true;
90+
return IMAGE_ERROR_UNSUPPORTED_FORMAT;
91+
}
92+
if (file) file.close();
93+
openGif(lastFilename);
94+
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
95+
decoder.setScreenClearCallback(screenClearCallback);
96+
decoder.setUpdateScreenCallback(updateScreenCallback);
97+
decoder.setDrawPixelCallback(drawPixelCallback);
98+
decoder.setFileSeekCallback(fileSeekCallback);
99+
decoder.setFilePositionCallback(filePositionCallback);
100+
decoder.setFileReadCallback(fileReadCallback);
101+
decoder.setFileReadBlockCallback(fileReadBlockCallback);
102+
decoder.setFileSizeCallback(fileSizeCallback);
103+
decoder.alloc();
104+
DEBUG_PRINTLN(F("Starting decoding"));
105+
if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; }
106+
DEBUG_PRINTLN(F("Decoding started"));
107+
}
108+
109+
if (gifDecodeFailed) return IMAGE_ERROR_PREV;
110+
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
111+
//if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; }
112+
113+
// speed 0 = half speed, 128 = normal, 255 = full FX FPS
114+
// TODO: 0 = 4x slow, 64 = 2x slow, 128 = normal, 192 = 2x fast, 255 = 4x fast
115+
uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128;
116+
117+
// TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions
118+
if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING;
119+
120+
decoder.getSize(&gifWidth, &gifHeight);
121+
122+
int result = decoder.decodeFrame(false);
123+
if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; }
124+
125+
currentFrameDelay = decoder.getFrameDelay_ms();
126+
unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate
127+
currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy;
128+
lastFrameDisplayTime = millis();
129+
130+
return IMAGE_ERROR_NONE;
131+
}
132+
133+
void endImagePlayback(Segment *seg) {
134+
DEBUG_PRINTLN(F("Image playback end called"));
135+
if (!activeSeg || activeSeg != seg) return;
136+
if (file) file.close();
137+
decoder.dealloc();
138+
gifDecodeFailed = false;
139+
activeSeg = nullptr;
140+
lastFilename[1] = '\0';
141+
DEBUG_PRINTLN(F("Image playback ended"));
142+
}
143+
144+
#endif

0 commit comments

Comments
 (0)