New comit of SDL2
[supertux.git] / src / SDL2 / external / libwebp-0.3.0 / examples / gif2webp.c
1 // Copyright 2012 Google Inc. All Rights Reserved.
2 //
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 tool to convert animated GIFs to WebP
9 //
10 // Authors: Skal (pascal.massimino@gmail.com)
11 //          Urvang (urvang@google.com)
12
13 #include <assert.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <gif_lib.h>
23 #include "webp/encode.h"
24 #include "webp/mux.h"
25 #include "./example_util.h"
26
27 #define GIF_TRANSPARENT_MASK 0x01
28 #define GIF_DISPOSE_MASK     0x07
29 #define GIF_DISPOSE_SHIFT    2
30 #define TRANSPARENT_COLOR    0x00ffffff
31 #define WHITE_COLOR          0xffffffff
32
33 //------------------------------------------------------------------------------
34
35 static int transparent_index = -1;  // No transparency by default.
36
37 static void ClearPicture(WebPPicture* const picture, uint32_t color) {
38   int x, y;
39   for (y = 0; y < picture->height; ++y) {
40     uint32_t* const dst = picture->argb + y * picture->argb_stride;
41     for (x = 0; x < picture->width; ++x) dst[x] = color;
42   }
43 }
44
45 static void Remap(const uint8_t* const src, const GifFileType* const gif,
46                   uint32_t* dst, int len) {
47   int i;
48   const GifColorType* colors;
49   const ColorMapObject* const cmap =
50       gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
51   if (cmap == NULL) return;
52   colors = cmap->Colors;
53
54   for (i = 0; i < len; ++i) {
55     const GifColorType c = colors[src[i]];
56     dst[i] = (src[i] == transparent_index) ? TRANSPARENT_COLOR
57            : c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24);
58   }
59 }
60
61 static int ReadSubImage(GifFileType* gif, WebPPicture* pic, WebPPicture* view) {
62   const GifImageDesc image_desc = gif->Image;
63   const int offset_x = image_desc.Left;
64   const int offset_y = image_desc.Top;
65   const int sub_w = image_desc.Width;
66   const int sub_h = image_desc.Height;
67   uint32_t* dst = NULL;
68   uint8_t* tmp = NULL;
69   int ok = 0;
70
71   // Use a view for the sub-picture:
72   if (!WebPPictureView(pic, offset_x, offset_y, sub_w, sub_h, view)) {
73     fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n",
74             sub_w, sub_h, offset_x, offset_y);
75     goto End;
76   }
77   dst = view->argb;
78
79   tmp = (uint8_t*)malloc(sub_w * sizeof(*tmp));
80   if (tmp == NULL) goto End;
81
82   if (image_desc.Interlace) {  // Interlaced image.
83     // We need 4 passes, with the following offsets and jumps.
84     const int interlace_offsets[] = { 0, 4, 2, 1 };
85     const int interlace_jumps[]   = { 8, 8, 4, 2 };
86     int pass;
87     for (pass = 0; pass < 4; ++pass) {
88       int y;
89       for (y = interlace_offsets[pass]; y < sub_h; y += interlace_jumps[pass]) {
90         if (DGifGetLine(gif, tmp, sub_w) == GIF_ERROR) goto End;
91         Remap(tmp, gif, dst + y * view->argb_stride, sub_w);
92       }
93     }
94   } else {  // Non-interlaced image.
95     int y;
96     for (y = 0; y < sub_h; ++y) {
97       if (DGifGetLine(gif, tmp, sub_w) == GIF_ERROR) goto End;
98       Remap(tmp, gif, dst + y * view->argb_stride, sub_w);
99     }
100   }
101   // re-align the view with even offset (and adjust dimensions if needed).
102   WebPPictureView(pic, offset_x & ~1, offset_y & ~1,
103                   sub_w + (offset_x & 1), sub_h + (offset_y & 1), view);
104   ok = 1;
105
106  End:
107   free(tmp);
108   return ok;
109 }
110
111 static int GetBackgroundColor(const ColorMapObject* const color_map,
112                               GifWord bgcolor_idx, uint32_t* const bgcolor) {
113   if (transparent_index != -1 && bgcolor_idx == transparent_index) {
114     *bgcolor = TRANSPARENT_COLOR;  // Special case.
115     return 1;
116   } else if (color_map == NULL || color_map->Colors == NULL
117              || bgcolor_idx >= color_map->ColorCount) {
118     return 0;  // Invalid color map or index.
119   } else {
120     const GifColorType color = color_map->Colors[bgcolor_idx];
121     *bgcolor = (0xff        << 24)
122              | (color.Red   << 16)
123              | (color.Green <<  8)
124              | (color.Blue  <<  0);
125     return 1;
126   }
127 }
128
129 static void DisplayGifError(const GifFileType* const gif, int gif_error) {
130   // GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
131   // libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
132 #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && \
133         ((GIFLIB_MAJOR == 4 && GIFLIB_MINOR >= 2) || GIFLIB_MAJOR > 4)
134 #if GIFLIB_MAJOR >= 5
135     // Static string actually, hence the const char* cast.
136     const char* error_str = (const char*)GifErrorString(
137         (gif == NULL) ? gif_error : gif->Error);
138 #else
139     const char* error_str = (const char*)GifErrorString();
140     (void)gif;
141 #endif
142     if (error_str == NULL) error_str = "Unknown error";
143     fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
144 #else
145     (void)gif;
146     fprintf(stderr, "GIFLib Error %d: ", gif_error);
147     PrintGifError();
148     fprintf(stderr, "\n");
149 #endif
150 }
151
152 static const char* const kErrorMessages[] = {
153   "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
154   "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
155 };
156
157 static const char* ErrorString(WebPMuxError err) {
158   assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
159   return kErrorMessages[-err];
160 }
161
162 //------------------------------------------------------------------------------
163
164 static void Help(void) {
165   printf("Usage:\n");
166   printf(" gif2webp [options] gif_file -o webp_file\n");
167   printf("options:\n");
168   printf("  -h / -help  ............ this help\n");
169   printf("  -lossy ................. Encode image using lossy compression.\n");
170   printf("  -q <float> ............. quality factor (0:small..100:big)\n");
171   printf("  -m <int> ............... compression method (0=fast, 6=slowest)\n");
172   printf("  -f <int> ............... filter strength (0=off..100)\n");
173   printf("\n");
174   printf("  -version ............... print version number and exit.\n");
175   printf("  -v ..................... verbose.\n");
176   printf("  -quiet ................. don't print anything.\n");
177   printf("\n");
178 }
179
180 //------------------------------------------------------------------------------
181
182 int main(int argc, const char *argv[]) {
183   int verbose = 0;
184   int gif_error = GIF_ERROR;
185   WebPMuxError err = WEBP_MUX_OK;
186   int ok = 0;
187   const char *in_file = NULL, *out_file = NULL;
188   FILE* out = NULL;
189   GifFileType* gif = NULL;
190   WebPPicture picture;
191   WebPMuxFrameInfo frame;
192   WebPMuxAnimParams anim = { WHITE_COLOR, 0 };
193
194   int is_first_frame = 1;
195   int done;
196   int c;
197   int quiet = 0;
198   WebPConfig config;
199   WebPMux* mux = NULL;
200   WebPData webp_data = { NULL, 0 };
201   int stored_icc = 0;  // Whether we have already stored an ICC profile.
202   int stored_xmp = 0;
203
204   memset(&frame, 0, sizeof(frame));
205   frame.id = WEBP_CHUNK_ANMF;
206   frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
207
208   if (!WebPConfigInit(&config) || !WebPPictureInit(&picture)) {
209     fprintf(stderr, "Error! Version mismatch!\n");
210     return -1;
211   }
212   config.lossless = 1;  // Use lossless compression by default.
213
214   if (argc == 1) {
215     Help();
216     return 0;
217   }
218
219   for (c = 1; c < argc; ++c) {
220     if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
221       Help();
222       return 0;
223     } else if (!strcmp(argv[c], "-o") && c < argc - 1) {
224       out_file = argv[++c];
225     } else if (!strcmp(argv[c], "-lossy")) {
226       config.lossless = 0;
227     } else if (!strcmp(argv[c], "-q") && c < argc - 1) {
228       config.quality = (float)strtod(argv[++c], NULL);
229     } else if (!strcmp(argv[c], "-m") && c < argc - 1) {
230       config.method = strtol(argv[++c], NULL, 0);
231     } else if (!strcmp(argv[c], "-f") && c < argc - 1) {
232       config.filter_strength = strtol(argv[++c], NULL, 0);
233     } else if (!strcmp(argv[c], "-version")) {
234       const int enc_version = WebPGetEncoderVersion();
235       const int mux_version = WebPGetMuxVersion();
236       printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n",
237              (enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff,
238              enc_version & 0xff, (mux_version >> 16) & 0xff,
239              (mux_version >> 8) & 0xff, mux_version & 0xff);
240       return 0;
241     } else if (!strcmp(argv[c], "-quiet")) {
242       quiet = 1;
243     } else if (!strcmp(argv[c], "-v")) {
244       verbose = 1;
245     } else if (argv[c][0] == '-') {
246       fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]);
247       Help();
248       return -1;
249     } else {
250       in_file = argv[c];
251     }
252   }
253   if (!WebPValidateConfig(&config)) {
254     fprintf(stderr, "Error! Invalid configuration.\n");
255     goto End;
256   }
257
258   if (in_file == NULL) {
259     fprintf(stderr, "No input file specified!\n");
260     Help();
261     goto End;
262   }
263
264   // Start the decoder object
265 #if defined(GIFLIB_MAJOR) && (GIFLIB_MAJOR >= 5)
266   // There was an API change in version 5.0.0.
267   gif = DGifOpenFileName(in_file, &gif_error);
268 #else
269   gif = DGifOpenFileName(in_file);
270 #endif
271   if (gif == NULL) goto End;
272
273   // Allocate picture buffer
274   picture.width = gif->SWidth;
275   picture.height = gif->SHeight;
276   picture.use_argb = 1;
277     if (!WebPPictureAlloc(&picture)) goto End;
278
279   mux = WebPMuxNew();
280   if (mux == NULL) {
281     fprintf(stderr, "ERROR: could not create a mux object.\n");
282     goto End;
283   }
284
285   // Loop over GIF images
286   done = 0;
287   do {
288     GifRecordType type;
289     if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End;
290
291     switch (type) {
292       case IMAGE_DESC_RECORD_TYPE: {
293         WebPPicture sub_image;
294         WebPMemoryWriter memory;
295
296         if (frame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
297           ClearPicture(&picture, anim.bgcolor);
298         }
299
300         if (!DGifGetImageDesc(gif)) goto End;
301         if (!ReadSubImage(gif, &picture, &sub_image)) goto End;
302
303         if (!config.lossless) {
304           // We need to call BGRA variant because of the way we do Remap(). Note
305           // that 'sub_image' will no longer be a view and own some memory.
306           WebPPictureImportBGRA(
307               &sub_image, (uint8_t*)sub_image.argb,
308               sub_image.argb_stride * sizeof(*sub_image.argb));
309           sub_image.use_argb = 0;
310         } else {
311           sub_image.use_argb = 1;
312         }
313
314         sub_image.writer = WebPMemoryWrite;
315         sub_image.custom_ptr = &memory;
316         WebPMemoryWriterInit(&memory);
317         if (!WebPEncode(&config, &sub_image)) {
318           fprintf(stderr, "Error! Cannot encode picture as WebP\n");
319           fprintf(stderr, "Error code: %d\n", sub_image.error_code);
320           goto End;
321         }
322
323         // Now we have all the info about the frame, as a Graphic Control
324         // Extension Block always appears before the Image Descriptor Block.
325         // So add the frame to mux.
326         frame.x_offset = gif->Image.Left & ~1;
327         frame.y_offset = gif->Image.Top & ~1;
328         frame.bitstream.bytes = memory.mem;
329         frame.bitstream.size = memory.size;
330         err = WebPMuxPushFrame(mux, &frame, 1);
331         if (err != WEBP_MUX_OK) {
332           fprintf(stderr, "ERROR (%s): Could not add animation frame.\n",
333                   ErrorString(err));
334           goto End;
335         }
336         if (verbose) {
337           printf("Added frame %dx%d (offset:%d,%d duration:%d) ",
338                  sub_image.width, sub_image.height,
339                  frame.x_offset, frame.y_offset,
340                  frame.duration);
341           printf("dispose:%d transparent index:%d\n",
342                  frame.dispose_method, transparent_index);
343         }
344         WebPDataClear(&frame.bitstream);
345         WebPPictureFree(&sub_image);
346         break;
347       }
348       case EXTENSION_RECORD_TYPE: {
349         int extension;
350         GifByteType *data = NULL;
351         if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) {
352           goto End;
353         }
354         switch (extension) {
355           case COMMENT_EXT_FUNC_CODE: {
356             break;  // Do nothing for now.
357           }
358           case GRAPHICS_EXT_FUNC_CODE: {
359             const int flags = data[1];
360             const int dispose = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK;
361             const int delay = data[2] | (data[3] << 8);  // In 10 ms units.
362             if (data[0] != 4) goto End;
363             frame.duration = delay * 10;  // Duration is in 1 ms units for WebP.
364             if (dispose == 3) {
365               fprintf(stderr, "WARNING: GIF_DISPOSE_RESTORE not supported.");
366               // failsafe. TODO(urvang): emulate the correct behaviour by
367               // recoding the whole frame.
368               frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
369             } else {
370               frame.dispose_method =
371                   (dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND
372                                  : WEBP_MUX_DISPOSE_NONE;
373             }
374             transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1;
375             if (is_first_frame) {
376               if (!GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
377                                       &anim.bgcolor)) {
378                 fprintf(stderr, "GIF decode warning: invalid background color "
379                         "index. Assuming white background.\n");
380               }
381               ClearPicture(&picture, anim.bgcolor);
382               is_first_frame = 0;
383             }
384             break;
385           }
386           case PLAINTEXT_EXT_FUNC_CODE: {
387             break;
388           }
389           case APPLICATION_EXT_FUNC_CODE: {
390             if (data[0] != 11) break;    // Chunk is too short
391             if (!memcmp(data + 1, "NETSCAPE2.0", 11)) {
392               // Recognize and parse Netscape2.0 NAB extension for loop count.
393               if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
394               if (data == NULL) goto End;  // Loop count sub-block missing.
395               if (data[0] != 3 && data[1] != 1) break;   // wrong size/marker
396               anim.loop_count = data[2] | (data[3] << 8);
397               if (verbose) printf("Loop count: %d\n", anim.loop_count);
398             } else {  // An extension containing metadata.
399               // We only store the first encountered chunk of each type.
400               const int is_xmp =
401                   !stored_xmp && !memcmp(data + 1, "XMP DataXMP", 11);
402               const int is_icc =
403                   !stored_icc && !memcmp(data + 1, "ICCRGBG1012", 11);
404               if (is_xmp || is_icc) {
405                 const char* const fourccs[2] = { "XMP " , "ICCP" };
406                 const char* const features[2] = { "XMP" , "ICC" };
407                 WebPData metadata = { NULL, 0 };
408                 // Construct metadata from sub-blocks.
409                 // Usual case (including ICC profile): In each sub-block, the
410                 // first byte specifies its size in bytes (0 to 255) and the
411                 // rest of the bytes contain the data.
412                 // Special case for XMP data: In each sub-block, the first byte
413                 // is also part of the XMP payload. XMP in GIF also has a 257
414                 // byte padding data. See the XMP specification for details.
415                 while (1) {
416                   WebPData prev_metadata = metadata;
417                   WebPData subblock;
418                   if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) {
419                     WebPDataClear(&metadata);
420                     goto End;
421                   }
422                   if (data == NULL) break;  // Finished.
423                   subblock.size = is_xmp ? data[0] + 1 : data[0];
424                   assert(subblock.size > 0);
425                   subblock.bytes = is_xmp ? data : data + 1;
426                   metadata.bytes =
427                       (uint8_t*)realloc((void*)metadata.bytes,
428                                         prev_metadata.size + subblock.size);
429                   if (metadata.bytes == NULL) {
430                     WebPDataClear(&prev_metadata);
431                     goto End;
432                   }
433                   metadata.size += subblock.size;
434                   memcpy((void*)(metadata.bytes + prev_metadata.size),
435                          subblock.bytes, subblock.size);
436                 }
437                 if (is_xmp) {
438                   // XMP padding data is 0x01, 0xff, 0xfe ... 0x01, 0x00.
439                   const size_t xmp_pading_size = 257;
440                   if (metadata.size > xmp_pading_size) {
441                     metadata.size -= xmp_pading_size;
442                   }
443                 }
444
445                 // Add metadata chunk.
446                 err = WebPMuxSetChunk(mux, fourccs[is_icc], &metadata, 1);
447                 if (verbose) {
448                   printf("%s size: %d\n", features[is_icc], (int)metadata.size);
449                 }
450                 WebPDataClear(&metadata);
451                 if (err != WEBP_MUX_OK) {
452                   fprintf(stderr, "ERROR (%s): Could not set %s chunk.\n",
453                           ErrorString(err), features[is_icc]);
454                   goto End;
455                 }
456                 if (is_icc) {
457                   stored_icc = 1;
458                 } else if (is_xmp) {
459                   stored_xmp = 1;
460                 }
461               }
462             }
463             break;
464           }
465           default: {
466             break;  // skip
467           }
468         }
469         while (data != NULL) {
470           if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
471         }
472         break;
473       }
474       case TERMINATE_RECORD_TYPE: {
475         done = 1;
476         break;
477       }
478       default: {
479         if (verbose) {
480           fprintf(stderr, "Skipping over unknown record type %d\n", type);
481         }
482         break;
483       }
484     }
485   } while (!done);
486
487   // Finish muxing
488   err = WebPMuxSetAnimationParams(mux, &anim);
489   if (err != WEBP_MUX_OK) {
490     fprintf(stderr, "ERROR (%s): Could not set animation parameters.\n",
491             ErrorString(err));
492     goto End;
493   }
494
495   err = WebPMuxAssemble(mux, &webp_data);
496   if (err != WEBP_MUX_OK) {
497     fprintf(stderr, "ERROR (%s) assembling the WebP file.\n", ErrorString(err));
498     goto End;
499   }
500   if (out_file != NULL) {
501     if (!ExUtilWriteFile(out_file, webp_data.bytes, webp_data.size)) {
502       fprintf(stderr, "Error writing output file: %s\n", out_file);
503       goto End;
504     }
505     if (!quiet) {
506       printf("Saved output file: %s\n", out_file);
507     }
508   } else {
509     if (!quiet) {
510       printf("Nothing written; use -o flag to save the result.\n");
511     }
512   }
513
514   // All OK.
515   ok = 1;
516   gif_error = GIF_OK;
517
518  End:
519   WebPDataClear(&webp_data);
520   WebPMuxDelete(mux);
521   WebPPictureFree(&picture);
522   if (out != NULL && out_file != NULL) fclose(out);
523
524   if (gif_error != GIF_OK) {
525     DisplayGifError(gif, gif_error);
526   }
527   if (gif != NULL) {
528     DGifCloseFile(gif);
529   }
530
531   return !ok;
532 }
533
534 //------------------------------------------------------------------------------