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