github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/image/webp/libwebp/examples/vwebp.c (about) 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 "config.h" 15 #endif 16 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 21 #if defined(WEBP_HAVE_GL) 22 23 #if defined(HAVE_GLUT_GLUT_H) 24 #include <GLUT/glut.h> 25 #else 26 #include <GL/glut.h> 27 #ifdef FREEGLUT 28 #include <GL/freeglut.h> 29 #endif 30 #endif 31 32 #ifdef WEBP_HAVE_QCMS 33 #include <qcms.h> 34 #endif 35 36 #include "webp/decode.h" 37 #include "webp/demux.h" 38 39 #include "./example_util.h" 40 41 #ifdef _MSC_VER 42 #define snprintf _snprintf 43 #endif 44 45 static void Help(void); 46 47 // Unfortunate global variables. Gathered into a struct for comfort. 48 static struct { 49 int has_animation; 50 int has_color_profile; 51 int done; 52 int decoding_error; 53 int print_info; 54 int use_color_profile; 55 56 int canvas_width, canvas_height; 57 int loop_count; 58 uint32_t bg_color; 59 60 const char* file_name; 61 WebPData data; 62 WebPDecoderConfig config; 63 const WebPDecBuffer* pic; 64 WebPDemuxer* dmux; 65 WebPIterator curr_frame; 66 WebPIterator prev_frame; 67 WebPChunkIterator iccp; 68 } kParams; 69 70 static void ClearPreviousPic(void) { 71 WebPFreeDecBuffer((WebPDecBuffer*)kParams.pic); 72 kParams.pic = NULL; 73 } 74 75 static void ClearParams(void) { 76 ClearPreviousPic(); 77 WebPDataClear(&kParams.data); 78 WebPDemuxReleaseIterator(&kParams.curr_frame); 79 WebPDemuxReleaseIterator(&kParams.prev_frame); 80 WebPDemuxReleaseChunkIterator(&kParams.iccp); 81 WebPDemuxDelete(kParams.dmux); 82 kParams.dmux = NULL; 83 } 84 85 // ----------------------------------------------------------------------------- 86 // Color profile handling 87 static int ApplyColorProfile(const WebPData* const profile, 88 WebPDecBuffer* const rgba) { 89 #ifdef WEBP_HAVE_QCMS 90 int i, ok = 0; 91 uint8_t* line; 92 uint8_t major_revision; 93 qcms_profile* input_profile = NULL; 94 qcms_profile* output_profile = NULL; 95 qcms_transform* transform = NULL; 96 const qcms_data_type input_type = QCMS_DATA_RGBA_8; 97 const qcms_data_type output_type = QCMS_DATA_RGBA_8; 98 const qcms_intent intent = QCMS_INTENT_DEFAULT; 99 100 if (profile == NULL || rgba == NULL) return 0; 101 if (profile->bytes == NULL || profile->size < 10) return 1; 102 major_revision = profile->bytes[8]; 103 104 qcms_enable_iccv4(); 105 input_profile = qcms_profile_from_memory(profile->bytes, profile->size); 106 // qcms_profile_is_bogus() is broken with ICCv4. 107 if (input_profile == NULL || 108 (major_revision < 4 && qcms_profile_is_bogus(input_profile))) { 109 fprintf(stderr, "Color profile is bogus!\n"); 110 goto Error; 111 } 112 113 output_profile = qcms_profile_sRGB(); 114 if (output_profile == NULL) { 115 fprintf(stderr, "Error creating output color profile!\n"); 116 goto Error; 117 } 118 119 qcms_profile_precache_output_transform(output_profile); 120 transform = qcms_transform_create(input_profile, input_type, 121 output_profile, output_type, 122 intent); 123 if (transform == NULL) { 124 fprintf(stderr, "Error creating color transform!\n"); 125 goto Error; 126 } 127 128 line = rgba->u.RGBA.rgba; 129 for (i = 0; i < rgba->height; ++i, line += rgba->u.RGBA.stride) { 130 qcms_transform_data(transform, line, line, rgba->width); 131 } 132 ok = 1; 133 134 Error: 135 if (input_profile != NULL) qcms_profile_release(input_profile); 136 if (output_profile != NULL) qcms_profile_release(output_profile); 137 if (transform != NULL) qcms_transform_release(transform); 138 return ok; 139 #else 140 (void)profile; 141 (void)rgba; 142 return 1; 143 #endif // WEBP_HAVE_QCMS 144 } 145 146 //------------------------------------------------------------------------------ 147 // File decoding 148 149 static int Decode(void) { // Fills kParams.curr_frame 150 const WebPIterator* const curr = &kParams.curr_frame; 151 WebPDecoderConfig* const config = &kParams.config; 152 WebPDecBuffer* const output_buffer = &config->output; 153 int ok = 0; 154 155 ClearPreviousPic(); 156 output_buffer->colorspace = MODE_RGBA; 157 ok = (WebPDecode(curr->fragment.bytes, curr->fragment.size, 158 config) == VP8_STATUS_OK); 159 if (!ok) { 160 fprintf(stderr, "Decoding of frame #%d failed!\n", curr->frame_num); 161 } else { 162 kParams.pic = output_buffer; 163 if (kParams.use_color_profile) { 164 ok = ApplyColorProfile(&kParams.iccp.chunk, output_buffer); 165 if (!ok) { 166 fprintf(stderr, "Applying color profile to frame #%d failed!\n", 167 curr->frame_num); 168 } 169 } 170 } 171 return ok; 172 } 173 174 static void decode_callback(int what) { 175 if (what == 0 && !kParams.done) { 176 int duration = 0; 177 if (kParams.dmux != NULL) { 178 WebPIterator* const curr = &kParams.curr_frame; 179 if (!WebPDemuxNextFrame(curr)) { 180 WebPDemuxReleaseIterator(curr); 181 if (WebPDemuxGetFrame(kParams.dmux, 1, curr)) { 182 --kParams.loop_count; 183 kParams.done = (kParams.loop_count == 0); 184 } else { 185 kParams.decoding_error = 1; 186 kParams.done = 1; 187 return; 188 } 189 } 190 duration = curr->duration; 191 } 192 if (!Decode()) { 193 kParams.decoding_error = 1; 194 kParams.done = 1; 195 } else { 196 glutPostRedisplay(); 197 glutTimerFunc(duration, decode_callback, what); 198 } 199 } 200 } 201 202 //------------------------------------------------------------------------------ 203 // Callbacks 204 205 static void HandleKey(unsigned char key, int pos_x, int pos_y) { 206 (void)pos_x; 207 (void)pos_y; 208 if (key == 'q' || key == 'Q' || key == 27 /* Esc */) { 209 #ifdef FREEGLUT 210 glutLeaveMainLoop(); 211 #else 212 ClearParams(); 213 exit(0); 214 #endif 215 } else if (key == 'c') { 216 if (kParams.has_color_profile && !kParams.decoding_error) { 217 kParams.use_color_profile = 1 - kParams.use_color_profile; 218 219 if (kParams.has_animation) { 220 // Restart the completed animation to pickup the color profile change. 221 if (kParams.done && kParams.loop_count == 0) { 222 kParams.loop_count = 223 (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT) + 1; 224 kParams.done = 0; 225 // Start the decode loop immediately. 226 glutTimerFunc(0, decode_callback, 0); 227 } 228 } else { 229 Decode(); 230 glutPostRedisplay(); 231 } 232 } 233 } else if (key == 'i') { 234 kParams.print_info = 1 - kParams.print_info; 235 glutPostRedisplay(); 236 } 237 } 238 239 static void HandleReshape(int width, int height) { 240 // TODO(skal): proper handling of resize, esp. for large pictures. 241 // + key control of the zoom. 242 glViewport(0, 0, width, height); 243 glMatrixMode(GL_PROJECTION); 244 glLoadIdentity(); 245 glMatrixMode(GL_MODELVIEW); 246 glLoadIdentity(); 247 } 248 249 static void PrintString(const char* const text) { 250 void* const font = GLUT_BITMAP_9_BY_15; 251 int i; 252 for (i = 0; text[i]; ++i) { 253 glutBitmapCharacter(font, text[i]); 254 } 255 } 256 257 static float GetColorf(uint32_t color, int shift) { 258 return (color >> shift) / 255.f; 259 } 260 261 static void DrawCheckerBoard(void) { 262 const int square_size = 8; // must be a power of 2 263 int x, y; 264 GLint viewport[4]; // x, y, width, height 265 266 glPushMatrix(); 267 268 glGetIntegerv(GL_VIEWPORT, viewport); 269 // shift to integer coordinates with (0,0) being top-left. 270 glOrtho(0, viewport[2], viewport[3], 0, -1, 1); 271 for (y = 0; y < viewport[3]; y += square_size) { 272 for (x = 0; x < viewport[2]; x += square_size) { 273 const GLubyte color = 128 + 64 * (!((x + y) & square_size)); 274 glColor3ub(color, color, color); 275 glRecti(x, y, x + square_size, y + square_size); 276 } 277 } 278 glPopMatrix(); 279 } 280 281 static void HandleDisplay(void) { 282 const WebPDecBuffer* const pic = kParams.pic; 283 const WebPIterator* const curr = &kParams.curr_frame; 284 WebPIterator* const prev = &kParams.prev_frame; 285 GLfloat xoff, yoff; 286 if (pic == NULL) return; 287 glPushMatrix(); 288 glPixelZoom(1, -1); 289 xoff = (GLfloat)(2. * curr->x_offset / kParams.canvas_width); 290 yoff = (GLfloat)(2. * curr->y_offset / kParams.canvas_height); 291 glRasterPos2f(-1.f + xoff, 1.f - yoff); 292 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 293 glPixelStorei(GL_UNPACK_ROW_LENGTH, pic->u.RGBA.stride / 4); 294 295 if (prev->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND || 296 curr->blend_method == WEBP_MUX_NO_BLEND) { 297 // TODO(later): these offsets and those above should factor in window size. 298 // they will be incorrect if the window is resized. 299 // glScissor() takes window coordinates (0,0 at bottom left). 300 int window_x, window_y; 301 if (prev->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { 302 // Clear the previous frame rectangle. 303 window_x = prev->x_offset; 304 window_y = kParams.canvas_height - prev->y_offset - prev->height; 305 } else { // curr->blend_method == WEBP_MUX_NO_BLEND. 306 // We simulate no-blending behavior by first clearing the current frame 307 // rectangle (to a checker-board) and then alpha-blending against it. 308 window_x = curr->x_offset; 309 window_y = kParams.canvas_height - curr->y_offset - curr->height; 310 } 311 glEnable(GL_SCISSOR_TEST); 312 // Only update the requested area, not the whole canvas. 313 glScissor(window_x, window_y, prev->width, prev->height); 314 315 glClear(GL_COLOR_BUFFER_BIT); // use clear color 316 DrawCheckerBoard(); 317 318 glDisable(GL_SCISSOR_TEST); 319 } 320 321 *prev = *curr; 322 323 glDrawPixels(pic->width, pic->height, 324 GL_RGBA, GL_UNSIGNED_BYTE, 325 (GLvoid*)pic->u.RGBA.rgba); 326 if (kParams.print_info) { 327 char tmp[32]; 328 329 glColor4f(0.90f, 0.0f, 0.90f, 1.0f); 330 glRasterPos2f(-0.95f, 0.90f); 331 PrintString(kParams.file_name); 332 333 snprintf(tmp, sizeof(tmp), "Dimension:%d x %d", pic->width, pic->height); 334 glColor4f(0.90f, 0.0f, 0.90f, 1.0f); 335 glRasterPos2f(-0.95f, 0.80f); 336 PrintString(tmp); 337 if (curr->x_offset != 0 || curr->y_offset != 0) { 338 snprintf(tmp, sizeof(tmp), " (offset:%d,%d)", 339 curr->x_offset, curr->y_offset); 340 glRasterPos2f(-0.95f, 0.70f); 341 PrintString(tmp); 342 } 343 } 344 glPopMatrix(); 345 glFlush(); 346 } 347 348 static void StartDisplay(void) { 349 const int width = kParams.canvas_width; 350 const int height = kParams.canvas_height; 351 glutInitDisplayMode(GLUT_RGBA); 352 glutInitWindowSize(width, height); 353 glutCreateWindow("WebP viewer"); 354 glutDisplayFunc(HandleDisplay); 355 glutIdleFunc(NULL); 356 glutKeyboardFunc(HandleKey); 357 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 358 glEnable(GL_BLEND); 359 glClearColor(GetColorf(kParams.bg_color, 0), 360 GetColorf(kParams.bg_color, 8), 361 GetColorf(kParams.bg_color, 16), 362 GetColorf(kParams.bg_color, 24)); 363 HandleReshape(width, height); 364 glClear(GL_COLOR_BUFFER_BIT); 365 DrawCheckerBoard(); 366 } 367 368 //------------------------------------------------------------------------------ 369 // Main 370 371 static void Help(void) { 372 printf("Usage: vwebp in_file [options]\n\n" 373 "Decodes the WebP image file and visualize it using OpenGL\n" 374 "Options are:\n" 375 " -version .... print version number and exit.\n" 376 " -noicc ....... don't use the icc profile if present.\n" 377 " -nofancy ..... don't use the fancy YUV420 upscaler.\n" 378 " -nofilter .... disable in-loop filtering.\n" 379 " -dither <int> dithering strength (0..100). Default=50.\n" 380 " -mt .......... use multi-threading.\n" 381 " -info ........ print info.\n" 382 " -h ....... this help message.\n" 383 "\n" 384 "Keyboard shortcuts:\n" 385 " 'c' ................ toggle use of color profile.\n" 386 " 'i' ................ overlay file information.\n" 387 " 'q' / 'Q' / ESC .... quit.\n" 388 ); 389 } 390 391 int main(int argc, char *argv[]) { 392 int c; 393 WebPDecoderConfig* const config = &kParams.config; 394 WebPIterator* const curr = &kParams.curr_frame; 395 WebPIterator* const prev = &kParams.prev_frame; 396 397 if (!WebPInitDecoderConfig(config)) { 398 fprintf(stderr, "Library version mismatch!\n"); 399 return -1; 400 } 401 config->options.dithering_strength = 50; 402 kParams.use_color_profile = 1; 403 404 for (c = 1; c < argc; ++c) { 405 if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { 406 Help(); 407 return 0; 408 } else if (!strcmp(argv[c], "-noicc")) { 409 kParams.use_color_profile = 0; 410 } else if (!strcmp(argv[c], "-nofancy")) { 411 config->options.no_fancy_upsampling = 1; 412 } else if (!strcmp(argv[c], "-nofilter")) { 413 config->options.bypass_filtering = 1; 414 } else if (!strcmp(argv[c], "-dither") && c + 1 < argc) { 415 config->options.dithering_strength = strtol(argv[++c], NULL, 0); 416 } else if (!strcmp(argv[c], "-info")) { 417 kParams.print_info = 1; 418 } else if (!strcmp(argv[c], "-version")) { 419 const int dec_version = WebPGetDecoderVersion(); 420 const int dmux_version = WebPGetDemuxVersion(); 421 printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n", 422 (dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff, 423 dec_version & 0xff, (dmux_version >> 16) & 0xff, 424 (dmux_version >> 8) & 0xff, dmux_version & 0xff); 425 return 0; 426 } else if (!strcmp(argv[c], "-mt")) { 427 config->options.use_threads = 1; 428 } else if (!strcmp(argv[c], "--")) { 429 if (c < argc - 1) kParams.file_name = argv[++c]; 430 break; 431 } else if (argv[c][0] == '-') { 432 printf("Unknown option '%s'\n", argv[c]); 433 Help(); 434 return -1; 435 } else { 436 kParams.file_name = argv[c]; 437 } 438 } 439 440 if (kParams.file_name == NULL) { 441 printf("missing input file!!\n"); 442 Help(); 443 return 0; 444 } 445 446 if (!ExUtilReadFile(kParams.file_name, 447 &kParams.data.bytes, &kParams.data.size)) { 448 goto Error; 449 } 450 451 if (!WebPGetInfo(kParams.data.bytes, kParams.data.size, NULL, NULL)) { 452 fprintf(stderr, "Input file doesn't appear to be WebP format.\n"); 453 goto Error; 454 } 455 456 kParams.dmux = WebPDemux(&kParams.data); 457 if (kParams.dmux == NULL) { 458 fprintf(stderr, "Could not create demuxing object!\n"); 459 goto Error; 460 } 461 462 if (WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & FRAGMENTS_FLAG) { 463 fprintf(stderr, "Image fragments are not supported for now!\n"); 464 goto Error; 465 } 466 kParams.canvas_width = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_WIDTH); 467 kParams.canvas_height = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_HEIGHT); 468 if (kParams.print_info) { 469 printf("Canvas: %d x %d\n", kParams.canvas_width, kParams.canvas_height); 470 } 471 472 prev->width = kParams.canvas_width; 473 prev->height = kParams.canvas_height; 474 prev->x_offset = prev->y_offset = 0; 475 prev->dispose_method = WEBP_MUX_DISPOSE_BACKGROUND; 476 477 memset(&kParams.iccp, 0, sizeof(kParams.iccp)); 478 kParams.has_color_profile = 479 !!(WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & ICCP_FLAG); 480 if (kParams.has_color_profile) { 481 #ifdef WEBP_HAVE_QCMS 482 if (!WebPDemuxGetChunk(kParams.dmux, "ICCP", 1, &kParams.iccp)) goto Error; 483 printf("VP8X: Found color profile\n"); 484 #else 485 fprintf(stderr, "Warning: color profile present, but qcms is unavailable!\n" 486 "Build libqcms from Mozilla or Chromium and define WEBP_HAVE_QCMS " 487 "before building.\n"); 488 #endif 489 } 490 491 if (!WebPDemuxGetFrame(kParams.dmux, 1, curr)) goto Error; 492 493 kParams.has_animation = (curr->num_frames > 1); 494 kParams.loop_count = (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT); 495 kParams.bg_color = WebPDemuxGetI(kParams.dmux, WEBP_FF_BACKGROUND_COLOR); 496 printf("VP8X: Found %d images in file (loop count = %d)\n", 497 curr->num_frames, kParams.loop_count); 498 499 // Decode first frame 500 if (!Decode()) goto Error; 501 502 // Position iterator to last frame. Next call to HandleDisplay will wrap over. 503 // We take this into account by bumping up loop_count. 504 WebPDemuxGetFrame(kParams.dmux, 0, curr); 505 if (kParams.loop_count) ++kParams.loop_count; 506 507 // Start display (and timer) 508 glutInit(&argc, argv); 509 #ifdef FREEGLUT 510 glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); 511 #endif 512 StartDisplay(); 513 514 if (kParams.has_animation) glutTimerFunc(0, decode_callback, 0); 515 glutMainLoop(); 516 517 // Should only be reached when using FREEGLUT: 518 ClearParams(); 519 return 0; 520 521 Error: 522 ClearParams(); 523 return -1; 524 } 525 526 #else // !WEBP_HAVE_GL 527 528 int main(int argc, const char *argv[]) { 529 fprintf(stderr, "OpenGL support not enabled in %s.\n", argv[0]); 530 (void)argc; 531 return 0; 532 } 533 534 #endif 535 536 //------------------------------------------------------------------------------