1 // Copyright 2012 Google Inc. All Rights Reserved.
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 // -----------------------------------------------------------------------------
8 // simple tool to convert animated GIFs to WebP
10 // Authors: Skal (pascal.massimino@gmail.com)
11 // Urvang (urvang@google.com)
23 #include "webp/encode.h"
25 #include "./example_util.h"
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
33 //------------------------------------------------------------------------------
35 static int transparent_index = -1; // No transparency by default.
37 static void ClearPicture(WebPPicture* const picture, uint32_t color) {
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;
45 static void Remap(const uint8_t* const src, const GifFileType* const gif,
46 uint32_t* dst, int len) {
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;
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);
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;
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);
79 tmp = (uint8_t*)malloc(sub_w * sizeof(*tmp));
80 if (tmp == NULL) goto End;
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 };
87 for (pass = 0; pass < 4; ++pass) {
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);
94 } else { // Non-interlaced image.
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);
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);
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.
116 } else if (color_map == NULL || color_map->Colors == NULL
117 || bgcolor_idx >= color_map->ColorCount) {
118 return 0; // Invalid color map or index.
120 const GifColorType color = color_map->Colors[bgcolor_idx];
121 *bgcolor = (0xff << 24)
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);
139 const char* error_str = (const char*)GifErrorString();
142 if (error_str == NULL) error_str = "Unknown error";
143 fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
146 fprintf(stderr, "GIFLib Error %d: ", gif_error);
148 fprintf(stderr, "\n");
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"
157 static const char* ErrorString(WebPMuxError err) {
158 assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
159 return kErrorMessages[-err];
162 //------------------------------------------------------------------------------
164 static void Help(void) {
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");
174 printf(" -version ............... print version number and exit.\n");
175 printf(" -v ..................... verbose.\n");
176 printf(" -quiet ................. don't print anything.\n");
180 //------------------------------------------------------------------------------
182 int main(int argc, const char *argv[]) {
184 int gif_error = GIF_ERROR;
185 WebPMuxError err = WEBP_MUX_OK;
187 const char *in_file = NULL, *out_file = NULL;
189 GifFileType* gif = NULL;
191 WebPMuxFrameInfo frame;
192 WebPMuxAnimParams anim = { WHITE_COLOR, 0 };
194 int is_first_frame = 1;
200 WebPData webp_data = { NULL, 0 };
201 int stored_icc = 0; // Whether we have already stored an ICC profile.
204 memset(&frame, 0, sizeof(frame));
205 frame.id = WEBP_CHUNK_ANMF;
206 frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
208 if (!WebPConfigInit(&config) || !WebPPictureInit(&picture)) {
209 fprintf(stderr, "Error! Version mismatch!\n");
212 config.lossless = 1; // Use lossless compression by default.
219 for (c = 1; c < argc; ++c) {
220 if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
223 } else if (!strcmp(argv[c], "-o") && c < argc - 1) {
224 out_file = argv[++c];
225 } else if (!strcmp(argv[c], "-lossy")) {
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);
241 } else if (!strcmp(argv[c], "-quiet")) {
243 } else if (!strcmp(argv[c], "-v")) {
245 } else if (argv[c][0] == '-') {
246 fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]);
253 if (!WebPValidateConfig(&config)) {
254 fprintf(stderr, "Error! Invalid configuration.\n");
258 if (in_file == NULL) {
259 fprintf(stderr, "No input file specified!\n");
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);
269 gif = DGifOpenFileName(in_file);
271 if (gif == NULL) goto End;
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;
281 fprintf(stderr, "ERROR: could not create a mux object.\n");
285 // Loop over GIF images
289 if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End;
292 case IMAGE_DESC_RECORD_TYPE: {
293 WebPPicture sub_image;
294 WebPMemoryWriter memory;
296 if (frame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
297 ClearPicture(&picture, anim.bgcolor);
300 if (!DGifGetImageDesc(gif)) goto End;
301 if (!ReadSubImage(gif, &picture, &sub_image)) goto End;
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;
311 sub_image.use_argb = 1;
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);
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",
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,
341 printf("dispose:%d transparent index:%d\n",
342 frame.dispose_method, transparent_index);
344 WebPDataClear(&frame.bitstream);
345 WebPPictureFree(&sub_image);
348 case EXTENSION_RECORD_TYPE: {
350 GifByteType *data = NULL;
351 if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) {
355 case COMMENT_EXT_FUNC_CODE: {
356 break; // Do nothing for now.
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.
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;
370 frame.dispose_method =
371 (dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND
372 : WEBP_MUX_DISPOSE_NONE;
374 transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1;
375 if (is_first_frame) {
376 if (!GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
378 fprintf(stderr, "GIF decode warning: invalid background color "
379 "index. Assuming white background.\n");
381 ClearPicture(&picture, anim.bgcolor);
386 case PLAINTEXT_EXT_FUNC_CODE: {
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.
401 !stored_xmp && !memcmp(data + 1, "XMP DataXMP", 11);
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.
416 WebPData prev_metadata = metadata;
418 if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) {
419 WebPDataClear(&metadata);
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;
427 (uint8_t*)realloc((void*)metadata.bytes,
428 prev_metadata.size + subblock.size);
429 if (metadata.bytes == NULL) {
430 WebPDataClear(&prev_metadata);
433 metadata.size += subblock.size;
434 memcpy((void*)(metadata.bytes + prev_metadata.size),
435 subblock.bytes, subblock.size);
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;
445 // Add metadata chunk.
446 err = WebPMuxSetChunk(mux, fourccs[is_icc], &metadata, 1);
448 printf("%s size: %d\n", features[is_icc], (int)metadata.size);
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]);
469 while (data != NULL) {
470 if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
474 case TERMINATE_RECORD_TYPE: {
480 fprintf(stderr, "Skipping over unknown record type %d\n", type);
488 err = WebPMuxSetAnimationParams(mux, &anim);
489 if (err != WEBP_MUX_OK) {
490 fprintf(stderr, "ERROR (%s): Could not set animation parameters.\n",
495 err = WebPMuxAssemble(mux, &webp_data);
496 if (err != WEBP_MUX_OK) {
497 fprintf(stderr, "ERROR (%s) assembling the WebP file.\n", ErrorString(err));
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);
506 printf("Saved output file: %s\n", out_file);
510 printf("Nothing written; use -o flag to save the result.\n");
519 WebPDataClear(&webp_data);
521 WebPPictureFree(&picture);
522 if (out != NULL && out_file != NULL) fclose(out);
524 if (gif_error != GIF_OK) {
525 DisplayGifError(gif, gif_error);
534 //------------------------------------------------------------------------------