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