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