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