diff --git a/Makefile.vc b/Makefile.vc index 81248297..8153e21e 100644 --- a/Makefile.vc +++ b/Makefile.vc @@ -232,6 +232,9 @@ EX_FORMAT_DEC_OBJS = \ $(DIROBJ)\examples\webpdec.obj \ $(DIROBJ)\examples\wicdec.obj \ +EX_GIF_DEC_OBJS = \ + $(DIROBJ)\examples\gifdec.obj \ + EX_UTIL_OBJS = \ $(DIROBJ)\examples\example_util.obj \ @@ -302,8 +305,8 @@ gif2webp: $(DIRBIN)\gif2webp.exe $(DIRBIN)\cwebp.exe: $(DIROBJ)\examples\cwebp.obj $(EX_FORMAT_DEC_OBJS) $(DIRBIN)\dwebp.exe: $(DIROBJ)\examples\dwebp.obj -$(DIRBIN)\gif2webp.exe: $(DIROBJ)\examples\gif2webp.obj $(EX_UTIL_OBJS) -$(DIRBIN)\gif2webp.exe: $(LIBWEBPMUX) $(LIBWEBP) +$(DIRBIN)\gif2webp.exe: $(DIROBJ)\examples\gif2webp.obj $(EX_GIF_DEC_OBJS) +$(DIRBIN)\gif2webp.exe: $(EX_UTIL_OBJS) $(LIBWEBPMUX) $(LIBWEBP) $(DIRBIN)\vwebp.exe: $(DIROBJ)\examples\vwebp.obj $(DIRBIN)\vwebp.exe: $(EX_UTIL_OBJS) $(LIBWEBPDEMUX) $(LIBWEBP) $(DIRBIN)\webpmux.exe: $(DIROBJ)\examples\webpmux.obj $(LIBWEBPMUX) diff --git a/examples/Makefile.am b/examples/Makefile.am index f591ead7..808f8e28 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -32,7 +32,7 @@ cwebp_CPPFLAGS += $(JPEG_INCLUDES) $(PNG_INCLUDES) $(TIFF_INCLUDES) cwebp_LDADD = libexampleutil.la ../src/libwebp.la cwebp_LDADD += $(JPEG_LIBS) $(PNG_LIBS) $(TIFF_LIBS) -gif2webp_SOURCES = gif2webp.c +gif2webp_SOURCES = gif2webp.c gifdec.c gifdec.h gif2webp_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) $(GIF_INCLUDES) gif2webp_LDADD = libexampleutil.la ../src/mux/libwebpmux.la ../src/libwebp.la gif2webp_LDADD += $(GIF_LIBS) diff --git a/examples/gif2webp.c b/examples/gif2webp.c index cb6de948..8078e7ef 100644 --- a/examples/gif2webp.c +++ b/examples/gif2webp.c @@ -27,225 +27,11 @@ #include "webp/encode.h" #include "webp/mux.h" #include "./example_util.h" - -// GIFLIB_MAJOR is only defined in libgif >= 4.2.0. -#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) -# define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR) -# define LOCAL_GIF_PREREQ(maj, min) \ - (LOCAL_GIF_VERSION >= (((maj) << 8) | (min))) -#else -# define LOCAL_GIF_VERSION 0 -# define LOCAL_GIF_PREREQ(maj, min) 0 -#endif - -#define GIF_TRANSPARENT_MASK 0x01 -#define GIF_DISPOSE_MASK 0x07 -#define GIF_DISPOSE_SHIFT 2 -#define WHITE_COLOR 0xffffffff -#define TRANSPARENT_COLOR 0x00ffffff -#define MAX_CACHE_SIZE 30 - -typedef enum GIFDisposeMethod { - GIF_DISPOSE_NONE, - GIF_DISPOSE_BACKGROUND, - GIF_DISPOSE_RESTORE_PREVIOUS -} GIFDisposeMethod; - -typedef struct { - int x_offset, y_offset, width, height; -} GIFFrameRect; +#include "./gifdec.h" //------------------------------------------------------------------------------ -static int transparent_index = -1; // Opaque frame by default. - -static void ClearRectangle(WebPPicture* const picture, - int left, int top, int width, int height) { - int j; - for (j = top; j < top + height; ++j) { - uint32_t* const dst = picture->argb + j * picture->argb_stride; - int i; - for (i = left; i < left + width; ++i) { - dst[i] = TRANSPARENT_COLOR; - } - } -} - -static void ClearPic(WebPPicture* const pic, const GIFFrameRect* const rect) { - if (rect != NULL) { - ClearRectangle(pic, rect->x_offset, rect->y_offset, - rect->width, rect->height); - } else { - ClearRectangle(pic, 0, 0, pic->width, pic->height); - } -} - -// TODO: Also used in picture.c. Move to a common location? -// Copy width x height pixels from 'src' to 'dst' honoring the strides. -static void CopyPlane(const uint8_t* src, int src_stride, - uint8_t* dst, int dst_stride, int width, int height) { - while (height-- > 0) { - memcpy(dst, src, width); - src += src_stride; - dst += dst_stride; - } -} - -// Copy pixels from 'src' to 'dst' honoring strides. 'src' and 'dst' are assumed -// to be already allocated. -static void CopyPixels(const WebPPicture* const src, WebPPicture* const dst) { - assert(src->width == dst->width && src->height == dst->height); - CopyPlane((uint8_t*)src->argb, 4 * src->argb_stride, (uint8_t*)dst->argb, - 4 * dst->argb_stride, 4 * src->width, src->height); -} - -// Given 'src' picture and its frame rectangle 'rect', blend it into 'dst'. -static void BlendPixels(const WebPPicture* const src, - const GIFFrameRect* const rect, - WebPPicture* const dst) { - int j; - assert(src->width == dst->width && src->height == dst->height); - for (j = rect->y_offset; j < rect->y_offset + rect->height; ++j) { - int i; - for (i = rect->x_offset; i < rect->x_offset + rect->width; ++i) { - const uint32_t src_pixel = src->argb[j * src->argb_stride + i]; - const int src_alpha = src_pixel >> 24; - if (src_alpha != 0) { - dst->argb[j * dst->argb_stride + i] = src_pixel; - } - } - } -} - -static void DisposeFrameRectangle(GIFDisposeMethod dispose_method, - const GIFFrameRect* const rect, - const WebPPicture* const prev_canvas, - WebPPicture* const curr_canvas) { - assert(rect != NULL); - if (dispose_method == GIF_DISPOSE_BACKGROUND) { - ClearPic(curr_canvas, rect); - } else if (dispose_method == GIF_DISPOSE_RESTORE_PREVIOUS) { - const int src_stride = prev_canvas->argb_stride; - const uint32_t* const src = - prev_canvas->argb + rect->x_offset + rect->y_offset * src_stride; - const int dst_stride = curr_canvas->argb_stride; - uint32_t* const dst = - curr_canvas->argb + rect->x_offset + rect->y_offset * dst_stride; - assert(prev_canvas != NULL); - CopyPlane((uint8_t*)src, 4 * src_stride, (uint8_t*)dst, 4 * dst_stride, - 4 * rect->width, rect->height); - } -} - -static void Remap(const uint8_t* const src, const 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); - } -} - -// Read the GIF image frame. -static int ReadFrame(GifFileType* const gif, GIFFrameRect* const gif_rect, - WebPPicture* const webp_frame) { - WebPPicture sub_image; - const GifImageDesc* const image_desc = &gif->Image; - uint32_t* dst = NULL; - uint8_t* tmp = NULL; - int ok = 0; - GIFFrameRect rect = { - image_desc->Left, image_desc->Top, image_desc->Width, image_desc->Height - }; - *gif_rect = rect; - - // Use a view for the sub-picture: - if (!WebPPictureView(webp_frame, rect.x_offset, rect.y_offset, - rect.width, rect.height, &sub_image)) { - fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", - rect.width, rect.height, rect.x_offset, rect.y_offset); - return 0; - } - dst = sub_image.argb; - - tmp = (uint8_t*)malloc(rect.width * 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 < rect.height; - y += interlace_jumps[pass]) { - if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; - Remap(tmp, gif, dst + y * sub_image.argb_stride, rect.width); - } - } - } else { // Non-interlaced image. - int y; - for (y = 0; y < rect.height; ++y) { - if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; - Remap(tmp, gif, dst + y * sub_image.argb_stride, rect.width); - } - } - ok = 1; - - End: - if (!ok) webp_frame->error_code = sub_image.error_code; - WebPPictureFree(&sub_image); - free(tmp); - return ok; -} - -static void GetBackgroundColor(const ColorMapObject* const color_map, - int bgcolor_idx, uint32_t* const bgcolor) { - if (transparent_index != -1 && bgcolor_idx == transparent_index) { - *bgcolor = TRANSPARENT_COLOR; // Special case. - } else if (color_map == NULL || color_map->Colors == NULL - || bgcolor_idx >= color_map->ColorCount) { - *bgcolor = WHITE_COLOR; - fprintf(stderr, - "GIF decode warning: invalid background color index. Assuming " - "white background.\n"); - } else { - const GifColorType color = color_map->Colors[bgcolor_idx]; - *bgcolor = (0xff << 24) - | (color.Red << 16) - | (color.Green << 8) - | (color.Blue << 0); - } -} - -static void DisplayGifError(const GifFileType* const gif, int gif_error) { - // libgif 4.2.0 has retired PrintGifError() and added GifErrorString(). -#if LOCAL_GIF_PREREQ(4,2) -#if LOCAL_GIF_PREREQ(5,0) - // Static string actually, hence the const char* cast. - const char* error_str = (const char*)GifErrorString( - (gif == NULL) ? gif_error : gif->Error); -#else - const char* error_str = (const char*)GifErrorString(); - (void)gif; -#endif - if (error_str == NULL) error_str = "Unknown error"; - fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str); -#else - (void)gif; - fprintf(stderr, "GIFLib Error %d: ", gif_error); - PrintGifError(); - fprintf(stderr, "\n"); -#endif -} +static int transparent_index = GIF_INDEX_INVALID; // Opaque by default. static const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = { "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA", @@ -514,14 +300,15 @@ int main(int argc, const char *argv[]) { frame.height = gif->SHeight; frame.use_argb = 1; if (!WebPPictureAlloc(&frame)) goto End; - ClearPic(&frame, NULL); + GIFClearPic(&frame, NULL); WebPPictureCopy(&frame, &curr_canvas); WebPPictureCopy(&frame, &prev_canvas); WebPPictureCopy(&frame, &prev_to_prev_canvas); // Background color. - GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor, - &enc_options.anim_params.bgcolor); + GIFGetBackgroundColor(gif->SColorMap, gif->SBackGroundColor, + transparent_index, + &enc_options.anim_params.bgcolor); // Initialize encoder. enc = WebPAnimEncoderNew(curr_canvas.width, curr_canvas.height, @@ -536,12 +323,12 @@ int main(int argc, const char *argv[]) { image_desc->Height = gif->SHeight; } - if (!ReadFrame(gif, &gif_rect, &frame)) { + if (!GIFReadFrame(gif, transparent_index, &gif_rect, &frame)) { goto End; } // Blend frame rectangle with previous canvas to compose full canvas. // Note that 'curr_canvas' is same as 'prev_canvas' at this point. - BlendPixels(&frame, &gif_rect, &curr_canvas); + GIFBlendFrames(&frame, &gif_rect, &curr_canvas); if (!WebPAnimEncoderAdd(enc, &curr_canvas, duration, &config)) { fprintf(stderr, "Error! Cannot encode frame as WebP\n"); @@ -549,17 +336,16 @@ int main(int argc, const char *argv[]) { } // Update canvases. - CopyPixels(&prev_canvas, &prev_to_prev_canvas); - DisposeFrameRectangle(orig_dispose, &gif_rect, &prev_canvas, - &curr_canvas); - CopyPixels(&curr_canvas, &prev_canvas); + GIFCopyPixels(&prev_canvas, &prev_to_prev_canvas); + GIFDisposeFrame(orig_dispose, &gif_rect, &prev_canvas, &curr_canvas); + GIFCopyPixels(&curr_canvas, &prev_canvas); // In GIF, graphic control extensions are optional for a frame, so we // may not get one before reading the next frame. To handle this case, // we reset frame properties to reasonable defaults for the next frame. orig_dispose = GIF_DISPOSE_NONE; duration = 0; - transparent_index = -1; // Opaque frame by default. + transparent_index = GIF_INDEX_INVALID; break; } case EXTENSION_RECORD_TYPE: { @@ -573,25 +359,10 @@ int main(int argc, const char *argv[]) { 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; - duration = delay * 10; // Duration is in 1 ms units for WebP. - switch (dispose) { - case 3: - orig_dispose = GIF_DISPOSE_RESTORE_PREVIOUS; - break; - case 2: - orig_dispose = GIF_DISPOSE_BACKGROUND; - break; - case 1: - case 0: - default: - orig_dispose = GIF_DISPOSE_NONE; - break; + if (!GIFReadGraphicsExtension(data, &duration, &orig_dispose, + &transparent_index)) { + goto End; } - transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1; break; } case PLAINTEXT_EXT_FUNC_CODE: { @@ -601,11 +372,9 @@ int main(int argc, const char *argv[]) { if (data[0] != 11) break; // Chunk is too short if (!memcmp(data + 1, "NETSCAPE2.0", 11) || !memcmp(data + 1, "ANIMEXTS1.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 - loop_count = data[2] | (data[3] << 8); + if (!GIFReadLoopCount(gif, &data, &loop_count)) { + goto End; + } if (verbose) { fprintf(stderr, "Loop count: %d\n", loop_count); } @@ -620,42 +389,9 @@ int main(int argc, const char *argv[]) { !stored_icc && !memcmp(data + 1, "ICCRGBG1012", 11); if (is_xmp || is_icc) { - WebPData* const metadata = is_xmp ? &xmp_data : &icc_data; - // Construct metadata from sub-blocks. - // Usual case (including ICC profile): In each sub-block, the - // first byte specifies its size in bytes (0 to 255) and the - // rest of the bytes contain the data. - // Special case for XMP data: In each sub-block, the first byte - // is also part of the XMP payload. XMP in GIF also has a 257 - // byte padding data. See the XMP specification for details. - while (1) { - WebPData subblock; - const uint8_t* tmp; - if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) { - goto End; - } - if (data == NULL) break; // Finished. - subblock.size = is_xmp ? data[0] + 1 : data[0]; - assert(subblock.size > 0); - subblock.bytes = is_xmp ? data : data + 1; - // Note: We store returned value in 'tmp' first, to avoid - // leaking old memory in metadata->bytes on error. - tmp = (uint8_t*)realloc((void*)metadata->bytes, - metadata->size + subblock.size); - if (tmp == NULL) { - goto End; - } - memcpy((void*)(tmp + metadata->size), - subblock.bytes, subblock.size); - metadata->bytes = tmp; - metadata->size += subblock.size; - } - if (is_xmp) { - // XMP padding data is 0x01, 0xff, 0xfe ... 0x01, 0x00. - const size_t xmp_pading_size = 257; - if (metadata->size > xmp_pading_size) { - metadata->size -= xmp_pading_size; - } + if (!GIFReadMetadata(gif, &data, + is_xmp ? &xmp_data : &icc_data)) { + goto End; } if (is_icc) { stored_icc = 1; @@ -783,7 +519,7 @@ int main(int argc, const char *argv[]) { if (out != NULL && out_file != NULL) fclose(out); if (gif_error != GIF_OK) { - DisplayGifError(gif, gif_error); + GIFDisplayError(gif, gif_error); } if (gif != NULL) { #if LOCAL_GIF_PREREQ(5,1) diff --git a/examples/gifdec.c b/examples/gifdec.c new file mode 100644 index 00000000..cd72ebdc --- /dev/null +++ b/examples/gifdec.c @@ -0,0 +1,406 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// GIF decode. + +#ifdef HAVE_CONFIG_H +#include "webp/config.h" +#endif + +#include "./gifdec.h" + +#include + +#ifdef WEBP_HAVE_GIF +#include +#include +#include + +#include "webp/mux_types.h" +#include "webp/encode.h" + +#define GIF_TRANSPARENT_COLOR 0x00ffffff +#define GIF_WHITE_COLOR 0xffffffff +#define GIF_TRANSPARENT_MASK 0x01 +#define GIF_DISPOSE_MASK 0x07 +#define GIF_DISPOSE_SHIFT 2 + +void GIFGetBackgroundColor(const ColorMapObject* const color_map, + int bgcolor_index, int transparent_index, + uint32_t* const bgcolor) { + if (transparent_index != GIF_INDEX_INVALID && + bgcolor_index == transparent_index) { + *bgcolor = GIF_TRANSPARENT_COLOR; // Special case. + } else if (color_map == NULL || color_map->Colors == NULL + || bgcolor_index >= color_map->ColorCount) { + *bgcolor = GIF_WHITE_COLOR; + fprintf(stderr, + "GIF decode warning: invalid background color index. Assuming " + "white background.\n"); + } else { + const GifColorType color = color_map->Colors[bgcolor_index]; + *bgcolor = (0xff << 24) + | (color.Red << 16) + | (color.Green << 8) + | (color.Blue << 0); + } +} + +int GIFReadGraphicsExtension(const GifByteType* const buf, int* const duration, + GIFDisposeMethod* const dispose, + int* const transparent_index) { + const int flags = buf[1]; + const int dispose_raw = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK; + const int duration_raw = buf[2] | (buf[3] << 8); // In 10 ms units. + if (buf[0] != 4) return 0; + *duration = duration_raw * 10; // Duration is in 1 ms units. + switch (dispose_raw) { + case 3: + *dispose = GIF_DISPOSE_RESTORE_PREVIOUS; + break; + case 2: + *dispose = GIF_DISPOSE_BACKGROUND; + break; + case 1: + case 0: + default: + *dispose = GIF_DISPOSE_NONE; + break; + } + *transparent_index = + (flags & GIF_TRANSPARENT_MASK) ? buf[4] : GIF_INDEX_INVALID; + return 1; +} + +static void Remap(const GifFileType* const gif, const uint8_t* const src, + int len, int transparent_index, uint32_t* dst) { + 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) ? GIF_TRANSPARENT_COLOR + : c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24); + } +} + +int GIFReadFrame(GifFileType* const gif, int transparent_index, + GIFFrameRect* const gif_rect, WebPPicture* const picture) { + WebPPicture sub_image; + const GifImageDesc* const image_desc = &gif->Image; + uint32_t* dst = NULL; + uint8_t* tmp = NULL; + int ok = 0; + GIFFrameRect rect = { + image_desc->Left, image_desc->Top, image_desc->Width, image_desc->Height + }; + *gif_rect = rect; + + // Use a view for the sub-picture: + if (!WebPPictureView(picture, rect.x_offset, rect.y_offset, + rect.width, rect.height, &sub_image)) { + fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", + rect.width, rect.height, rect.x_offset, rect.y_offset); + return 0; + } + dst = sub_image.argb; + + tmp = (uint8_t*)malloc(rect.width * 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 < rect.height; + y += interlace_jumps[pass]) { + if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; + Remap(gif, tmp, rect.width, transparent_index, + dst + y * sub_image.argb_stride); + } + } + } else { // Non-interlaced image. + int y; + for (y = 0; y < rect.height; ++y) { + if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; + Remap(gif, tmp, rect.width, transparent_index, + dst + y * sub_image.argb_stride); + } + } + ok = 1; + + End: + if (!ok) picture->error_code = sub_image.error_code; + WebPPictureFree(&sub_image); + free(tmp); + return ok; +} + +int GIFReadLoopCount(GifFileType* const gif, GifByteType** const buf, + int* const loop_count) { + assert(!memcmp(*buf + 1, "NETSCAPE2.0", 11) || + !memcmp(*buf + 1, "ANIMEXTS1.0", 11)); + if (DGifGetExtensionNext(gif, buf) == GIF_ERROR) { + return 0; + } + if (*buf == NULL) { + return 0; // Loop count sub-block missing. + } + if ((*buf)[0] < 3 || (*buf)[1] != 1) { + return 0; // wrong size/marker + } + *loop_count = (*buf)[2] | ((*buf)[3] << 8); + return 1; +} + +int GIFReadMetadata(GifFileType* const gif, GifByteType** const buf, + WebPData* const metadata) { + const int is_xmp = !memcmp(*buf + 1, "XMP DataXMP", 11); + const int is_icc = !memcmp(*buf + 1, "ICCRGBG1012", 11); + assert(is_xmp || is_icc); + (void)is_icc; // silence unused warning. + // Construct metadata from sub-blocks. + // Usual case (including ICC profile): In each sub-block, the + // first byte specifies its size in bytes (0 to 255) and the + // rest of the bytes contain the data. + // Special case for XMP data: In each sub-block, the first byte + // is also part of the XMP payload. XMP in GIF also has a 257 + // byte padding data. See the XMP specification for details. + while (1) { + WebPData subblock; + const uint8_t* tmp; + if (DGifGetExtensionNext(gif, buf) == GIF_ERROR) { + return 0; + } + if (*buf == NULL) break; // Finished. + subblock.size = is_xmp ? (*buf)[0] + 1 : (*buf)[0]; + assert(subblock.size > 0); + subblock.bytes = is_xmp ? *buf : *buf + 1; + // Note: We store returned value in 'tmp' first, to avoid + // leaking old memory in metadata->bytes on error. + tmp = (uint8_t*)realloc((void*)metadata->bytes, + metadata->size + subblock.size); + if (tmp == NULL) { + return 0; + } + memcpy((void*)(tmp + metadata->size), + subblock.bytes, subblock.size); + metadata->bytes = tmp; + metadata->size += subblock.size; + } + if (is_xmp) { + // XMP padding data is 0x01, 0xff, 0xfe ... 0x01, 0x00. + const size_t xmp_pading_size = 257; + if (metadata->size > xmp_pading_size) { + metadata->size -= xmp_pading_size; + } + } + return 1; +} + +static void ClearRectangle(WebPPicture* const picture, + int left, int top, int width, int height) { + int j; + for (j = top; j < top + height; ++j) { + uint32_t* const dst = picture->argb + j * picture->argb_stride; + int i; + for (i = left; i < left + width; ++i) { + dst[i] = GIF_TRANSPARENT_COLOR; + } + } +} + +void GIFClearPic(WebPPicture* const pic, const GIFFrameRect* const rect) { + if (rect != NULL) { + ClearRectangle(pic, rect->x_offset, rect->y_offset, + rect->width, rect->height); + } else { + ClearRectangle(pic, 0, 0, pic->width, pic->height); + } +} + +// TODO: Also used in picture.c. Move to a common location? +// Copy width x height pixels from 'src' to 'dst' honoring the strides. +static void CopyPlane(const uint8_t* src, int src_stride, + uint8_t* dst, int dst_stride, int width, int height) { + while (height-- > 0) { + memcpy(dst, src, width); + src += src_stride; + dst += dst_stride; + } +} + +void GIFCopyPixels(const WebPPicture* const src, WebPPicture* const dst) { + assert(src->width == dst->width && src->height == dst->height); + CopyPlane((uint8_t*)src->argb, 4 * src->argb_stride, (uint8_t*)dst->argb, + 4 * dst->argb_stride, 4 * src->width, src->height); +} + +void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect, + const WebPPicture* const prev_canvas, + WebPPicture* const curr_canvas) { + assert(rect != NULL); + if (dispose == GIF_DISPOSE_BACKGROUND) { + GIFClearPic(curr_canvas, rect); + } else if (dispose == GIF_DISPOSE_RESTORE_PREVIOUS) { + const int src_stride = prev_canvas->argb_stride; + const uint32_t* const src = + prev_canvas->argb + rect->x_offset + rect->y_offset * src_stride; + const int dst_stride = curr_canvas->argb_stride; + uint32_t* const dst = + curr_canvas->argb + rect->x_offset + rect->y_offset * dst_stride; + assert(prev_canvas != NULL); + CopyPlane((uint8_t*)src, 4 * src_stride, (uint8_t*)dst, 4 * dst_stride, + 4 * rect->width, rect->height); + } +} + +void GIFBlendFrames(const WebPPicture* const src, + const GIFFrameRect* const rect, WebPPicture* const dst) { + int j; + assert(src->width == dst->width && src->height == dst->height); + for (j = rect->y_offset; j < rect->y_offset + rect->height; ++j) { + int i; + for (i = rect->x_offset; i < rect->x_offset + rect->width; ++i) { + const uint32_t src_pixel = src->argb[j * src->argb_stride + i]; + const int src_alpha = src_pixel >> 24; + if (src_alpha != 0) { + dst->argb[j * dst->argb_stride + i] = src_pixel; + } + } + } +} + +void GIFDisplayError(const GifFileType* const gif, int gif_error) { + // libgif 4.2.0 has retired PrintGifError() and added GifErrorString(). +#if LOCAL_GIF_PREREQ(4,2) +#if LOCAL_GIF_PREREQ(5,0) + // Static string actually, hence the const char* cast. + const char* error_str = (const char*)GifErrorString( + (gif == NULL) ? gif_error : gif->Error); +#else + const char* error_str = (const char*)GifErrorString(); + (void)gif; +#endif + if (error_str == NULL) error_str = "Unknown error"; + fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str); +#else + (void)gif; + fprintf(stderr, "GIFLib Error %d: ", gif_error); + PrintGifError(); + fprintf(stderr, "\n"); +#endif +} + +#else // !WEBP_HAVE_GIF + +static void ErrorGIFNotAvailable() { + fprintf(stderr, "GIF support not compiled. Please install the libgif-dev " + "package before building.\n"); +} + +void GIFGetBackgroundColor(const struct ColorMapObject* const color_map, + int bgcolor_index, int transparent_index, + uint32_t* const bgcolor) { + (void)color_map; + (void)bgcolor_index; + (void)transparent_index; + (void)bgcolor; + ErrorGIFNotAvailable(); +} + +int GIFReadGraphicsExtension(const GifByteType* const data, int* const duration, + GIFDisposeMethod* const dispose, + int* const transparent_index) { + (void)data; + (void)duration; + (void)dispose; + (void)transparent_index; + ErrorGIFNotAvailable(); + return 0; +} + +int GIFReadFrame(struct GifFileType* const gif, int transparent_index, + GIFFrameRect* const gif_rect, + struct WebPPicture* const picture) { + (void)gif; + (void)transparent_index; + (void)gif_rect; + (void)picture; + ErrorGIFNotAvailable(); + return 0; +} + +int GIFReadLoopCount(struct GifFileType* const gif, GifByteType** const buf, + int* const loop_count) { + (void)gif; + (void)buf; + (void)loop_count; + ErrorGIFNotAvailable(); + return 0; +} + +int GIFReadMetadata(struct GifFileType* const gif, GifByteType** const buf, + struct WebPData* const metadata) { + (void)gif; + (void)buf; + (void)metadata; + ErrorGIFNotAvailable(); + return 0; +} + +void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect, + const struct WebPPicture* const prev_canvas, + struct WebPPicture* const curr_canvas) { + (void)dispose; + (void)rect; + (void)prev_canvas; + (void)curr_canvas; + ErrorGIFNotAvailable(); +} + +void GIFBlendFrames(const struct WebPPicture* const src, + const GIFFrameRect* const rect, + struct WebPPicture* const dst) { + (void)src; + (void)rect; + (void)dst; + ErrorGIFNotAvailable(); +} + +void GIFDisplayError(const struct GifFileType* const gif, int gif_error) { + (void)gif; + (void)gif_error; + ErrorGIFNotAvailable(); +} + +void GIFClearPic(struct WebPPicture* const pic, + const GIFFrameRect* const rect) { + (void)pic; + (void)rect; + ErrorGIFNotAvailable(); +} + +void GIFCopyPixels(const struct WebPPicture* const src, + struct WebPPicture* const dst) { + (void)src; + (void)dst; + ErrorGIFNotAvailable(); +} + +#endif // WEBP_HAVE_GIF + +// ----------------------------------------------------------------------------- diff --git a/examples/gifdec.h b/examples/gifdec.h new file mode 100644 index 00000000..55ea2a40 --- /dev/null +++ b/examples/gifdec.h @@ -0,0 +1,112 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// GIF decode. + +#ifndef WEBP_EXAMPLES_GIFDEC_H_ +#define WEBP_EXAMPLES_GIFDEC_H_ + +#include +#include "webp/types.h" + +#ifdef WEBP_HAVE_GIF +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// GIFLIB_MAJOR is only defined in libgif >= 4.2.0. +#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) +# define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR) +# define LOCAL_GIF_PREREQ(maj, min) \ + (LOCAL_GIF_VERSION >= (((maj) << 8) | (min))) +#else +# define LOCAL_GIF_VERSION 0 +# define LOCAL_GIF_PREREQ(maj, min) 0 +#endif + +#define GIF_INDEX_INVALID (-1) + +typedef enum GIFDisposeMethod { + GIF_DISPOSE_NONE, + GIF_DISPOSE_BACKGROUND, + GIF_DISPOSE_RESTORE_PREVIOUS +} GIFDisposeMethod; + +typedef struct { + int x_offset, y_offset, width, height; +} GIFFrameRect; + +struct WebPData; +struct WebPPicture; + +#ifndef WEBP_HAVE_GIF +struct ColorMapObject; +struct GifFileType; +typedef unsigned char GifByteType; +#endif + +// Given the index of background color and transparent color, returns the +// corresponding background color (in BGRA format) in 'bgcolor'. +void GIFGetBackgroundColor(const struct ColorMapObject* const color_map, + int bgcolor_index, int transparent_index, + uint32_t* const bgcolor); + +// Parses the given graphics extension data to get frame duration (in 1ms +// units), dispose method and transparent color index. +// Returns true on success. +int GIFReadGraphicsExtension(const GifByteType* const buf, int* const duration, + GIFDisposeMethod* const dispose, + int* const transparent_index); + +// Reads the next GIF frame from 'gif' into 'picture'. Also, returns the GIF +// frame dimensions and offsets in 'rect'. +// Returns true on success. +int GIFReadFrame(struct GifFileType* const gif, int transparent_index, + GIFFrameRect* const gif_rect, + struct WebPPicture* const picture); + +// Parses loop count from the given Netscape extension data. +int GIFReadLoopCount(struct GifFileType* const gif, GifByteType** const buf, + int* const loop_count); + +// Parses the given ICC or XMP extension data and stores it into 'metadata'. +// Returns true on success. +int GIFReadMetadata(struct GifFileType* const gif, GifByteType** const buf, + struct WebPData* const metadata); + +// Dispose the pixels within 'rect' of 'curr_canvas' based on 'dispose' method +// and 'prev_canvas'. +void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect, + const struct WebPPicture* const prev_canvas, + struct WebPPicture* const curr_canvas); + +// Given 'src' picture and its frame rectangle 'rect', blend it into 'dst'. +void GIFBlendFrames(const struct WebPPicture* const src, + const GIFFrameRect* const rect, + struct WebPPicture* const dst); + +// Prints an error string based on 'gif_error'. +void GIFDisplayError(const struct GifFileType* const gif, int gif_error); + +// In the given 'pic', clear the pixels in 'rect' to transparent color. +void GIFClearPic(struct WebPPicture* const pic, const GIFFrameRect* const rect); + +// Copy pixels from 'src' to 'dst' honoring strides. 'src' and 'dst' are assumed +// to be already allocated. +void GIFCopyPixels(const struct WebPPicture* const src, + struct WebPPicture* const dst); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_EXAMPLES_GIFDEC_H_ diff --git a/makefile.unix b/makefile.unix index a38e5a38..8042d3a0 100644 --- a/makefile.unix +++ b/makefile.unix @@ -181,6 +181,9 @@ EX_FORMAT_DEC_OBJS = \ EX_UTIL_OBJS = \ examples/example_util.o \ +GIFDEC_OBJS = \ + examples/gifdec.o \ + MUX_OBJS = \ src/mux/anim_encode.o \ src/mux/muxedit.o \ @@ -258,6 +261,7 @@ OUTPUT = $(OUT_LIBS) $(OUT_EXAMPLES) ifeq ($(MAKECMDGOALS),clean) OUTPUT += $(EXTRA_EXAMPLES) OUTPUT += src/demux/libwebpdemux.a src/mux/libwebpmux.a + OUTPUT += examples/libgifdec.a endif ex: $(OUT_EXAMPLES) @@ -277,6 +281,7 @@ src/utils/bit_writer.o: src/utils/endian_inl.h $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ examples/libexample_util.a: $(EX_UTIL_OBJS) +examples/libgifdec.a: $(GIFDEC_OBJS) src/libwebpdecoder.a: $(LIBWEBPDECODER_OBJS) src/libwebp.a: $(LIBWEBP_OBJS) src/mux/libwebpmux.a: $(LIBWEBPMUX_OBJS) @@ -287,7 +292,7 @@ src/demux/libwebpdemux.a: $(LIBWEBPDEMUX_OBJS) examples/cwebp: examples/cwebp.o $(EX_FORMAT_DEC_OBJS) examples/dwebp: examples/dwebp.o -examples/gif2webp: examples/gif2webp.o +examples/gif2webp: examples/gif2webp.o $(GIFDEC_OBJS) examples/vwebp: examples/vwebp.o examples/webpmux: examples/webpmux.o @@ -295,7 +300,7 @@ examples/cwebp: examples/libexample_util.a src/libwebp.a examples/cwebp: EXTRA_LIBS += $(CWEBP_LIBS) examples/dwebp: examples/libexample_util.a src/libwebpdecoder.a examples/dwebp: EXTRA_LIBS += $(DWEBP_LIBS) -examples/gif2webp: examples/libexample_util.a +examples/gif2webp: examples/libexample_util.a examples/libgifdec.a examples/gif2webp: src/mux/libwebpmux.a src/libwebp.a examples/gif2webp: EXTRA_LIBS += $(GIF_LIBS) examples/gif2webp: EXTRA_FLAGS += -DWEBP_HAVE_GIF