1 /*
2  Example application of FLIF decoder using libflif_dec
3  Copyright (C) 2015  Jon Sneyers
4 
5  License: Creative Commons CC0 1.0 Universal (Public Domain)
6  https://creativecommons.org/publicdomain/zero/1.0/legalcode
7 */
8 #if defined(__MINGW32__)
9 /* Use Gnu-style printf and scanf - see: https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/ */
10 #define __USE_MINGW_ANSI_STDIO 1
11 #endif
12 #include <flif_dec.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #if !defined(_MSC_VER)
16 /* Use inttypes.h format macros for greater portability */
17 #include <inttypes.h>
18 #endif
19 #include <SDL.h>
20 #include <time.h>
21 #include <stdbool.h>
22 
23 /**************************/
24 /* FIX COMPILER WARNINGS  */
25 /**************************/
26 
27 #ifdef FLIF_UNUSED
28 #elif defined(__GNUC__) || defined(__clang__)
29 # define FLIF_UNUSED(x) x __attribute__((unused))
30 #elif defined(__LCLINT__)
31 # define FLIF_UNUSED(x) /*@unused@*/ x
32 #else
33 # define FLIF_UNUSED(x) x
34 #endif
35 
36 // SDL2 for Visual Studio C++ 2015
37 
38 #if _MSC_VER >= 1900
39 FILE _iob[] = {*stdin, *stdout, *stderr};
__iob_func(void)40 extern "C" FILE * __cdecl __iob_func(void)
41 {
42     return _iob;
43 }
HackToReferencePrintEtc()44 void HackToReferencePrintEtc()
45 {
46     fprintf(stderr, "");
47 }
48 #endif
49 
50 // comment out to not decode progressively
51 #define PROGRESSIVE_DECODING
52 
53 #pragma pack(push,1)
54 typedef struct RGBA { uint8_t r,g,b,a; } RGBA;
55 #pragma pack(pop)
56 
57 FLIF_DECODER* volatile d = NULL;
58 SDL_Window* window = NULL;
59 SDL_DisplayMode dm;
60 SDL_DisplayMode ddm;
61 SDL_Renderer* renderer = NULL;
62 SDL_Texture** image_frame = NULL;
63 SDL_Surface* decsurf = NULL;
64 SDL_Surface* bgsurf = NULL;
65 SDL_Surface* tmpsurf = NULL;
66 volatile int quit = 0;
67 int frame = 0;
68 volatile int nb_frames = 0;
69 int* frame_delay = NULL;
70 
71 SDL_mutex *volatile mutex;
72 
73 Uint32 RESIZE_TO_IMAGE_EVENTTYPE = (Uint32)-1;
74 int image_size_w = 0;
75 int image_size_h = 0;
76 
77 int window_size_set = 0;
78 int framecount = 0;
79 
80 // Renders the image or current animation frame (assuming it is available as a SDL_Texture)
draw_image()81 void draw_image() {
82     if (!window) return;
83     if (SDL_LockMutex(mutex) == 0) {
84       if (!renderer) { printf("Error: Could not get renderer\n"); return; }
85       SDL_Rect ir = {}; // image rectangle (source texture)
86       SDL_Rect wr = {}; // window rectangle
87       SDL_Rect tr = {}; // target rectangle
88       if (SDL_QueryTexture(image_frame[frame], NULL, NULL, &ir.w, &ir.h)) { printf("Error: Could not query texture\n"); return; };
89       if (!ir.w || !ir.h) { printf("Error: Empty texture ?\n"); return; };
90       framecount++;
91       SDL_GetWindowSize(window, &wr.w, &wr.h);
92       tr = wr;
93       // scale to fit window, but respect aspect ratio
94       if (wr.w > ir.w * wr.h / ir.h) tr.w = wr.h * ir.w / ir.h;
95       else if (wr.w < ir.w * wr.h / ir.h) tr.h = wr.w * ir.h / ir.w;
96       tr.x = (wr.w - tr.w)/2;
97       tr.y = (wr.h - tr.h)/2;
98 
99       // alternative below: only scale down, don't scale up
100       /*
101       // window smaller than image: scale down, but respect aspect ratio
102       if (tr.w < ir.w) { tr.h=wr.w*ir.h/ir.w; tr.y=(wr.h-ir.h)/2;}
103       if (tr.h < ir.h) { tr.w=wr.h*ir.w/ir.h; tr.x=(wr.w-ir.w)/2;}
104 
105       // window larger than image: center the image (don't scale up)
106       if (tr.w > ir.w) { tr.x=(wr.w-ir.w)/2; tr.w=ir.w; }
107       if (tr.h > ir.h) { tr.y=(wr.h-ir.h)/2; tr.h=ir.h; }
108       */
109 
110       //printf("Rendering %ix%i frame on %ix%i (+%i,%i) target rectangle\n", ir.w, ir.h, tr.w, tr.h, tr.x, tr.y);
111 
112       // if target has an offset, make sure there is a background
113       if (tr.x || tr.y) SDL_RenderClear(renderer);
114       // blit the frame into the target area
115       SDL_RenderCopy(renderer, image_frame[frame], NULL, &tr);
116       // flip the framebuffer
117       SDL_RenderPresent(renderer);
118       SDL_UnlockMutex(mutex);
119     } else {
120       fprintf(stderr, "Couldn't lock mutex\n");
121     }
122 }
123 
do_event(SDL_Event e)124 int do_event(SDL_Event e) {
125     if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_CLOSE) {printf("Closed\n"); quit=1; return 0;}
126     if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_q) {printf("Quit\n"); quit=1; return 0;}
127     // refresh the window if its size changes
128     if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED && renderer) { draw_image(); }
129     if (RESIZE_TO_IMAGE_EVENTTYPE != (Uint32)-1 && e.type == RESIZE_TO_IMAGE_EVENTTYPE){
130         if (e.user.code == 0){
131             SDL_SetWindowSize(window,image_size_w,image_size_h);
132             SDL_SetWindowPosition(window,SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED);
133         }
134         else
135             SDL_MaximizeWindow(window);
136     }
137     return 1;
138 }
139 
140 
141 // returns true on success
updateTextures(uint32_t quality,int64_t bytes_read)142 bool updateTextures(uint32_t quality, int64_t bytes_read) {
143 /* Old versions Microsoft C did not use the standard print specifiers. See:
144    https://msdn.microsoft.com/en-us/library/tcxf1dw6(v=vs.90).aspx
145 
146    In fact, mingw-w64 had to provide their own implementation just to work
147    the issue with the incompatibility with old versions of msvcrt.dll.  See:
148 
149    https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/
150 */
151     #if defined(_MSC_VER)
152     printf("%lli bytes read, rendering at quality=%.2f%%\n",(long long int) bytes_read, 0.01*quality);
153     #else
154     printf("%" PRId64 " bytes read, rendering at quality=%.2f%%\n", bytes_read, 0.01*quality);
155     #endif
156 
157     FLIF_IMAGE* image = flif_decoder_get_image(d, 0);
158     if (!image) { printf("Error: No decoded image found\n"); return false; }
159     uint32_t w = flif_image_get_width(image);
160     uint32_t h = flif_image_get_height(image);
161 
162     // set the window title and size
163     if (!window) { printf("Error: Could not create window\n"); return false; }
164     char title[100];
165     #if defined(_MSC_VER)
166     sprintf(title,"FLIF image decoded at %ux%u [read %lli bytes, quality=%.2f%%]",w,h,(long long int) bytes_read, 0.01*quality);
167     #else
168     sprintf(title,"FLIF image decoded at %ux%u [read %" PRId64 " bytes, quality=%.2f%%]",w,h,(long long int) bytes_read, 0.01*quality);
169     #endif
170     SDL_SetWindowTitle(window,title);
171     if (!window_size_set && RESIZE_TO_IMAGE_EVENTTYPE != (Uint32)-1) {
172       uint32_t image_size_w = (w > (uint32_t)dm.w ? (uint32_t)dm.w : w);
173       uint32_t image_size_h = (h > (uint32_t)dm.h ? (uint32_t)dm.h : h);
174       if (image_size_w > w * image_size_h / h) image_size_w = image_size_h * w / h;
175       else if (image_size_w < w * image_size_h / h) image_size_h = image_size_w * h / w;
176       if (w > (uint32_t)dm.w*8/10 && h > (uint32_t)dm.h*8/10) { image_size_w = image_size_w*8/10; image_size_h = image_size_h*8/10; }
177 
178       // On Windows, a window cannot be resized from a non-GUI thread.
179       // Therefore delegate the resize to the event loop.
180       SDL_Event resize_to_image_event;
181       resize_to_image_event.type = RESIZE_TO_IMAGE_EVENTTYPE;
182       resize_to_image_event.user.code = (w > (uint32_t)dm.w*8/10 && h > (uint32_t)dm.h*8/10); // do_maximize flag
183 
184       SDL_PushEvent(&resize_to_image_event);
185       window_size_set = 1;
186     }
187 
188     // allocate enough room for the texture pointers and frame delays
189     if (!image_frame) image_frame = (SDL_Texture**) calloc(flif_decoder_num_images(d), sizeof(FLIF_IMAGE*));
190     if (!frame_delay) frame_delay = (int*) calloc(flif_decoder_num_images(d), sizeof(int));
191 
192     // produce one SDL_Texture per frame
193     for (int f = 0; (uint32_t)f < flif_decoder_num_images(d); f++) {
194         if (quit) {
195           return 0;
196         }
197         FLIF_IMAGE* image = flif_decoder_get_image(d, f);
198         if (!image) { printf("Error: No decoded image found\n"); return false; }
199         frame_delay[f] = flif_image_get_frame_delay(image);
200         // Copy the decoded pixels to a temporary surface
201         if (!tmpsurf) tmpsurf = SDL_CreateRGBSurface(0,w,h,32,0x000000FF,0x0000FF00,0x00FF0000,0xFF000000);
202         if (!tmpsurf) { printf("Error: Could not create surface\n"); return false; }
203         char* pp =(char*) tmpsurf->pixels;
204         for (uint32_t r=0; r<h; r++) {
205             flif_image_read_row_RGBA8(image, r, pp, w * sizeof(RGBA));
206             pp += tmpsurf->pitch;
207         }
208         // Draw checkerboard background for image/animation with alpha channel
209         if (flif_image_get_nb_channels(image) > 3) {
210           if (!bgsurf) bgsurf = SDL_CreateRGBSurface(0,w,h,32,0x000000FF,0x0000FF00,0x00FF0000,0xFF000000);
211           if (!bgsurf) { printf("Error: Could not create surface\n"); return false; }
212           SDL_Rect sq; sq.w=20; sq.h=20;
213           for (sq.y=0; (uint32_t)sq.y<h; sq.y+=sq.h) for (sq.x=0; (uint32_t)sq.x<w; sq.x+=sq.w)
214               SDL_FillRect(bgsurf,&sq,(((sq.y/sq.h + sq.x/sq.w)&1) ? 0xFF606060 : 0xFFA0A0A0));
215           // Alpha-blend decoded frame on top of checkerboard background
216           SDL_BlitSurface(tmpsurf,NULL,bgsurf,NULL);
217           SDL_FreeSurface(tmpsurf); tmpsurf = bgsurf; bgsurf = NULL;
218         }
219         if (!renderer) { printf("Error: Could not get renderer\n"); return false; }
220         if (image_frame[f]) SDL_DestroyTexture(image_frame[f]);
221         // Convert the surface to a texture (for accelerated blitting)
222         image_frame[f] = SDL_CreateTextureFromSurface(renderer, tmpsurf);
223         if (!image_frame[f]) { printf("Could not create texture!\n"); quit=1; return 1; }
224         SDL_SetTextureBlendMode(image_frame[f],SDL_BLENDMODE_NONE);
225     }
226     SDL_FreeSurface(tmpsurf); tmpsurf=NULL;
227 
228     return true;
229 }
230 
231 #ifdef PROGRESSIVE_DECODING
232 const double preview_interval= .6;
233 
234 clock_t last_preview_time = 0;
235 
236 // Callback function: converts (partially) decoded image/animation to a/several SDL_Texture(s),
237 //                    resizes the viewer window if needed, and calls draw_image()
238 // Input arguments are: quality (0..10000), current position in the .flif file
239 // Output is the desired minimal quality before doing the next callback
progressive_render(uint32_t quality,int64_t bytes_read,uint8_t decode_over,FLIF_UNUSED (void * user_data),void * context)240 uint32_t progressive_render(uint32_t quality, int64_t bytes_read, uint8_t decode_over, FLIF_UNUSED(void *user_data), void *context) {
241     if (SDL_LockMutex(mutex) == 0) {
242       clock_t now = clock();
243       double timeElapsed = ((double)(now - last_preview_time)) / CLOCKS_PER_SEC;
244       if (quality != 10000 && (!decode_over) && timeElapsed< preview_interval) {
245         SDL_UnlockMutex(mutex);
246         return quality + 1000;
247       }
248 
249       // For benchmarking
250       // clock_t finalTime = clock();
251       // if (quality == 10000) printf("Total time: %.2lf\n", ((double)finalTime ) / CLOCKS_PER_SEC);
252 
253       flif_decoder_generate_preview(context);
254 
255       bool success = updateTextures(quality, bytes_read);
256 
257       last_preview_time = clock();
258 
259       SDL_UnlockMutex(mutex);
260 
261       if (!success || quit) {
262         return 0; // stop decoding
263       } else {
264         // setting nb_frames to a value > 1 will make sure the main thread keeps calling draw_image()
265         nb_frames = flif_decoder_num_images(d);
266         draw_image();
267       }
268 
269       return quality + 1000; // call me back when you have at least 10.00% better quality
270     } else {
271       fprintf(stderr, "Couldn't lock mutex\n");
272       return 0;
273     }
274 
275 }
276 #endif
277 
278 // When decoding progressively, this is a separate thread (so a partially loaded animation keeps playing while decoding more detail)
decodeThread(void * arg)279 static int decodeThread(void * arg) {
280     char ** argv = (char **)arg;
281     d = flif_create_decoder();
282     if (!d) return 1;
283     // set the quality to 100% (a lower value will decode a lower-quality preview)
284     flif_decoder_set_quality(d, 100);             // this is the default, so can be omitted
285     // set the scale-down factor to 1 (a higher value will decode a downsampled preview)
286     flif_decoder_set_scale(d, 1);                 // this is the default, so can be omitted
287     // set the maximum size to twice the screen resolution; if an image is larger, a downsampled preview will be decoded
288     flif_decoder_set_resize(d, ddm.w*2, ddm.h*2);   // the default is to not have a maximum size
289 
290     // alternatively, set the decode width to exactly the screen width (the height will be set to respect aspect ratio)
291     // flif_decoder_set_fit(d, dm.w, 0);   // the default is to not have a maximum size
292 #ifdef PROGRESSIVE_DECODING
293     // set the callback function to render the partial (and final) decoded images
294     flif_decoder_set_callback(d, &(progressive_render), NULL);  // the default is "no callback"; decode completely until quality/scale/size target is reached
295     // do the first callback when at least 5.00% quality has been decoded
296     flif_decoder_set_first_callback_quality(d, 500);      // the default is to callback almost immediately
297 #endif
298     if (!flif_decoder_decode_file(d, argv[1])) {
299         printf("Error: decoding failed\n");
300         flif_destroy_decoder(d);
301         d = NULL;
302         quit = 1;
303         return 1;
304     }
305 #ifndef PROGRESSIVE_DECODING
306     // no callback was set, so we manually call our callback function to render the final image/frames
307     updateTextures(10000,-1);
308 #endif
309     flif_destroy_decoder(d);
310     d = NULL;
311     return 0;
312 }
313 
abort_decode()314 bool abort_decode() {
315   if (SDL_LockMutex(mutex) == 0) {
316     int retValue = flif_abort_decoder(d);
317     SDL_UnlockMutex(mutex);
318     return retValue;
319   } else {
320     return false;
321   }
322 }
323 
main(int argc,char ** argv)324 int main(int argc, char **argv) {
325     if (argc < 2 || argc > 2) {
326         printf("Usage:  %s  image.flif\n",argv[0]);
327         return 0;
328     }
329 
330     mutex = SDL_CreateMutex();
331     if (!mutex) {
332       fprintf(stderr, "Couldn't create mutex\n");
333       return 1;
334     }
335 
336     RESIZE_TO_IMAGE_EVENTTYPE = SDL_RegisterEvents(1);
337 
338 #ifdef PROGRESSIVE_DECODING
339     last_preview_time = (-2*preview_interval* CLOCKS_PER_SEC);
340 #endif
341 
342     SDL_Init(SDL_INIT_VIDEO);
343     SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
344 
345     SDL_EventState(SDL_MOUSEMOTION,SDL_IGNORE);
346     window = SDL_CreateWindow("FLIF Viewer -- Loading...", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 200, 200, SDL_WINDOW_RESIZABLE);
347 
348     renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
349     SDL_SetRenderDrawColor(renderer, 127, 127, 127, 255); // background color (in case aspect ratio of window doesn't match image)
350     SDL_RenderClear(renderer);
351     SDL_RenderPresent(renderer);
352 
353     int displayIndex = SDL_GetWindowDisplayIndex(window);
354     if (SDL_GetDesktopDisplayMode(displayIndex,&ddm)) { printf("Error: SDL_GetWindowDisplayMode\n"); return 1; }
355     if (SDL_GetWindowDisplayMode(window,&dm)) { printf("Error: SDL_GetWindowDisplayMode\n"); return 1; }
356     int result = 0;
357 #ifdef PROGRESSIVE_DECODING
358     printf("Decoding progressively...\n");
359     SDL_Thread *decode_thread = SDL_CreateThread(decodeThread,"Decode_FLIF",argv);
360     if (NULL == decode_thread) {
361         printf("Error: failed to create decode thread\n");
362         return 1;
363     }
364 #else
365     printf("Decoding entire image...\n");
366     result = decodeThread(argv);
367 #endif
368     SDL_Event e;
369     unsigned int current_time;
370     unsigned int begin=SDL_GetTicks();
371     while (!quit) {
372         if (nb_frames > 1) {
373             current_time = SDL_GetTicks();
374             draw_image();
375             int time_passed = SDL_GetTicks()-current_time;
376             int time_to_wait = frame_delay[frame] - time_passed;
377             if (time_to_wait>0) SDL_Delay(time_to_wait);  // todo: if the animation has extremely long frame delays, this makes the viewer unresponsive
378             frame++;
379             frame %= nb_frames;
380         } else {
381             SDL_Delay(200); // if it's not an animation, check event queue 5 times per second
382         }
383         while (SDL_PollEvent(&e)) do_event(e);
384     }
385 
386     if (nb_frames > 1) printf("Rendered %i frames in %.2f seconds, %.4f frames per second\n", framecount, 0.001*(SDL_GetTicks()-begin), 1000.0*framecount/(SDL_GetTicks()-begin));
387 
388 #ifdef PROGRESSIVE_DECODING
389     // make sure the decoding gets properly aborted (in case it was not done yet)
390     while(d != NULL && abort_decode()) SDL_Delay(100);
391     SDL_WaitThread(decode_thread, &result);
392 #endif
393     SDL_DestroyWindow(window);
394     SDL_Quit();
395     return result;
396 }
397