vwebp.c (21673B)
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 <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 78 static void ClearPreviousPic(void) { 79 WebPFreeDecBuffer((WebPDecBuffer*)kParams.pic); 80 kParams.pic = NULL; 81 } 82 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. 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 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 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 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 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 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 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 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 309 static float GetColorf(uint32_t color, int shift) { 310 return ((color >> shift) & 0xff) / 255.f; 311 } 312 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 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. 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 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 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 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 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(EXIT_FAILURE); 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(EXIT_SUCCESS); 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(EXIT_SUCCESS); 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(EXIT_FAILURE); 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(EXIT_FAILURE); 565 } 566 } 567 568 if (kParams.file_name == NULL) { 569 printf("missing input file!!\n"); 570 Help(); 571 FREE_WARGV_AND_RETURN(EXIT_FAILURE); 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(EXIT_SUCCESS); 647 648 Error: 649 ClearParams(); 650 FREE_WARGV_AND_RETURN(EXIT_FAILURE); 651 } 652 653 #else // !WEBP_HAVE_GL 654 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 EXIT_FAILURE; 659 } 660 661 #endif 662 663 //------------------------------------------------------------------------------