blob: fff2f087ad53b82cd1cc7dbf44a55a92503e164c [file] [log] [blame]
James Zernad1e1632012-01-06 14:49:06 -08001// Copyright 2011 Google Inc. All Rights Reserved.
Pascal Massimino7937b402011-12-13 14:02:04 -08002//
3// This code is licensed under the same terms as WebM:
4// Software License Agreement: http://www.webmproject.org/license/software/
5// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
6// -----------------------------------------------------------------------------
7//
8// Simple WebP file viewer.
9//
10// Compiling on linux:
11// sudo apt-get install libglut3-dev mesa-common-dev
Pascal Massimino861a5b72012-05-09 00:41:12 -070012// gcc -o vwebp vwebp.c -O3 -lwebp -lwebpmux -lglut -lGL -lpthread -lm
Pascal Massimino7937b402011-12-13 14:02:04 -080013// Compiling on Mac + XCode:
Pascal Massimino861a5b72012-05-09 00:41:12 -070014// gcc -o vwebp vwebp.c -lwebp -lwebpmux -framework GLUT -framework OpenGL
Pascal Massimino7937b402011-12-13 14:02:04 -080015//
16// Author: Skal (pascal.massimino@gmail.com)
17
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21
22#include "webp/decode.h"
Pascal Massimino861a5b72012-05-09 00:41:12 -070023#include "webp/mux.h"
Pascal Massimino7937b402011-12-13 14:02:04 -080024
25#ifdef __APPLE__
26#include <GLUT/glut.h>
27#else
28#include <GL/glut.h>
29#ifdef FREEGLUT
30#include <GL/freeglut.h>
31#endif
32#endif
33
James Zern061263a2012-05-11 16:00:57 -070034#include "./example_util.h"
35
James Zern91179542011-12-16 19:30:33 -080036#ifdef _MSC_VER
37#define snprintf _snprintf
38#endif
39
Pascal Massimino861a5b72012-05-09 00:41:12 -070040static void Help(void);
41
42// Unfortunate global variables. Gathered into a struct for comfort.
43static struct {
44 int has_animation;
45 int done;
46 int decoding_error;
47 int print_info;
48
skal68f282f2012-11-15 09:15:04 +010049 int canvas_width, canvas_height;
Urvang Joshi6808e692012-07-06 11:35:36 +053050 int loop_count;
skal68f282f2012-11-15 09:15:04 +010051 uint32_t bg_color;
Pascal Massimino861a5b72012-05-09 00:41:12 -070052
53 const char* file_name;
54 WebPData data;
Pascal Massimino861a5b72012-05-09 00:41:12 -070055 WebPDecoderConfig* config;
56 const WebPDecBuffer* pic;
James Zernd7a5ac82012-09-26 23:44:59 -070057 WebPDemuxer* dmux;
58 WebPIterator frameiter;
59} kParams;
Pascal Massimino861a5b72012-05-09 00:41:12 -070060
61static void ClearPreviousPic(void) {
62 WebPFreeDecBuffer((WebPDecBuffer*)kParams.pic);
63 kParams.pic = NULL;
64}
65
Pascal Massimino861a5b72012-05-09 00:41:12 -070066static void ClearParams(void) {
67 ClearPreviousPic();
Urvang Joshif3bab8e2012-06-07 17:19:02 +053068 WebPDataClear(&kParams.data);
James Zernd7a5ac82012-09-26 23:44:59 -070069 WebPDemuxReleaseIterator(&kParams.frameiter);
70 WebPDemuxDelete(kParams.dmux);
71 kParams.dmux = NULL;
Pascal Massimino861a5b72012-05-09 00:41:12 -070072}
Pascal Massimino7937b402011-12-13 14:02:04 -080073
74//------------------------------------------------------------------------------
75// Callbacks
76
77static void HandleKey(unsigned char key, int pos_x, int pos_y) {
78 (void)pos_x;
79 (void)pos_y;
80 if (key == 'q' || key == 'Q' || key == 27 /* Esc */) {
81#ifdef FREEGLUT
82 glutLeaveMainLoop();
83#else
Pascal Massimino861a5b72012-05-09 00:41:12 -070084 ClearParams();
Pascal Massimino7937b402011-12-13 14:02:04 -080085 exit(0);
86#endif
87 } else if (key == 'i') {
Pascal Massimino861a5b72012-05-09 00:41:12 -070088 kParams.print_info = 1 - kParams.print_info;
Pascal Massimino7937b402011-12-13 14:02:04 -080089 glutPostRedisplay();
90 }
91}
92
93static void HandleReshape(int width, int height) {
94 // TODO(skal): proper handling of resize, esp. for large pictures.
95 // + key control of the zoom.
96 glViewport(0, 0, width, height);
97 glMatrixMode(GL_PROJECTION);
98 glLoadIdentity();
99 glMatrixMode(GL_MODELVIEW);
100 glLoadIdentity();
101}
102
103static void PrintString(const char* const text) {
104 void* const font = GLUT_BITMAP_9_BY_15;
105 int i;
106 for (i = 0; text[i]; ++i) {
107 glutBitmapCharacter(font, text[i]);
108 }
109}
110
skal68f282f2012-11-15 09:15:04 +0100111static float GetColorf(uint32_t color, int shift) {
112 return (color >> shift) / 255.f;
113}
114
James Zerna73b8972012-07-09 23:01:52 -0700115static void DrawCheckerBoard(void) {
116 const int square_size = 8; // must be a power of 2
117 int x, y;
118 GLint viewport[4]; // x, y, width, height
119
120 glPushMatrix();
121
122 glGetIntegerv(GL_VIEWPORT, viewport);
123 // shift to integer coordinates with (0,0) being top-left.
124 glOrtho(0, viewport[2], viewport[3], 0, -1, 1);
125 for (y = 0; y < viewport[3]; y += square_size) {
126 for (x = 0; x < viewport[2]; x += square_size) {
127 const GLubyte color = 128 + 64 * (!((x + y) & square_size));
128 glColor3ub(color, color, color);
129 glRecti(x, y, x + square_size, y + square_size);
130 }
131 }
132 glPopMatrix();
133}
134
Pascal Massimino7937b402011-12-13 14:02:04 -0800135static void HandleDisplay(void) {
skal68f282f2012-11-15 09:15:04 +0100136 const WebPDecBuffer* const pic = kParams.pic;
137 const WebPIterator* const iter = &kParams.frameiter;
138 double xoff, yoff;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700139 if (pic == NULL) return;
Pascal Massimino7937b402011-12-13 14:02:04 -0800140 glPushMatrix();
141 glPixelZoom(1, -1);
skal68f282f2012-11-15 09:15:04 +0100142 xoff = 2. * iter->x_offset / kParams.canvas_width;
143 yoff = 2. * iter->y_offset / kParams.canvas_height;
144 glRasterPos2f(-1. + xoff, 1. - yoff);
Pascal Massimino7937b402011-12-13 14:02:04 -0800145 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
Pascal Massimino861a5b72012-05-09 00:41:12 -0700146 glPixelStorei(GL_UNPACK_ROW_LENGTH, pic->u.RGBA.stride / 4);
skal68f282f2012-11-15 09:15:04 +0100147 if (iter->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
148 glClear(GL_COLOR_BUFFER_BIT); // use clear color
149 }
Pascal Massimino861a5b72012-05-09 00:41:12 -0700150 glDrawPixels(pic->width, pic->height,
151 GL_RGBA, GL_UNSIGNED_BYTE,
152 (GLvoid*)pic->u.RGBA.rgba);
153 if (kParams.print_info) {
Pascal Massimino7937b402011-12-13 14:02:04 -0800154 char tmp[32];
155
James Zernb35c07d2012-07-09 22:36:58 -0700156 glColor4f(0.0, 0.0, 0.0, 1.0);
James Zern91179542011-12-16 19:30:33 -0800157 glRasterPos2f(-0.95f, 0.90f);
Pascal Massimino861a5b72012-05-09 00:41:12 -0700158 PrintString(kParams.file_name);
Pascal Massimino7937b402011-12-13 14:02:04 -0800159
Pascal Massimino861a5b72012-05-09 00:41:12 -0700160 snprintf(tmp, sizeof(tmp), "Dimension:%d x %d", pic->width, pic->height);
James Zernb35c07d2012-07-09 22:36:58 -0700161 glColor4f(0.0, 0.0, 0.0, 1.0);
James Zern91179542011-12-16 19:30:33 -0800162 glRasterPos2f(-0.95f, 0.80f);
Pascal Massimino7937b402011-12-13 14:02:04 -0800163 PrintString(tmp);
skal68f282f2012-11-15 09:15:04 +0100164 if (iter->x_offset != 0 || iter->y_offset != 0) {
165 snprintf(tmp, sizeof(tmp), " (offset:%d,%d)",
166 iter->x_offset, iter->y_offset);
167 glRasterPos2f(-0.95f, 0.70f);
168 PrintString(tmp);
169 }
Pascal Massimino7937b402011-12-13 14:02:04 -0800170 }
James Zerna73b8972012-07-09 23:01:52 -0700171 glPopMatrix();
Pascal Massimino7937b402011-12-13 14:02:04 -0800172 glFlush();
173}
174
skal68f282f2012-11-15 09:15:04 +0100175static void StartDisplay(void) {
176 const int width = kParams.canvas_width;
177 const int height = kParams.canvas_height;
Pascal Massimino7937b402011-12-13 14:02:04 -0800178 glutInitDisplayMode(GLUT_RGBA);
skal68f282f2012-11-15 09:15:04 +0100179 glutInitWindowSize(width, height);
Pascal Massimino7937b402011-12-13 14:02:04 -0800180 glutCreateWindow("WebP viewer");
Pascal Massimino7937b402011-12-13 14:02:04 -0800181 glutDisplayFunc(HandleDisplay);
182 glutIdleFunc(NULL);
183 glutKeyboardFunc(HandleKey);
Urvang Joshi08220102012-06-20 11:18:35 -0700184 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
185 glEnable(GL_BLEND);
skal68f282f2012-11-15 09:15:04 +0100186 glClearColor(GetColorf(kParams.bg_color, 0),
187 GetColorf(kParams.bg_color, 8),
188 GetColorf(kParams.bg_color, 16),
189 GetColorf(kParams.bg_color, 24));
190 HandleReshape(width, height);
191 glClear(GL_COLOR_BUFFER_BIT);
192 DrawCheckerBoard();
Pascal Massimino7937b402011-12-13 14:02:04 -0800193}
194
195//------------------------------------------------------------------------------
196// File decoding
197
skal68f282f2012-11-15 09:15:04 +0100198static int Decode(void) { // Fills kParams.frameiter
James Zernd7a5ac82012-09-26 23:44:59 -0700199 const WebPIterator* const iter = &kParams.frameiter;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700200 WebPDecoderConfig* const config = kParams.config;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700201 WebPDecBuffer* const output_buffer = &config->output;
202 int ok = 0;
203
204 ClearPreviousPic();
Pascal Massimino7937b402011-12-13 14:02:04 -0800205 output_buffer->colorspace = MODE_RGBA;
Urvang Joshia00a3da2012-10-31 17:49:15 -0700206 ok = (WebPDecode(iter->fragment.bytes, iter->fragment.size,
James Zernd7a5ac82012-09-26 23:44:59 -0700207 config) == VP8_STATUS_OK);
Pascal Massimino7937b402011-12-13 14:02:04 -0800208 if (!ok) {
Urvang Joshia0770722012-10-30 14:54:46 -0700209 fprintf(stderr, "Decoding of frame #%d failed!\n", iter->frame_num);
Pascal Massimino861a5b72012-05-09 00:41:12 -0700210 } else {
211 kParams.pic = output_buffer;
Pascal Massimino7937b402011-12-13 14:02:04 -0800212 }
213 return ok;
214}
215
Pascal Massimino861a5b72012-05-09 00:41:12 -0700216static void decode_callback(int what) {
217 if (what == 0 && !kParams.done) {
Urvang Joshi6808e692012-07-06 11:35:36 +0530218 int duration = 0;
James Zernd7a5ac82012-09-26 23:44:59 -0700219 if (kParams.dmux != NULL) {
skal68f282f2012-11-15 09:15:04 +0100220 WebPIterator* const iter = &kParams.frameiter;
221 if (!WebPDemuxNextFrame(iter)) {
222 WebPDemuxReleaseIterator(iter);
223 if (WebPDemuxGetFrame(kParams.dmux, 1, iter)) {
224 --kParams.loop_count;
225 kParams.done = (kParams.loop_count == 0);
226 } else {
227 kParams.decoding_error = 1;
228 kParams.done = 1;
229 return;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700230 }
231 }
skal68f282f2012-11-15 09:15:04 +0100232 duration = iter->duration;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700233 }
skal68f282f2012-11-15 09:15:04 +0100234 if (!Decode()) {
235 kParams.decoding_error = 1;
236 kParams.done = 1;
237 } else {
238 glutPostRedisplay();
239 glutTimerFunc(duration, decode_callback, what);
240 }
Pascal Massimino861a5b72012-05-09 00:41:12 -0700241 }
242}
243
Pascal Massimino7937b402011-12-13 14:02:04 -0800244//------------------------------------------------------------------------------
245// Main
246
247static void Help(void) {
248 printf("Usage: vwebp in_file [options]\n\n"
249 "Decodes the WebP image file and visualize it using OpenGL\n"
250 "Options are:\n"
251 " -version .... print version number and exit.\n"
252 " -nofancy ..... don't use the fancy YUV420 upscaler.\n"
253 " -nofilter .... disable in-loop filtering.\n"
254 " -mt .......... use multi-threading\n"
skal68f282f2012-11-15 09:15:04 +0100255 " -info ........ print info.\n"
Pascal Massimino7937b402011-12-13 14:02:04 -0800256 " -h ....... this help message.\n"
257 );
258}
259
260int main(int argc, char *argv[]) {
261 WebPDecoderConfig config;
262 int c;
263
264 if (!WebPInitDecoderConfig(&config)) {
265 fprintf(stderr, "Library version mismatch!\n");
266 return -1;
267 }
Pascal Massimino861a5b72012-05-09 00:41:12 -0700268 kParams.config = &config;
Pascal Massimino7937b402011-12-13 14:02:04 -0800269
270 for (c = 1; c < argc; ++c) {
271 if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
272 Help();
273 return 0;
274 } else if (!strcmp(argv[c], "-nofancy")) {
275 config.options.no_fancy_upsampling = 1;
276 } else if (!strcmp(argv[c], "-nofilter")) {
277 config.options.bypass_filtering = 1;
skal68f282f2012-11-15 09:15:04 +0100278 } else if (!strcmp(argv[c], "-info")) {
279 kParams.print_info = 1;
Pascal Massimino7937b402011-12-13 14:02:04 -0800280 } else if (!strcmp(argv[c], "-version")) {
281 const int version = WebPGetDecoderVersion();
282 printf("%d.%d.%d\n",
283 (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
284 return 0;
285 } else if (!strcmp(argv[c], "-mt")) {
286 config.options.use_threads = 1;
Pascal Massimino7937b402011-12-13 14:02:04 -0800287 } else if (argv[c][0] == '-') {
288 printf("Unknown option '%s'\n", argv[c]);
289 Help();
290 return -1;
291 } else {
Pascal Massimino861a5b72012-05-09 00:41:12 -0700292 kParams.file_name = argv[c];
Pascal Massimino7937b402011-12-13 14:02:04 -0800293 }
294 }
295
James Zern061263a2012-05-11 16:00:57 -0700296 if (kParams.file_name == NULL) {
297 printf("missing input file!!\n");
298 Help();
299 return 0;
300 }
301
302 if (!ExUtilReadFile(kParams.file_name,
Urvang Joshia0770722012-10-30 14:54:46 -0700303 &kParams.data.bytes, &kParams.data.size)) {
James Zern061263a2012-05-11 16:00:57 -0700304 goto Error;
305 }
Pascal Massimino861a5b72012-05-09 00:41:12 -0700306
James Zernd7a5ac82012-09-26 23:44:59 -0700307 kParams.dmux = WebPDemux(&kParams.data);
308 if (kParams.dmux == NULL) {
Pascal Massimino861a5b72012-05-09 00:41:12 -0700309 fprintf(stderr, "Could not create demuxing object!\n");
310 goto Error;
Pascal Massimino7937b402011-12-13 14:02:04 -0800311 }
312
Urvang Joshia00a3da2012-10-31 17:49:15 -0700313 if (WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & FRAGMENTS_FLAG) {
314 fprintf(stderr, "Image fragments are not supported for now!\n");
Pascal Massimino861a5b72012-05-09 00:41:12 -0700315 goto Error;
316 }
skal68f282f2012-11-15 09:15:04 +0100317 kParams.canvas_width = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_WIDTH);
318 kParams.canvas_height = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_HEIGHT);
319 if (kParams.print_info) {
320 printf("Canvas: %d x %d\n", kParams.canvas_width, kParams.canvas_height);
321 }
Pascal Massimino7937b402011-12-13 14:02:04 -0800322
James Zernd7a5ac82012-09-26 23:44:59 -0700323 if (!WebPDemuxGetFrame(kParams.dmux, 1, &kParams.frameiter)) goto Error;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700324
Urvang Joshia0770722012-10-30 14:54:46 -0700325 kParams.has_animation = (kParams.frameiter.num_frames > 1);
James Zernd7a5ac82012-09-26 23:44:59 -0700326 kParams.loop_count = (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT);
skal68f282f2012-11-15 09:15:04 +0100327 kParams.bg_color = WebPDemuxGetI(kParams.dmux, WEBP_FF_BACKGROUND_COLOR);
James Zernd7a5ac82012-09-26 23:44:59 -0700328 printf("VP8X: Found %d images in file (loop count = %d)\n",
Urvang Joshia0770722012-10-30 14:54:46 -0700329 kParams.frameiter.num_frames, kParams.loop_count);
Pascal Massimino861a5b72012-05-09 00:41:12 -0700330
331 // Decode first frame
skal68f282f2012-11-15 09:15:04 +0100332 if (!Decode()) goto Error;
333
334 // Position iterator to last frame. Next call to HandleDisplay will wrap over.
335 // We take this into account by bumping up loop_count.
336 WebPDemuxGetFrame(kParams.dmux, 0, &kParams.frameiter);
337 if (kParams.loop_count) ++kParams.loop_count;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700338
339 // Start display (and timer)
Pascal Massimino7937b402011-12-13 14:02:04 -0800340 glutInit(&argc, argv);
James Zern880fd982012-05-25 14:53:46 -0700341#ifdef FREEGLUT
342 glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
343#endif
skal68f282f2012-11-15 09:15:04 +0100344 StartDisplay();
345
Pascal Massimino861a5b72012-05-09 00:41:12 -0700346 if (kParams.has_animation) glutTimerFunc(0, decode_callback, 0);
347 glutMainLoop();
Pascal Massimino7937b402011-12-13 14:02:04 -0800348
349 // Should only be reached when using FREEGLUT:
Pascal Massimino861a5b72012-05-09 00:41:12 -0700350 ClearParams();
Pascal Massimino7937b402011-12-13 14:02:04 -0800351 return 0;
Pascal Massimino861a5b72012-05-09 00:41:12 -0700352
353 Error:
354 ClearParams();
355 return -1;
Pascal Massimino7937b402011-12-13 14:02:04 -0800356}
357
358//------------------------------------------------------------------------------