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