From 11b2721258dc79f663a2f00f67e0bd19b5e3b750 Mon Sep 17 00:00:00 2001 From: Urvang Joshi Date: Mon, 5 Nov 2012 17:35:36 -0800 Subject: [PATCH] Write a GIF to WebP converter based on libgif. Change-Id: Ic64bf5307996a788ec61caf59a99ab37b4b1ae96 --- examples/gif2webp.c | 420 ++++++++++++++++++++++++++++++++++++++++++++ makefile.unix | 12 +- 2 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 examples/gif2webp.c diff --git a/examples/gif2webp.c b/examples/gif2webp.c new file mode 100644 index 00000000..2d82cbc2 --- /dev/null +++ b/examples/gif2webp.c @@ -0,0 +1,420 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// This code is licensed under the same terms as WebM: +// Software License Agreement: http://www.webmproject.org/license/software/ +// Additional IP Rights Grant: http://www.webmproject.org/license/additional/ +// ----------------------------------------------------------------------------- +// +// simple tool to convert animated GIFs to WebP +// +// Authors: Skal (pascal.massimino@gmail.com) +// Urvang (urvang@google.com) + +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +// TODO(later): Use WEBP_USE_GIF flag here, determined at config time. + +#include +#include "webp/encode.h" +#include "webp/mux.h" +#include "./example_util.h" + +#define GIF_TRANSPARENT_MASK 0x01 +#define GIF_DISPOSE_MASK 0x07 +#define GIF_DISPOSE_SHIFT 2 +#define TRANSPARENT_COLOR 0x00ffffff +#define WHITE_COLOR 0xffffffff + +//------------------------------------------------------------------------------ + +static int transparent_index = -1; // No transparency by default. + +static int MyReader(GifFileType* gif, GifByteType* buffer, int length) { + FILE* const file = (FILE*)gif->UserData; + return fread(buffer, 1, length, file); +} + +static void Remap(const uint8_t* const src, GifFileType* const gif, + uint32_t* dst, int len) { + int i; + const GifColorType* colors; + const ColorMapObject* const cmap = + gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; + if (cmap == NULL) return; + colors = cmap->Colors; + + for (i = 0; i < len; ++i) { + const GifColorType c = colors[src[i]]; + dst[i] = (src[i] == transparent_index) ? TRANSPARENT_COLOR + : c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24); + } +} + +static int ReadSubImage(GifFileType* gif, WebPPicture* pic, WebPPicture* view) { + const GifImageDesc image_desc = gif->Image; + const int offset_x = image_desc.Left; + const int offset_y = image_desc.Top; + const int sub_w = image_desc.Width; + const int sub_h = image_desc.Height; + uint32_t* dst = NULL; + uint8_t* tmp = NULL; + int ok = 0; + + // Use a view for the sub-picture: + if (!WebPPictureView(pic, offset_x, offset_y, sub_w, sub_h, view)) { + fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", + sub_w, sub_h, offset_x, offset_y); + goto End; + } + dst = view->argb; + + tmp = (uint8_t*)malloc(sub_w * sizeof(*tmp)); + if (tmp == NULL) goto End; + + if (image_desc.Interlace) { // Interlaced image. + // We need 4 passes, with the following offsets and jumps. + const int interlace_offsets[] = { 0, 4, 2, 1 }; + const int interlace_jumps[] = { 8, 8, 4, 2 }; + int pass; + for (pass = 0; pass < 4; ++pass) { + int y; + for (y = interlace_offsets[pass]; y < sub_h; y += interlace_jumps[pass]) { + if (DGifGetLine(gif, tmp, sub_w) == GIF_ERROR) goto End; + Remap(tmp, gif, dst + y * view->argb_stride, sub_w); + } + } + } else { // Non-interlaced image. + int y; + for (y = 0; y < sub_h; ++y) { + if (DGifGetLine(gif, tmp, sub_w) == GIF_ERROR) goto End; + Remap(tmp, gif, dst + y * view->argb_stride, sub_w); + } + } + ok = 1; + + End: + free(tmp); + return ok; +} + +static int GetColorFromIndex(const ColorMapObject* const color_map, GifWord idx, + uint32_t* const argb) { + assert(color_map != NULL && color_map->Colors != NULL); + if (idx >= color_map->ColorCount) { + return 0; // Invalid index. + } else { + const GifColorType color = color_map->Colors[idx]; + *argb = (0xff << 24) + | (color.Red << 16) + | (color.Green << 8) + | (color.Blue << 0); + return 1; + } +} + +static const char* const kErrorMessages[] = { + "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA", + "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA" +}; + +static const char* ErrorString(WebPMuxError err) { + assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA); + return kErrorMessages[-err]; +} + +//------------------------------------------------------------------------------ + +static void Help(void) { + printf("Usage:\n"); + printf(" gif2webp [options] gif_file -o webp_file\n"); + printf("options:\n"); + printf(" -h / -help ............ this help\n"); + printf(" -lossy ................. Encode image using lossy compression.\n"); + printf(" -q ............. quality factor (0:small..100:big)\n"); + printf(" -m ............... compression method (0=fast, 6=slowest)\n"); + printf(" -f ............... filter strength (0=off..100)\n"); + printf("\n"); + printf(" -version ............... print version number and exit.\n"); + printf(" -v ..................... verbose.\n"); + printf(" -quiet ................. don't print anything.\n"); + printf("\n"); +} + +//------------------------------------------------------------------------------ + +int main(int argc, const char *argv[]) { + int verbose = 0; + int gif_error = -1; + WebPMuxError err = WEBP_MUX_OK; + int ok = 0; + const char *in_file = NULL, *out_file = NULL; + FILE* out = NULL; + FILE* in = NULL; + GifFileType* gif = NULL; + WebPPicture picture; + WebPPicture view; + WebPMemoryWriter memory; + WebPMuxFrameInfo frame = { + {NULL, 0}, 0, 0, 100, WEBP_CHUNK_ANMF, WEBP_MUX_DISPOSE_NONE + }; + WebPMuxAnimParams anim = { WHITE_COLOR, 0 }; + int done; + int c; + int quiet = 0; + WebPConfig config; + WebPMux* mux = NULL; + WebPData webp_data = { NULL, 0 }; + + if (!WebPConfigInit(&config) || !WebPPictureInit(&picture)) { + fprintf(stderr, "Error! Version mismatch!\n"); + return -1; + } + config.lossless = 1; // Use lossless compression by default. + + if (argc == 1) { + Help(); + return 0; + } + + for (c = 1; c < argc; ++c) { + if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { + Help(); + return 0; + } else if (!strcmp(argv[c], "-o") && c < argc - 1) { + out_file = argv[++c]; + } else if (!strcmp(argv[c], "-lossy")) { + config.lossless = 0; + } else if (!strcmp(argv[c], "-q") && c < argc - 1) { + config.quality = (float)strtod(argv[++c], NULL); + } else if (!strcmp(argv[c], "-m") && c < argc - 1) { + config.method = strtol(argv[++c], NULL, 0); + } else if (!strcmp(argv[c], "-f") && c < argc - 1) { + config.filter_strength = strtol(argv[++c], NULL, 0); + } else if (!strcmp(argv[c], "-version")) { + const int version = WebPGetEncoderVersion(); + printf("%d.%d.%d\n", + (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); + return 0; + } else if (!strcmp(argv[c], "-quiet")) { + quiet = 1; + } else if (!strcmp(argv[c], "-v")) { + verbose = 1; + } else if (argv[c][0] == '-') { + fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]); + Help(); + return -1; + } else { + in_file = argv[c]; + } + } + if (!WebPValidateConfig(&config)) { + fprintf(stderr, "Error! Invalid configuration.\n"); + goto End; + } + + if (in_file == NULL) { + fprintf(stderr, "No input file specified!\n"); + Help(); + goto End; + } + + in = fopen(in_file, "rb"); + if (in == NULL) { + fprintf(stderr, "Can't open input file '%s'\n", in_file); + goto End; + } + + // Start the decoder object + gif = DGifOpen(in, MyReader); + if (gif == NULL) goto End; + + // Allocate picture buffer + picture.width = gif->SWidth; + picture.height = gif->SHeight; + picture.use_argb = 1; + picture.writer = WebPMemoryWrite; + picture.custom_ptr = &memory; + if (!WebPPictureAlloc(&picture)) goto End; + if (gif->SColorMap != NULL && + !GetColorFromIndex(gif->SColorMap, gif->SBackGroundColor, + &anim.bgcolor)) { + fprintf(stderr, "GIF decode error: invalid background color index.\n"); + goto End; + } + + mux = WebPMuxNew(); + if (mux == NULL) { + fprintf(stderr, "ERROR: could not create a mux object.\n"); + goto End; + } + + // Loop over GIF images + done = 0; + do { + GifRecordType type; + if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End; + + switch (type) { + case IMAGE_DESC_RECORD_TYPE: { + if (!DGifGetImageDesc(gif)) goto End; + if (!ReadSubImage(gif, &picture, &view)) goto End; + + frame.x_offset = gif->Image.Left; + frame.y_offset = gif->Image.Top; + WebPMemoryWriterInit(&memory); + if (!config.lossless) { + // We need to call BGRA variant because of the way we do Remap(). + // TODO(later): This works for little-endian only due to uint32_t to + // uint8_t conversion. Make it work for big-endian too. + WebPPictureImportBGRA(&view, (uint8_t*)view.argb, + view.argb_stride * sizeof(*view.argb)); + view.use_argb = 0; + } else { + view.use_argb = 1; + } + if (!WebPEncode(&config, &view)) { + fprintf(stderr, "Error! Cannot encode picture as WebP\n"); + fprintf(stderr, "Error code: %d\n", view.error_code); + goto End; + } + frame.bitstream.bytes = memory.mem; + frame.bitstream.size = memory.size; + + // Now we have all the info about the frame, as a Graphic Control + // Extension Block always appears before the Image Descriptor Block. + // So add the frame to mux. + err = WebPMuxPushFrame(mux, &frame, 1); + if (err != WEBP_MUX_OK) { + fprintf(stderr, "ERROR (%s): Could not add animation frame.\n", + ErrorString(err)); + goto End; + } + WebPDataClear(&frame.bitstream); + break; + } + case EXTENSION_RECORD_TYPE: { + int extension; + GifByteType *data = NULL; + if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) { + goto End; + } + switch (extension) { + case COMMENT_EXT_FUNC_CODE: { + break; // Do nothing for now. + } + case GRAPHICS_EXT_FUNC_CODE: { + const int flags = data[1]; + const int dispose = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK; + const int delay = data[2] | (data[3] << 8); // In 10 ms units. + if (data[0] != 4) goto End; + frame.duration = delay * 10; // Duration is in 1 ms units for WebP. + frame.dispose_method = (dispose == 0) ? + WEBP_MUX_DISPOSE_NONE : WEBP_MUX_DISPOSE_BACKGROUND; + transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1; + break; + } + case PLAINTEXT_EXT_FUNC_CODE: { + break; + } + case APPLICATION_EXT_FUNC_CODE: { + if (data[0] != 11) break; // Chunk is too short + if (!memcmp(data + 1, "NETSCAPE2.0", 11)) { + // Recognize and parse Netscape2.0 NAB extension for loop count. + if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End; + if (data == NULL) goto End; // Loop count sub-block missing. + if (data[0] != 3 && data[1] != 1) break; // wrong size/marker + anim.loop_count = data[2] | (data[3] << 8); + if (verbose) fprintf(stderr, "Loop count: %d\n", anim.loop_count); + } else if (!memcmp(data + 1, "XMP dataXMP", 11)) { + // Read XMP metadata. + WebPData xmp; + if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End; + if (data == NULL) goto End; + xmp.bytes = (uint8_t*)data; + xmp.size = data[0] + 1; + WebPMuxSetChunk(mux, "XMP ", &xmp, 1); + if (verbose) fprintf(stderr, "XMP size: %zu\n", xmp.size); + } else if (!memcmp(data + 1, "ICCRGBG1012", 11)) { + // Read ICC profile. + WebPData icc; + if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End; + if (data == NULL) goto End; + icc.bytes = (uint8_t*)data; + icc.size = data[0] + 1; + WebPMuxSetChunk(mux, "ICCP", &icc, 1); + if (verbose) fprintf(stderr, "ICC size: %zu\n", icc.size); + } + break; + } + default: { + break; // skip + } + } + do { + if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End; + } while (data != NULL); + break; + } + case TERMINATE_RECORD_TYPE: { + done = 1; + break; + } + default: { + if (verbose) { + fprintf(stderr, "Skipping over unknown record type %d\n", type); + } + break; + } + } + } while (!done); + + // Finish muxing + err = WebPMuxSetAnimationParams(mux, &anim); + if (err != WEBP_MUX_OK) { + fprintf(stderr, "ERROR (%s): Could not set animation parameters.\n", + ErrorString(err)); + goto End; + } + + err = WebPMuxAssemble(mux, &webp_data); + if (err != WEBP_MUX_OK) { + fprintf(stderr, "ERROR (%s) assembling the WebP file.\n", ErrorString(err)); + goto End; + } + if (out_file != NULL) { + if (!ExUtilWriteFile(out_file, webp_data.bytes, webp_data.size)) { + fprintf(stderr, "Error writing output file: %s\n", out_file); + goto End; + } + if (!quiet) fprintf(stderr, "Saved output file: %s\n", out_file); + } + + // All OK. + ok = 1; + gif_error = 0; + + End: + WebPDataClear(&webp_data); + WebPMuxDelete(mux); + WebPPictureFree(&picture); + if (out != NULL && out_file != NULL) fclose(out); + if (in != NULL && in_file != NULL) fclose(in); + + if (gif_error != 0) { + PrintGifError(); + } + if (gif != NULL) { + DGifCloseFile(gif); + } + + return !ok; +} + +//------------------------------------------------------------------------------ diff --git a/makefile.unix b/makefile.unix index 77e4a896..baf0c437 100644 --- a/makefile.unix +++ b/makefile.unix @@ -25,17 +25,21 @@ ifeq ($(strip $(shell uname)), Darwin) else GL_LIBS = -lglut -lGL endif +GIF_LIBS = -lgif + # To install libraries on Mac OS X: # 1. Install MacPorts (http://www.macports.org/install.php) # 2. Run "sudo port install jpeg" # 3. Run "sudo port install libpng" # 4. Run "sudo port install tiff" +# 5. Run "sudo port install giflib" # To install libraries on Linux: # 1. Run "sudo apt-get install libjpeg62-dev" # 2. Run "sudo apt-get install libpng12-dev" # 3. Run "sudo apt-get install libtiff4-dev" +# 4. Run "sudo apt-get install libgif-dev" # Uncomment for build for 32bit platform # Alternatively, you can just use the command @@ -169,7 +173,8 @@ OUT_EXAMPLES = examples/cwebp examples/dwebp OUTPUT = $(OUT_LIBS) $(OUT_EXAMPLES) ifeq ($(MAKECMDGOALS),clean) - OUTPUT += examples/vwebp examples/webpmux src/mux/libwebpmux.a + OUTPUT += examples/gif2webp examples/vwebp examples/webpmux + OUTPUT += src/mux/libwebpmux.a endif all: ex @@ -188,16 +193,19 @@ ex: $(OUT_EXAMPLES) examples/cwebp: examples/cwebp.o examples/dwebp: examples/dwebp.o +examples/gif2webp: examples/gif2webp.o examples/vwebp: examples/vwebp.o examples/webpmux: examples/webpmux.o examples/cwebp: src/libwebp.a examples/dwebp: examples/libexample_util.a src/libwebp.a +examples/gif2webp: examples/libexample_util.a src/mux/libwebpmux.a src/libwebp.a +examples/gif2webp: EXTRA_LIBS += $(GIF_LIBS) examples/vwebp: examples/libexample_util.a src/mux/libwebpmux.a src/libwebp.a examples/vwebp: EXTRA_LIBS += $(GL_LIBS) examples/webpmux: examples/libexample_util.a src/mux/libwebpmux.a src/libwebp.a -$(OUT_EXAMPLES) examples/vwebp examples/webpmux: +$(OUT_EXAMPLES) examples/gif2webp examples/vwebp examples/webpmux: $(CC) -o $@ $^ $(LDFLAGS) dist: DESTDIR := dist