1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the COPYING file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 // -----------------------------------------------------------------------------
9 //
10 //  Simple OpenGL-based WebP file viewer.
11 //
12 // Author: Skal (pascal.massimino@gmail.com)
13 #ifdef HAVE_CONFIG_H
14 #include "webp/config.h"
15 #endif
16 
17 #if defined(__unix__) || defined(__CYGWIN__)
18 #define _POSIX_C_SOURCE 200112L  // for setenv
19 #endif
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #if defined(WEBP_HAVE_GL)
26 
27 #if defined(HAVE_GLUT_GLUT_H)
28 #include <GLUT/glut.h>
29 #else
30 #include <GL/glut.h>
31 #ifdef FREEGLUT
32 #include <GL/freeglut.h>
33 #endif
34 #endif
35 
36 #ifdef WEBP_HAVE_QCMS
37 #include <qcms.h>
38 #endif
39 
40 #include "webp/decode.h"
41 #include "webp/demux.h"
42 
43 #include "../examples/example_util.h"
44 #include "../imageio/imageio_util.h"
45 #include "./unicode.h"
46 
47 #if defined(_MSC_VER) && _MSC_VER < 1900
48 #define snprintf _snprintf
49 #endif
50 
51 // Unfortunate global variables. Gathered into a struct for comfort.
52 static struct {
53   int has_animation;
54   int has_color_profile;
55   int done;
56   int decoding_error;
57   int print_info;
58   int only_deltas;
59   int use_color_profile;
60   int draw_anim_background_color;
61 
62   int canvas_width, canvas_height;
63   int loop_count;
64   uint32_t bg_color;
65 
66   const char* file_name;
67   WebPData data;
68   WebPDecoderConfig config;
69   const WebPDecBuffer* pic;
70   WebPDemuxer* dmux;
71   WebPIterator curr_frame;
72   WebPIterator prev_frame;
73   WebPChunkIterator iccp;
74   int viewport_width, viewport_height;
75 } kParams;
76 
ClearPreviousPic(void)77 static void ClearPreviousPic(void) {
78   WebPFreeDecBuffer((WebPDecBuffer*)kParams.pic);
79   kParams.pic = NULL;
80 }
81 
ClearParams(void)82 static void ClearParams(void) {
83   ClearPreviousPic();
84   WebPDataClear(&kParams.data);
85   WebPDemuxReleaseIterator(&kParams.curr_frame);
86   WebPDemuxReleaseIterator(&kParams.prev_frame);
87   WebPDemuxReleaseChunkIterator(&kParams.iccp);
88   WebPDemuxDelete(kParams.dmux);
89   kParams.dmux = NULL;
90 }
91 
92 // Sets the previous frame to the dimensions of the canvas and has it dispose
93 // to background to cause the canvas to be cleared.
ClearPreviousFrame(void)94 static void ClearPreviousFrame(void) {
95   WebPIterator* const prev = &kParams.prev_frame;
96   prev->width = kParams.canvas_width;
97   prev->height = kParams.canvas_height;
98   prev->x_offset = prev->y_offset = 0;
99   prev->dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
100 }
101 
102 // -----------------------------------------------------------------------------
103 // Color profile handling
ApplyColorProfile(const WebPData* const profile, WebPDecBuffer* const rgba)104 static int ApplyColorProfile(const WebPData* const profile,
105                              WebPDecBuffer* const rgba) {
106 #ifdef WEBP_HAVE_QCMS
107   int i, ok = 0;
108   uint8_t* line;
109   uint8_t major_revision;
110   qcms_profile* input_profile = NULL;
111   qcms_profile* output_profile = NULL;
112   qcms_transform* transform = NULL;
113   const qcms_data_type input_type = QCMS_DATA_RGBA_8;
114   const qcms_data_type output_type = QCMS_DATA_RGBA_8;
115   const qcms_intent intent = QCMS_INTENT_DEFAULT;
116 
117   if (profile == NULL || rgba == NULL) return 0;
118   if (profile->bytes == NULL || profile->size < 10) return 1;
119   major_revision = profile->bytes[8];
120 
121   qcms_enable_iccv4();
122   input_profile = qcms_profile_from_memory(profile->bytes, profile->size);
123   // qcms_profile_is_bogus() is broken with ICCv4.
124   if (input_profile == NULL ||
125       (major_revision < 4 && qcms_profile_is_bogus(input_profile))) {
126     fprintf(stderr, "Color profile is bogus!\n");
127     goto Error;
128   }
129 
130   output_profile = qcms_profile_sRGB();
131   if (output_profile == NULL) {
132     fprintf(stderr, "Error creating output color profile!\n");
133     goto Error;
134   }
135 
136   qcms_profile_precache_output_transform(output_profile);
137   transform = qcms_transform_create(input_profile, input_type,
138                                     output_profile, output_type,
139                                     intent);
140   if (transform == NULL) {
141     fprintf(stderr, "Error creating color transform!\n");
142     goto Error;
143   }
144 
145   line = rgba->u.RGBA.rgba;
146   for (i = 0; i < rgba->height; ++i, line += rgba->u.RGBA.stride) {
147     qcms_transform_data(transform, line, line, rgba->width);
148   }
149   ok = 1;
150 
151  Error:
152   if (input_profile != NULL) qcms_profile_release(input_profile);
153   if (output_profile != NULL) qcms_profile_release(output_profile);
154   if (transform != NULL) qcms_transform_release(transform);
155   return ok;
156 #else
157   (void)profile;
158   (void)rgba;
159   return 1;
160 #endif  // WEBP_HAVE_QCMS
161 }
162 
163 //------------------------------------------------------------------------------
164 // File decoding
165 
Decode(void)166 static int Decode(void) {   // Fills kParams.curr_frame
167   const WebPIterator* const curr = &kParams.curr_frame;
168   WebPDecoderConfig* const config = &kParams.config;
169   WebPDecBuffer* const output_buffer = &config->output;
170   int ok = 0;
171 
172   ClearPreviousPic();
173   output_buffer->colorspace = MODE_RGBA;
174   ok = (WebPDecode(curr->fragment.bytes, curr->fragment.size,
175                    config) == VP8_STATUS_OK);
176   if (!ok) {
177     fprintf(stderr, "Decoding of frame #%d failed!\n", curr->frame_num);
178   } else {
179     kParams.pic = output_buffer;
180     if (kParams.use_color_profile) {
181       ok = ApplyColorProfile(&kParams.iccp.chunk, output_buffer);
182       if (!ok) {
183         fprintf(stderr, "Applying color profile to frame #%d failed!\n",
184                 curr->frame_num);
185       }
186     }
187   }
188   return ok;
189 }
190 
decode_callback(int what)191 static void decode_callback(int what) {
192   if (what == 0 && !kParams.done) {
193     int duration = 0;
194     if (kParams.dmux != NULL) {
195       WebPIterator* const curr = &kParams.curr_frame;
196       if (!WebPDemuxNextFrame(curr)) {
197         WebPDemuxReleaseIterator(curr);
198         if (WebPDemuxGetFrame(kParams.dmux, 1, curr)) {
199           --kParams.loop_count;
200           kParams.done = (kParams.loop_count == 0);
201           if (kParams.done) return;
202           ClearPreviousFrame();
203         } else {
204           kParams.decoding_error = 1;
205           kParams.done = 1;
206           return;
207         }
208       }
209       duration = curr->duration;
210       // Behavior copied from Chrome, cf:
211       // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/
212       // platform/graphics/DeferredImageDecoder.cpp?
213       // rcl=b4c33049f096cd283f32be9a58b9a9e768227c26&l=246
214       if (duration <= 10) duration = 100;
215     }
216     if (!Decode()) {
217       kParams.decoding_error = 1;
218       kParams.done = 1;
219     } else {
220       glutPostRedisplay();
221       glutTimerFunc(duration, decode_callback, what);
222     }
223   }
224 }
225 
226 //------------------------------------------------------------------------------
227 // Callbacks
228 
HandleKey(unsigned char key, int pos_x, int pos_y)229 static void HandleKey(unsigned char key, int pos_x, int pos_y) {
230   // Note: rescaling the window or toggling some features during an animation
231   // generates visual artifacts. This is not fixed because refreshing the frame
232   // may require rendering the whole animation from start till current frame.
233   (void)pos_x;
234   (void)pos_y;
235   if (key == 'q' || key == 'Q' || key == 27 /* Esc */) {
236 #ifdef FREEGLUT
237     glutLeaveMainLoop();
238 #else
239     ClearParams();
240     exit(0);
241 #endif
242   } else if (key == 'c') {
243     if (kParams.has_color_profile && !kParams.decoding_error) {
244       kParams.use_color_profile = 1 - kParams.use_color_profile;
245 
246       if (kParams.has_animation) {
247         // Restart the completed animation to pickup the color profile change.
248         if (kParams.done && kParams.loop_count == 0) {
249           kParams.loop_count =
250               (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT) + 1;
251           kParams.done = 0;
252           // Start the decode loop immediately.
253           glutTimerFunc(0, decode_callback, 0);
254         }
255       } else {
256         Decode();
257         glutPostRedisplay();
258       }
259     }
260   } else if (key == 'b') {
261     kParams.draw_anim_background_color = 1 - kParams.draw_anim_background_color;
262     if (!kParams.has_animation) ClearPreviousFrame();
263     glutPostRedisplay();
264   } else if (key == 'i') {
265     kParams.print_info = 1 - kParams.print_info;
266     if (!kParams.has_animation) ClearPreviousFrame();
267     glutPostRedisplay();
268   } else if (key == 'd') {
269     kParams.only_deltas = 1 - kParams.only_deltas;
270     glutPostRedisplay();
271   }
272 }
273 
HandleReshape(int width, int height)274 static void HandleReshape(int width, int height) {
275   // Note: reshape doesn't preserve aspect ratio, and might
276   // be handling larger-than-screen pictures incorrectly.
277   glViewport(0, 0, width, height);
278   glMatrixMode(GL_PROJECTION);
279   glLoadIdentity();
280   glMatrixMode(GL_MODELVIEW);
281   glLoadIdentity();
282   kParams.viewport_width = width;
283   kParams.viewport_height = height;
284   if (!kParams.has_animation) ClearPreviousFrame();
285 }
286 
PrintString(const char* const text)287 static void PrintString(const char* const text) {
288   void* const font = GLUT_BITMAP_9_BY_15;
289   int i;
290   for (i = 0; text[i]; ++i) {
291     glutBitmapCharacter(font, text[i]);
292   }
293 }
294 
GetColorf(uint32_t color, int shift)295 static float GetColorf(uint32_t color, int shift) {
296   return ((color >> shift) & 0xff) / 255.f;
297 }
298 
DrawCheckerBoard(void)299 static void DrawCheckerBoard(void) {
300   const int square_size = 8;  // must be a power of 2
301   int x, y;
302   GLint viewport[4];  // x, y, width, height
303 
304   glPushMatrix();
305 
306   glGetIntegerv(GL_VIEWPORT, viewport);
307   // shift to integer coordinates with (0,0) being top-left.
308   glOrtho(0, viewport[2], viewport[3], 0, -1, 1);
309   for (y = 0; y < viewport[3]; y += square_size) {
310     for (x = 0; x < viewport[2]; x += square_size) {
311       const GLubyte color = 128 + 64 * (!((x + y) & square_size));
312       glColor3ub(color, color, color);
313       glRecti(x, y, x + square_size, y + square_size);
314     }
315   }
316   glPopMatrix();
317 }
318 
DrawBackground(void)319 static void DrawBackground(void) {
320   // Whole window cleared with clear color, checkerboard rendered on top of it.
321   glClear(GL_COLOR_BUFFER_BIT);
322   DrawCheckerBoard();
323 
324   // ANIM background color rendered (blend) on top. Default is white for still
325   // images (without ANIM chunk). glClear() can't be used for that (no blend).
326   if (kParams.draw_anim_background_color) {
327     glPushMatrix();
328     glLoadIdentity();
329     glColor4f(GetColorf(kParams.bg_color, 16),  // BGRA from spec
330               GetColorf(kParams.bg_color, 8),
331               GetColorf(kParams.bg_color, 0),
332               GetColorf(kParams.bg_color, 24));
333     glRecti(-1, -1, +1, +1);
334     glPopMatrix();
335   }
336 }
337 
338 // Draw background in a scissored rectangle.
DrawBackgroundScissored(int window_x, int window_y, int frame_w, int frame_h)339 static void DrawBackgroundScissored(int window_x, int window_y, int frame_w,
340                                     int frame_h) {
341   // Only update the requested area, not the whole canvas.
342   window_x = window_x * kParams.viewport_width / kParams.canvas_width;
343   window_y = window_y * kParams.viewport_height / kParams.canvas_height;
344   frame_w = frame_w * kParams.viewport_width / kParams.canvas_width;
345   frame_h = frame_h * kParams.viewport_height / kParams.canvas_height;
346 
347   // glScissor() takes window coordinates (0,0 at bottom left).
348   window_y = kParams.viewport_height - window_y - frame_h;
349 
350   glEnable(GL_SCISSOR_TEST);
351   glScissor(window_x, window_y, frame_w, frame_h);
352   DrawBackground();
353   glDisable(GL_SCISSOR_TEST);
354 }
355 
HandleDisplay(void)356 static void HandleDisplay(void) {
357   const WebPDecBuffer* const pic = kParams.pic;
358   const WebPIterator* const curr = &kParams.curr_frame;
359   WebPIterator* const prev = &kParams.prev_frame;
360   GLfloat xoff, yoff;
361   if (pic == NULL) return;
362   glPushMatrix();
363   glPixelZoom((GLfloat)(+1. / kParams.canvas_width * kParams.viewport_width),
364               (GLfloat)(-1. / kParams.canvas_height * kParams.viewport_height));
365   xoff = (GLfloat)(2. * curr->x_offset / kParams.canvas_width);
366   yoff = (GLfloat)(2. * curr->y_offset / kParams.canvas_height);
367   glRasterPos2f(-1.f + xoff, 1.f - yoff);
368   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
369   glPixelStorei(GL_UNPACK_ROW_LENGTH, pic->u.RGBA.stride / 4);
370 
371   if (kParams.only_deltas) {
372     DrawBackground();
373   } else {
374     // The rectangle of the previous frame might be different than the current
375     // frame, so we may need to DrawBackgroundScissored for both.
376     if (prev->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
377       // Clear the previous frame rectangle.
378       DrawBackgroundScissored(prev->x_offset, prev->y_offset, prev->width,
379                               prev->height);
380     }
381     if (curr->blend_method == WEBP_MUX_NO_BLEND) {
382       // We simulate no-blending behavior by first clearing the current frame
383       // rectangle and then alpha-blending against it.
384       DrawBackgroundScissored(curr->x_offset, curr->y_offset, curr->width,
385                               curr->height);
386     }
387   }
388 
389   *prev = *curr;
390 
391   glDrawPixels(pic->width, pic->height,
392                GL_RGBA, GL_UNSIGNED_BYTE,
393                (GLvoid*)pic->u.RGBA.rgba);
394   if (kParams.print_info) {
395     char tmp[32];
396 
397     glColor4f(0.90f, 0.0f, 0.90f, 1.0f);
398     glRasterPos2f(-0.95f, 0.90f);
399     PrintString(kParams.file_name);
400 
401     snprintf(tmp, sizeof(tmp), "Dimension:%d x %d", pic->width, pic->height);
402     glColor4f(0.90f, 0.0f, 0.90f, 1.0f);
403     glRasterPos2f(-0.95f, 0.80f);
404     PrintString(tmp);
405     if (curr->x_offset != 0 || curr->y_offset != 0) {
406       snprintf(tmp, sizeof(tmp), " (offset:%d,%d)",
407                curr->x_offset, curr->y_offset);
408       glRasterPos2f(-0.95f, 0.70f);
409       PrintString(tmp);
410     }
411   }
412   glPopMatrix();
413 #if defined(__APPLE__) || defined(_WIN32)
414   glFlush();
415 #else
416   glutSwapBuffers();
417 #endif
418 }
419 
StartDisplay(void)420 static void StartDisplay(void) {
421   int width = kParams.canvas_width;
422   int height = kParams.canvas_height;
423   int screen_width, screen_height;
424   // TODO(webp:365) GLUT_DOUBLE results in flickering / old frames to be
425   // partially displayed with animated webp + alpha.
426 #if defined(__APPLE__) || defined(_WIN32)
427   glutInitDisplayMode(GLUT_RGBA);
428 #else
429   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
430 #endif
431   screen_width = glutGet(GLUT_SCREEN_WIDTH);
432   screen_height = glutGet(GLUT_SCREEN_HEIGHT);
433   if (width > screen_width || height > screen_height) {
434     if (width > screen_width) {
435       height = (height * screen_width + width - 1) / width;
436       width = screen_width;
437     }
438     if (height > screen_height) {
439       width = (width * screen_height + height - 1) / height;
440       height = screen_height;
441     }
442   }
443   glutInitWindowSize(width, height);
444   glutCreateWindow("WebP viewer");
445   glutDisplayFunc(HandleDisplay);
446   glutReshapeFunc(HandleReshape);
447   glutIdleFunc(NULL);
448   glutKeyboardFunc(HandleKey);
449   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
450   glEnable(GL_BLEND);
451   glClearColor(0, 0, 0, 0);  // window will be cleared to black (no blend)
452   DrawBackground();
453 }
454 
455 //------------------------------------------------------------------------------
456 // Main
457 
Help(void)458 static void Help(void) {
459   printf(
460       "Usage: vwebp in_file [options]\n\n"
461       "Decodes the WebP image file and visualize it using OpenGL\n"
462       "Options are:\n"
463       "  -version ..... print version number and exit\n"
464       "  -noicc ....... don't use the icc profile if present\n"
465       "  -nofancy ..... don't use the fancy YUV420 upscaler\n"
466       "  -nofilter .... disable in-loop filtering\n"
467       "  -dither <int>  dithering strength (0..100), default=50\n"
468       "  -noalphadither disable alpha plane dithering\n"
469       "  -usebgcolor .. display background color\n"
470       "  -mt .......... use multi-threading\n"
471       "  -info ........ print info\n"
472       "  -h ........... this help message\n"
473       "\n"
474       "Keyboard shortcuts:\n"
475       "  'c' ................ toggle use of color profile\n"
476       "  'b' ................ toggle background color display\n"
477       "  'i' ................ overlay file information\n"
478       "  'd' ................ disable blending & disposal (debug)\n"
479       "  'q' / 'Q' / ESC .... quit\n");
480 }
481 
main(int argc, char* argv[])482 int main(int argc, char* argv[]) {
483   int c;
484   WebPDecoderConfig* const config = &kParams.config;
485   WebPIterator* const curr = &kParams.curr_frame;
486 
487   INIT_WARGV(argc, argv);
488 
489   if (!WebPInitDecoderConfig(config)) {
490     fprintf(stderr, "Library version mismatch!\n");
491     FREE_WARGV_AND_RETURN(-1);
492   }
493   config->options.dithering_strength = 50;
494   config->options.alpha_dithering_strength = 100;
495   kParams.use_color_profile = 1;
496   // Background color hidden by default to see transparent areas.
497   kParams.draw_anim_background_color = 0;
498 
499   for (c = 1; c < argc; ++c) {
500     int parse_error = 0;
501     if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
502       Help();
503       FREE_WARGV_AND_RETURN(0);
504     } else if (!strcmp(argv[c], "-noicc")) {
505       kParams.use_color_profile = 0;
506     } else if (!strcmp(argv[c], "-nofancy")) {
507       config->options.no_fancy_upsampling = 1;
508     } else if (!strcmp(argv[c], "-nofilter")) {
509       config->options.bypass_filtering = 1;
510     } else if (!strcmp(argv[c], "-noalphadither")) {
511       config->options.alpha_dithering_strength = 0;
512     } else if (!strcmp(argv[c], "-usebgcolor")) {
513       kParams.draw_anim_background_color = 1;
514     } else if (!strcmp(argv[c], "-dither") && c + 1 < argc) {
515       config->options.dithering_strength =
516           ExUtilGetInt(argv[++c], 0, &parse_error);
517     } else if (!strcmp(argv[c], "-info")) {
518       kParams.print_info = 1;
519     } else if (!strcmp(argv[c], "-version")) {
520       const int dec_version = WebPGetDecoderVersion();
521       const int dmux_version = WebPGetDemuxVersion();
522       printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n",
523              (dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff,
524              dec_version & 0xff, (dmux_version >> 16) & 0xff,
525              (dmux_version >> 8) & 0xff, dmux_version & 0xff);
526       FREE_WARGV_AND_RETURN(0);
527     } else if (!strcmp(argv[c], "-mt")) {
528       config->options.use_threads = 1;
529     } else if (!strcmp(argv[c], "--")) {
530       if (c < argc - 1) kParams.file_name = (const char*)GET_WARGV(argv, ++c);
531       break;
532     } else if (argv[c][0] == '-') {
533       printf("Unknown option '%s'\n", argv[c]);
534       Help();
535       FREE_WARGV_AND_RETURN(-1);
536     } else {
537       kParams.file_name = (const char*)GET_WARGV(argv, c);
538     }
539 
540     if (parse_error) {
541       Help();
542       FREE_WARGV_AND_RETURN(-1);
543     }
544   }
545 
546   if (kParams.file_name == NULL) {
547     printf("missing input file!!\n");
548     Help();
549     FREE_WARGV_AND_RETURN(0);
550   }
551 
552   if (!ImgIoUtilReadFile(kParams.file_name,
553                          &kParams.data.bytes, &kParams.data.size)) {
554     goto Error;
555   }
556 
557   if (!WebPGetInfo(kParams.data.bytes, kParams.data.size, NULL, NULL)) {
558     fprintf(stderr, "Input file doesn't appear to be WebP format.\n");
559     goto Error;
560   }
561 
562   kParams.dmux = WebPDemux(&kParams.data);
563   if (kParams.dmux == NULL) {
564     fprintf(stderr, "Could not create demuxing object!\n");
565     goto Error;
566   }
567 
568   kParams.canvas_width = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_WIDTH);
569   kParams.canvas_height = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_HEIGHT);
570   if (kParams.print_info) {
571     printf("Canvas: %d x %d\n", kParams.canvas_width, kParams.canvas_height);
572   }
573 
574   ClearPreviousFrame();
575 
576   memset(&kParams.iccp, 0, sizeof(kParams.iccp));
577   kParams.has_color_profile =
578       !!(WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & ICCP_FLAG);
579   if (kParams.has_color_profile) {
580 #ifdef WEBP_HAVE_QCMS
581     if (!WebPDemuxGetChunk(kParams.dmux, "ICCP", 1, &kParams.iccp)) goto Error;
582     printf("VP8X: Found color profile\n");
583 #else
584     fprintf(stderr, "Warning: color profile present, but qcms is unavailable!\n"
585             "Build libqcms from Mozilla or Chromium and define WEBP_HAVE_QCMS "
586             "before building.\n");
587 #endif
588   }
589 
590   if (!WebPDemuxGetFrame(kParams.dmux, 1, curr)) goto Error;
591 
592   kParams.has_animation = (curr->num_frames > 1);
593   kParams.loop_count = (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT);
594   kParams.bg_color = WebPDemuxGetI(kParams.dmux, WEBP_FF_BACKGROUND_COLOR);
595   printf("VP8X: Found %d images in file (loop count = %d)\n",
596          curr->num_frames, kParams.loop_count);
597 
598   // Decode first frame
599   if (!Decode()) goto Error;
600 
601   // Position iterator to last frame. Next call to HandleDisplay will wrap over.
602   // We take this into account by bumping up loop_count.
603   WebPDemuxGetFrame(kParams.dmux, 0, curr);
604   if (kParams.loop_count) ++kParams.loop_count;
605 
606 #if defined(__unix__) || defined(__CYGWIN__)
607   // Work around GLUT compositor bug.
608   // https://bugs.launchpad.net/ubuntu/+source/freeglut/+bug/369891
609   setenv("XLIB_SKIP_ARGB_VISUALS", "1", 1);
610 #endif
611 
612   // Start display (and timer)
613   glutInit(&argc, argv);
614 #ifdef FREEGLUT
615   glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
616 #endif
617   StartDisplay();
618 
619   if (kParams.has_animation) glutTimerFunc(0, decode_callback, 0);
620   glutMainLoop();
621 
622   // Should only be reached when using FREEGLUT:
623   ClearParams();
624   FREE_WARGV_AND_RETURN(0);
625 
626  Error:
627   ClearParams();
628   FREE_WARGV_AND_RETURN(-1);
629 }
630 
631 #else   // !WEBP_HAVE_GL
632 
main(int argc, const char* argv[])633 int main(int argc, const char* argv[]) {
634   fprintf(stderr, "OpenGL support not enabled in %s.\n", argv[0]);
635   (void)argc;
636   return 0;
637 }
638 
639 #endif
640 
641 //------------------------------------------------------------------------------
642