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