New comit of SDL2
[supertux.git] / src / SDL2 / external / libwebp-0.3.0 / examples / vwebp.c
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // This code is licensed under the same terms as WebM:
4 //  Software License Agreement:  http://www.webmproject.org/license/software/
5 //  Additional IP Rights Grant:  http://www.webmproject.org/license/additional/
6 // -----------------------------------------------------------------------------
7 //
8 //  Simple OpenGL-based WebP file viewer.
9 //
10 // Author: Skal (pascal.massimino@gmail.com)
11 #ifdef HAVE_CONFIG_H
12 #include "config.h"
13 #endif
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18
19 #if defined(HAVE_GLUT_GLUT_H)
20 #include <GLUT/glut.h>
21 #else
22 #include <GL/glut.h>
23 #ifdef FREEGLUT
24 #include <GL/freeglut.h>
25 #endif
26 #endif
27
28 #ifdef WEBP_HAVE_QCMS
29 #include <qcms.h>
30 #endif
31
32 #include "webp/decode.h"
33 #include "webp/demux.h"
34
35 #include "./example_util.h"
36
37 #ifdef _MSC_VER
38 #define snprintf _snprintf
39 #endif
40
41 static void Help(void);
42
43 // Unfortunate global variables. Gathered into a struct for comfort.
44 static struct {
45   int has_animation;
46   int has_color_profile;
47   int done;
48   int decoding_error;
49   int print_info;
50   int use_color_profile;
51
52   int canvas_width, canvas_height;
53   int loop_count;
54   uint32_t bg_color;
55
56   const char* file_name;
57   WebPData data;
58   WebPDecoderConfig* config;
59   const WebPDecBuffer* pic;
60   WebPDemuxer* dmux;
61   WebPIterator frameiter;
62   struct {
63     int width, height;
64     int x_offset, y_offset;
65     enum WebPMuxAnimDispose dispose_method;
66   } prev_frame;
67   WebPChunkIterator iccp;
68 } kParams;
69
70 static void ClearPreviousPic(void) {
71   WebPFreeDecBuffer((WebPDecBuffer*)kParams.pic);
72   kParams.pic = NULL;
73 }
74
75 static void ClearParams(void) {
76   ClearPreviousPic();
77   WebPDataClear(&kParams.data);
78   WebPDemuxReleaseIterator(&kParams.frameiter);
79   WebPDemuxReleaseChunkIterator(&kParams.iccp);
80   WebPDemuxDelete(kParams.dmux);
81   kParams.dmux = NULL;
82 }
83
84 // -----------------------------------------------------------------------------
85 // Color profile handling
86 static int ApplyColorProfile(const WebPData* const profile,
87                              WebPDecBuffer* const rgba) {
88 #ifdef WEBP_HAVE_QCMS
89   int i, ok = 0;
90   uint8_t* line;
91   uint8_t major_revision;
92   qcms_profile* input_profile = NULL;
93   qcms_profile* output_profile = NULL;
94   qcms_transform* transform = NULL;
95   const qcms_data_type input_type = QCMS_DATA_RGBA_8;
96   const qcms_data_type output_type = QCMS_DATA_RGBA_8;
97   const qcms_intent intent = QCMS_INTENT_DEFAULT;
98
99   if (profile == NULL || rgba == NULL) return 0;
100   if (profile->bytes == NULL || profile->size < 10) return 1;
101   major_revision = profile->bytes[8];
102
103   qcms_enable_iccv4();
104   input_profile = qcms_profile_from_memory(profile->bytes, profile->size);
105   // qcms_profile_is_bogus() is broken with ICCv4.
106   if (input_profile == NULL ||
107       (major_revision < 4 && qcms_profile_is_bogus(input_profile))) {
108     fprintf(stderr, "Color profile is bogus!\n");
109     goto Error;
110   }
111
112   output_profile = qcms_profile_sRGB();
113   if (output_profile == NULL) {
114     fprintf(stderr, "Error creating output color profile!\n");
115     goto Error;
116   }
117
118   qcms_profile_precache_output_transform(output_profile);
119   transform = qcms_transform_create(input_profile, input_type,
120                                     output_profile, output_type,
121                                     intent);
122   if (transform == NULL) {
123     fprintf(stderr, "Error creating color transform!\n");
124     goto Error;
125   }
126
127   line = rgba->u.RGBA.rgba;
128   for (i = 0; i < rgba->height; ++i, line += rgba->u.RGBA.stride) {
129     qcms_transform_data(transform, line, line, rgba->width);
130   }
131   ok = 1;
132
133  Error:
134   if (input_profile != NULL) qcms_profile_release(input_profile);
135   if (output_profile != NULL) qcms_profile_release(output_profile);
136   if (transform != NULL) qcms_transform_release(transform);
137   return ok;
138 #else
139   (void)profile;
140   (void)rgba;
141   return 1;
142 #endif  // WEBP_HAVE_QCMS
143 }
144
145 //------------------------------------------------------------------------------
146 // File decoding
147
148 static int Decode(void) {   // Fills kParams.frameiter
149   const WebPIterator* const iter = &kParams.frameiter;
150   WebPDecoderConfig* const config = kParams.config;
151   WebPDecBuffer* const output_buffer = &config->output;
152   int ok = 0;
153
154   ClearPreviousPic();
155   output_buffer->colorspace = MODE_RGBA;
156   ok = (WebPDecode(iter->fragment.bytes, iter->fragment.size,
157                    config) == VP8_STATUS_OK);
158   if (!ok) {
159     fprintf(stderr, "Decoding of frame #%d failed!\n", iter->frame_num);
160   } else {
161     kParams.pic = output_buffer;
162     if (kParams.use_color_profile) {
163       ok = ApplyColorProfile(&kParams.iccp.chunk, output_buffer);
164       if (!ok) {
165         fprintf(stderr, "Applying color profile to frame #%d failed!\n",
166                 iter->frame_num);
167       }
168     }
169   }
170   return ok;
171 }
172
173 static void decode_callback(int what) {
174   if (what == 0 && !kParams.done) {
175     int duration = 0;
176     if (kParams.dmux != NULL) {
177       WebPIterator* const iter = &kParams.frameiter;
178       if (!WebPDemuxNextFrame(iter)) {
179         WebPDemuxReleaseIterator(iter);
180         if (WebPDemuxGetFrame(kParams.dmux, 1, iter)) {
181           --kParams.loop_count;
182           kParams.done = (kParams.loop_count == 0);
183         } else {
184           kParams.decoding_error = 1;
185           kParams.done = 1;
186           return;
187         }
188       }
189       duration = iter->duration;
190     }
191     if (!Decode()) {
192       kParams.decoding_error = 1;
193       kParams.done = 1;
194     } else {
195       glutPostRedisplay();
196       glutTimerFunc(duration, decode_callback, what);
197     }
198   }
199 }
200
201 //------------------------------------------------------------------------------
202 // Callbacks
203
204 static void HandleKey(unsigned char key, int pos_x, int pos_y) {
205   (void)pos_x;
206   (void)pos_y;
207   if (key == 'q' || key == 'Q' || key == 27 /* Esc */) {
208 #ifdef FREEGLUT
209     glutLeaveMainLoop();
210 #else
211     ClearParams();
212     exit(0);
213 #endif
214   } else if (key == 'c') {
215     if (kParams.has_color_profile && !kParams.decoding_error) {
216       kParams.use_color_profile = 1 - kParams.use_color_profile;
217
218       if (kParams.has_animation) {
219         // Restart the completed animation to pickup the color profile change.
220         if (kParams.done && kParams.loop_count == 0) {
221           kParams.loop_count =
222               (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT) + 1;
223           kParams.done = 0;
224           // Start the decode loop immediately.
225           glutTimerFunc(0, decode_callback, 0);
226         }
227       } else {
228         Decode();
229         glutPostRedisplay();
230       }
231     }
232   } else if (key == 'i') {
233     kParams.print_info = 1 - kParams.print_info;
234     glutPostRedisplay();
235   }
236 }
237
238 static void HandleReshape(int width, int height) {
239   // TODO(skal): proper handling of resize, esp. for large pictures.
240   // + key control of the zoom.
241   glViewport(0, 0, width, height);
242   glMatrixMode(GL_PROJECTION);
243   glLoadIdentity();
244   glMatrixMode(GL_MODELVIEW);
245   glLoadIdentity();
246 }
247
248 static void PrintString(const char* const text) {
249   void* const font = GLUT_BITMAP_9_BY_15;
250   int i;
251   for (i = 0; text[i]; ++i) {
252     glutBitmapCharacter(font, text[i]);
253   }
254 }
255
256 static float GetColorf(uint32_t color, int shift) {
257   return (color >> shift) / 255.f;
258 }
259
260 static void DrawCheckerBoard(void) {
261   const int square_size = 8;  // must be a power of 2
262   int x, y;
263   GLint viewport[4];  // x, y, width, height
264
265   glPushMatrix();
266
267   glGetIntegerv(GL_VIEWPORT, viewport);
268   // shift to integer coordinates with (0,0) being top-left.
269   glOrtho(0, viewport[2], viewport[3], 0, -1, 1);
270   for (y = 0; y < viewport[3]; y += square_size) {
271     for (x = 0; x < viewport[2]; x += square_size) {
272       const GLubyte color = 128 + 64 * (!((x + y) & square_size));
273       glColor3ub(color, color, color);
274       glRecti(x, y, x + square_size, y + square_size);
275     }
276   }
277   glPopMatrix();
278 }
279
280 static void HandleDisplay(void) {
281   const WebPDecBuffer* const pic = kParams.pic;
282   const WebPIterator* const iter = &kParams.frameiter;
283   GLfloat xoff, yoff;
284   if (pic == NULL) return;
285   glPushMatrix();
286   glPixelZoom(1, -1);
287   xoff = (GLfloat)(2. * iter->x_offset / kParams.canvas_width);
288   yoff = (GLfloat)(2. * iter->y_offset / kParams.canvas_height);
289   glRasterPos2f(-1.f + xoff, 1.f - yoff);
290   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
291   glPixelStorei(GL_UNPACK_ROW_LENGTH, pic->u.RGBA.stride / 4);
292
293   if (kParams.prev_frame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
294     // TODO(later): these offsets and those above should factor in window size.
295     //              they will be incorrect if the window is resized.
296     // glScissor() takes window coordinates (0,0 at bottom left).
297     const int window_x = kParams.prev_frame.x_offset;
298     const int window_y = kParams.canvas_height -
299                          kParams.prev_frame.y_offset -
300                          kParams.prev_frame.height;
301     glEnable(GL_SCISSOR_TEST);
302     // Only updated the requested area, not the whole canvas.
303     glScissor(window_x, window_y,
304               kParams.prev_frame.width, kParams.prev_frame.height);
305
306     glClear(GL_COLOR_BUFFER_BIT);  // use clear color
307     DrawCheckerBoard();
308
309     glDisable(GL_SCISSOR_TEST);
310   }
311   kParams.prev_frame.width = iter->width;
312   kParams.prev_frame.height = iter->height;
313   kParams.prev_frame.x_offset = iter->x_offset;
314   kParams.prev_frame.y_offset = iter->y_offset;
315   kParams.prev_frame.dispose_method = iter->dispose_method;
316
317   glDrawPixels(pic->width, pic->height,
318                GL_RGBA, GL_UNSIGNED_BYTE,
319                (GLvoid*)pic->u.RGBA.rgba);
320   if (kParams.print_info) {
321     char tmp[32];
322
323     glColor4f(0.90f, 0.0f, 0.90f, 1.0f);
324     glRasterPos2f(-0.95f, 0.90f);
325     PrintString(kParams.file_name);
326
327     snprintf(tmp, sizeof(tmp), "Dimension:%d x %d", pic->width, pic->height);
328     glColor4f(0.90f, 0.0f, 0.90f, 1.0f);
329     glRasterPos2f(-0.95f, 0.80f);
330     PrintString(tmp);
331     if (iter->x_offset != 0 || iter->y_offset != 0) {
332       snprintf(tmp, sizeof(tmp), " (offset:%d,%d)",
333                iter->x_offset, iter->y_offset);
334       glRasterPos2f(-0.95f, 0.70f);
335       PrintString(tmp);
336     }
337   }
338   glPopMatrix();
339   glFlush();
340 }
341
342 static void StartDisplay(void) {
343   const int width = kParams.canvas_width;
344   const int height = kParams.canvas_height;
345   glutInitDisplayMode(GLUT_RGBA);
346   glutInitWindowSize(width, height);
347   glutCreateWindow("WebP viewer");
348   glutDisplayFunc(HandleDisplay);
349   glutIdleFunc(NULL);
350   glutKeyboardFunc(HandleKey);
351   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
352   glEnable(GL_BLEND);
353   glClearColor(GetColorf(kParams.bg_color, 0),
354                GetColorf(kParams.bg_color, 8),
355                GetColorf(kParams.bg_color, 16),
356                GetColorf(kParams.bg_color, 24));
357   HandleReshape(width, height);
358   glClear(GL_COLOR_BUFFER_BIT);
359   DrawCheckerBoard();
360 }
361
362 //------------------------------------------------------------------------------
363 // Main
364
365 static void Help(void) {
366   printf("Usage: vwebp in_file [options]\n\n"
367          "Decodes the WebP image file and visualize it using OpenGL\n"
368          "Options are:\n"
369          "  -version  .... print version number and exit.\n"
370          "  -noicc ....... don't use the icc profile if present.\n"
371          "  -nofancy ..... don't use the fancy YUV420 upscaler.\n"
372          "  -nofilter .... disable in-loop filtering.\n"
373          "  -mt .......... use multi-threading.\n"
374          "  -info ........ print info.\n"
375          "  -h     ....... this help message.\n"
376          "\n"
377          "Keyboard shortcuts:\n"
378          "  'c' ................ toggle use of color profile.\n"
379          "  'i' ................ overlay file information.\n"
380          "  'q' / 'Q' / ESC .... quit.\n"
381         );
382 }
383
384 int main(int argc, char *argv[]) {
385   WebPDecoderConfig config;
386   int c;
387
388   if (!WebPInitDecoderConfig(&config)) {
389     fprintf(stderr, "Library version mismatch!\n");
390     return -1;
391   }
392   kParams.config = &config;
393   kParams.use_color_profile = 1;
394
395   for (c = 1; c < argc; ++c) {
396     if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
397       Help();
398       return 0;
399     } else if (!strcmp(argv[c], "-noicc")) {
400       kParams.use_color_profile = 0;
401     } else if (!strcmp(argv[c], "-nofancy")) {
402       config.options.no_fancy_upsampling = 1;
403     } else if (!strcmp(argv[c], "-nofilter")) {
404       config.options.bypass_filtering = 1;
405     } else if (!strcmp(argv[c], "-info")) {
406       kParams.print_info = 1;
407     } else if (!strcmp(argv[c], "-version")) {
408       const int dec_version = WebPGetDecoderVersion();
409       const int dmux_version = WebPGetDemuxVersion();
410       printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n",
411              (dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff,
412              dec_version & 0xff, (dmux_version >> 16) & 0xff,
413              (dmux_version >> 8) & 0xff, dmux_version & 0xff);
414       return 0;
415     } else if (!strcmp(argv[c], "-mt")) {
416       config.options.use_threads = 1;
417     } else if (argv[c][0] == '-') {
418       printf("Unknown option '%s'\n", argv[c]);
419       Help();
420       return -1;
421     } else {
422       kParams.file_name = argv[c];
423     }
424   }
425
426   if (kParams.file_name == NULL) {
427     printf("missing input file!!\n");
428     Help();
429     return 0;
430   }
431
432   if (!ExUtilReadFile(kParams.file_name,
433                       &kParams.data.bytes, &kParams.data.size)) {
434     goto Error;
435   }
436
437   kParams.dmux = WebPDemux(&kParams.data);
438   if (kParams.dmux == NULL) {
439     fprintf(stderr, "Could not create demuxing object!\n");
440     goto Error;
441   }
442
443   if (WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & FRAGMENTS_FLAG) {
444     fprintf(stderr, "Image fragments are not supported for now!\n");
445     goto Error;
446   }
447   kParams.canvas_width = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_WIDTH);
448   kParams.canvas_height = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_HEIGHT);
449   if (kParams.print_info) {
450     printf("Canvas: %d x %d\n", kParams.canvas_width, kParams.canvas_height);
451   }
452
453   kParams.prev_frame.width = kParams.canvas_width;
454   kParams.prev_frame.height = kParams.canvas_height;
455   kParams.prev_frame.x_offset = kParams.prev_frame.y_offset = 0;
456   kParams.prev_frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
457
458   memset(&kParams.iccp, 0, sizeof(kParams.iccp));
459   kParams.has_color_profile =
460       !!(WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & ICCP_FLAG);
461   if (kParams.has_color_profile) {
462 #ifdef WEBP_HAVE_QCMS
463     if (!WebPDemuxGetChunk(kParams.dmux, "ICCP", 1, &kParams.iccp)) goto Error;
464     printf("VP8X: Found color profile\n");
465 #else
466     fprintf(stderr, "Warning: color profile present, but qcms is unavailable!\n"
467             "Build libqcms from Mozilla or Chromium and define WEBP_HAVE_QCMS "
468             "before building.\n");
469 #endif
470   }
471
472   if (!WebPDemuxGetFrame(kParams.dmux, 1, &kParams.frameiter)) goto Error;
473
474   kParams.has_animation = (kParams.frameiter.num_frames > 1);
475   kParams.loop_count = (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT);
476   kParams.bg_color = WebPDemuxGetI(kParams.dmux, WEBP_FF_BACKGROUND_COLOR);
477   printf("VP8X: Found %d images in file (loop count = %d)\n",
478          kParams.frameiter.num_frames, kParams.loop_count);
479
480   // Decode first frame
481   if (!Decode()) goto Error;
482
483   // Position iterator to last frame. Next call to HandleDisplay will wrap over.
484   // We take this into account by bumping up loop_count.
485   WebPDemuxGetFrame(kParams.dmux, 0, &kParams.frameiter);
486   if (kParams.loop_count) ++kParams.loop_count;
487
488   // Start display (and timer)
489   glutInit(&argc, argv);
490 #ifdef FREEGLUT
491   glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
492 #endif
493   StartDisplay();
494
495   if (kParams.has_animation) glutTimerFunc(0, decode_callback, 0);
496   glutMainLoop();
497
498   // Should only be reached when using FREEGLUT:
499   ClearParams();
500   return 0;
501
502  Error:
503   ClearParams();
504   return -1;
505 }
506
507 //------------------------------------------------------------------------------