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. 52static 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 77static void ClearPreviousPic(void) { 78 WebPFreeDecBuffer((WebPDecBuffer*)kParams.pic); 79 kParams.pic = NULL; 80} 81 82static 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. 94static 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 104static 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 166static 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 191static 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 229static 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 274static 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 287static 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 295static float GetColorf(uint32_t color, int shift) { 296 return ((color >> shift) & 0xff) / 255.f; 297} 298 299static 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 319static 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. 339static 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 356static 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 420static 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 458static 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 482int 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 633int 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