xref: /aosp_15_r20/external/webp/examples/vwebp.c (revision b2055c353e87c8814eb2b6b1b11112a1562253bd)
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