diff --git a/Android.mk b/Android.mk index a8f1cc29..d2cd9980 100644 --- a/Android.mk +++ b/Android.mk @@ -33,6 +33,7 @@ dec_srcs := \ src/dec/webp.c \ demux_srcs := \ + src/demux/anim_decode.c \ src/demux/demux.c \ dsp_dec_srcs := \ diff --git a/Makefile.vc b/Makefile.vc index 95258c58..911474db 100644 --- a/Makefile.vc +++ b/Makefile.vc @@ -181,6 +181,7 @@ DEC_OBJS = \ $(DIROBJ)\dec\webp.obj \ DEMUX_OBJS = \ + $(DIROBJ)\demux\anim_decode.obj \ $(DIROBJ)\demux\demux.obj \ DSP_DEC_OBJS = \ diff --git a/NEWS b/NEWS index 61d0d6c8..abfa9629 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +- Next version: + * New WebPAnimEncoder API for creating animations + * New WebPAnimDecoder API for decoding animations + - 3/3/15: version 0.4.3 This is a binary compatible release. * Android / gcc / iOS / MSVS build fixes and improvements diff --git a/examples/anim_util.cc b/examples/anim_util.cc index 92f7132f..a62e9000 100644 --- a/examples/anim_util.cc +++ b/examples/anim_util.cc @@ -149,237 +149,74 @@ static bool IsWebP(const std::string& file_str) { file_str.length(), NULL, NULL) != 0; } -// Returns true if the current frame is a key-frame. -static bool IsKeyFrameWebP(const WebPIterator& curr, const WebPIterator& prev, - const DecodedFrame* const prev_frame, - int canvas_width, int canvas_height) { - if (prev_frame == NULL) { - return true; - } else if ((!curr.has_alpha || curr.blend_method == WEBP_MUX_NO_BLEND) && - IsFullFrame(curr.width, curr.height, - canvas_width, canvas_height)) { - return true; - } else { - return (prev.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) && - (IsFullFrame(prev.width, prev.height, canvas_width, canvas_height) || - prev_frame->is_key_frame); - } -} - -// Blend a single channel of 'src' over 'dst', given their alpha channel values. -static uint8_t BlendChannelWebP(uint32_t src, uint8_t src_a, uint32_t dst, - uint8_t dst_a, uint32_t scale, int shift) { - const uint8_t src_channel = (src >> shift) & 0xff; - const uint8_t dst_channel = (dst >> shift) & 0xff; - const uint32_t blend_unscaled = src_channel * src_a + dst_channel * dst_a; - assert(blend_unscaled < (1ULL << 32) / scale); - return (blend_unscaled * scale) >> 24; -} - -// Blend 'src' over 'dst' assuming they are NOT pre-multiplied by alpha. -static uint32_t BlendPixelWebP(uint32_t src, uint32_t dst) { - const uint8_t src_a = (src >> 24) & 0xff; - - if (src_a == 0) { - return dst; - } else { - const uint8_t dst_a = (dst >> 24) & 0xff; - // This is the approximate integer arithmetic for the actual formula: - // dst_factor_a = (dst_a * (255 - src_a)) / 255. - const uint8_t dst_factor_a = (dst_a * (256 - src_a)) >> 8; - assert(src_a + dst_factor_a < 256); - const uint8_t blend_a = src_a + dst_factor_a; - const uint32_t scale = (1UL << 24) / blend_a; - - const uint8_t blend_r = - BlendChannelWebP(src, src_a, dst, dst_factor_a, scale, 0); - const uint8_t blend_g = - BlendChannelWebP(src, src_a, dst, dst_factor_a, scale, 8); - const uint8_t blend_b = - BlendChannelWebP(src, src_a, dst, dst_factor_a, scale, 16); - - return (blend_r << 0) | (blend_g << 8) | (blend_b << 16) | (blend_a << 24); - } -} - -// Returns two ranges ( pairs) at row 'canvas_y', that belong to -// 'src' but not 'dst'. A point range is empty if the corresponding width is 0. -static void FindBlendRangeAtRowWebP(const WebPIterator* const src, - const WebPIterator* const dst, int canvas_y, - int* const left1, int* const width1, - int* const left2, int* const width2) { - const int src_max_x = src->x_offset + src->width; - const int dst_max_x = dst->x_offset + dst->width; - const int dst_max_y = dst->y_offset + dst->height; - assert(canvas_y >= src->y_offset && canvas_y < (src->y_offset + src->height)); - *left1 = -1; - *width1 = 0; - *left2 = -1; - *width2 = 0; - - if (canvas_y < dst->y_offset || canvas_y >= dst_max_y || - src->x_offset >= dst_max_x || src_max_x <= dst->x_offset) { - *left1 = src->x_offset; - *width1 = src->width; - return; - } - - if (src->x_offset < dst->x_offset) { - *left1 = src->x_offset; - *width1 = dst->x_offset - src->x_offset; - } - - if (src_max_x > dst_max_x) { - *left2 = dst_max_x; - *width2 = src_max_x - dst_max_x; - } -} - -// Blend 'num_pixels' in 'src' over 'dst'. -static void BlendPixelRowWebP(uint32_t* const src, const uint32_t* const dst, - int num_pixels) { - for (int i = 0; i < num_pixels; ++i) { - uint32_t* const src_pixel_ptr = &src[i]; - const uint8_t src_alpha = (*src_pixel_ptr >> 24) & 0xff; - if (src_alpha != 0xff) { - const uint32_t dst_pixel = dst[i]; - *src_pixel_ptr = BlendPixelWebP(*src_pixel_ptr, dst_pixel); - } - } -} - // Read animated WebP bitstream 'file_str' into 'AnimatedImage' struct. static bool ReadAnimatedWebP(const char filename[], const std::string& file_str, AnimatedImage* const image, bool dump_frames, const char dump_folder[]) { - bool ok = true; + bool ok = false; + bool dump_ok = true; + uint32_t frame_index = 0; + int prev_frame_timestamp = 0; + const WebPData webp_data = { reinterpret_cast(file_str.data()), file_str.size() }; - WebPDemuxer* const demux = WebPDemux(&webp_data); - if (demux == NULL) return false; + + WebPAnimDecoder* dec = WebPAnimDecoderNew(&webp_data); + if (dec == NULL) { + fprintf(stderr, "Error parsing image: %s\n", filename); + goto End; + } + + WebPAnimInfo anim_info; + if (!WebPAnimDecoderGetInfo(dec, &anim_info)) { + fprintf(stderr, "Error getting global info about the animation\n"); + goto End; + } // Animation properties. - image->canvas_width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); - image->canvas_height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); - image->loop_count = WebPDemuxGetI(demux, WEBP_FF_LOOP_COUNT); - image->bgcolor = WebPDemuxGetI(demux, WEBP_FF_BACKGROUND_COLOR); - - const uint32_t frame_count = WebPDemuxGetI(demux, WEBP_FF_FRAME_COUNT); - const uint32_t canvas_width = image->canvas_width; - const uint32_t canvas_height = image->canvas_height; + image->canvas_width = anim_info.canvas_width; + image->canvas_height = anim_info.canvas_height; + image->loop_count = anim_info.loop_count; + image->bgcolor = anim_info.bgcolor; // Allocate frames. - AllocateFrames(image, frame_count); + AllocateFrames(image, anim_info.frame_count); - // Decode and reconstruct frames. - WebPIterator prev_iter = WebPIterator(); - WebPIterator curr_iter = WebPIterator(); - - for (uint32_t i = 0; i < frame_count; ++i) { - prev_iter = curr_iter; - - // Get frame. - if (!WebPDemuxGetFrame(demux, i + 1, &curr_iter)) { - fprintf(stderr, "Error retrieving frame #%u\n", i); - return false; + // Decode frames. + while (WebPAnimDecoderHasMoreFrames(dec)) { + uint8_t* frame_rgba; + int timestamp; + if (!WebPAnimDecoderGetNext(dec, &frame_rgba, ×tamp)) { + fprintf(stderr, "Error decoding frame #%u\n", frame_index); + goto End; } - - DecodedFrame* const prev_frame = (i > 0) ? &image->frames[i - 1] : NULL; - uint8_t* const prev_rgba = - (prev_frame != NULL) ? prev_frame->rgba.data() : NULL; - DecodedFrame* const curr_frame = &image->frames[i]; + DecodedFrame* const curr_frame = &image->frames[frame_index]; uint8_t* const curr_rgba = curr_frame->rgba.data(); - - curr_frame->duration = curr_iter.duration; - curr_frame->is_key_frame = IsKeyFrameWebP(curr_iter, prev_iter, prev_frame, - canvas_width, canvas_height); - - // TODO(urvang): The logic of decoding and reconstructing the next animated - // frame given the previous one should be a single library call (ideally a - // user-facing API), which takes care of frame disposal, blending etc. - - // Initialize. - if (curr_frame->is_key_frame) { - ZeroFillCanvas(curr_rgba, canvas_width, canvas_height); - } else { - CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height); - if (prev_iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { - ZeroFillFrameRect(curr_rgba, canvas_width * kNumChannels, - prev_iter.x_offset, prev_iter.y_offset, - prev_iter.width, prev_iter.height); - } - } - - // Decode. - const uint8_t* input = curr_iter.fragment.bytes; - const size_t input_size = curr_iter.fragment.size; - const size_t output_offset = - (curr_iter.y_offset * canvas_width + curr_iter.x_offset) * kNumChannels; - uint8_t* output = curr_rgba + output_offset; - const int output_stride = kNumChannels * canvas_width; - const size_t output_size = output_stride * curr_iter.height; - - if (WebPDecodeRGBAInto(input, input_size, output, output_size, - output_stride) == NULL) { - ok = false; - break; - } - - // During the decoding of current frame, we may have set some pixels to be - // transparent (i.e. alpha < 255). However, the value of each of these - // pixels should have been determined by blending it against the value of - // that pixel in the previous frame if blending method of is WEBP_MUX_BLEND. - if (i > 0 && curr_iter.blend_method == WEBP_MUX_BLEND && - !curr_frame->is_key_frame) { - if (prev_iter.dispose_method == WEBP_MUX_DISPOSE_NONE) { - // Blend transparent pixels with pixels in previous canvas. - for (int y = 0; y < curr_iter.height; ++y) { - const size_t offset = - (curr_iter.y_offset + y) * canvas_width + curr_iter.x_offset; - BlendPixelRowWebP(reinterpret_cast(curr_rgba) + offset, - reinterpret_cast(prev_rgba) + offset, - curr_iter.width); - } - } else { - assert(prev_iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND); - // We need to blend a transparent pixel with its value just after - // initialization. That is, blend it with: - // * Fully transparent pixel if it belongs to prevRect <-- No-op. - // * The pixel in the previous canvas otherwise <-- Need alpha-blending. - for (int y = 0; y < curr_iter.height; ++y) { - const int canvas_y = curr_iter.y_offset + y; - int left1, width1, left2, width2; - FindBlendRangeAtRowWebP(&curr_iter, &prev_iter, canvas_y, &left1, - &width1, &left2, &width2); - if (width1 > 0) { - const size_t offset1 = canvas_y * canvas_width + left1; - BlendPixelRowWebP(reinterpret_cast(curr_rgba) + offset1, - reinterpret_cast(prev_rgba) + offset1, - width1); - } - if (width2 > 0) { - const size_t offset2 = canvas_y * canvas_width + left2; - BlendPixelRowWebP(reinterpret_cast(curr_rgba) + offset2, - reinterpret_cast(prev_rgba) + offset2, - width2); - } - } - } - } + curr_frame->duration = timestamp - prev_frame_timestamp; + curr_frame->is_key_frame = false; // Unused. + memcpy(curr_rgba, frame_rgba, + image->canvas_width * kNumChannels * image->canvas_height); // Needed only because we may want to compare with GIF later. CleanupTransparentPixels(reinterpret_cast(curr_rgba), - canvas_width, canvas_height); + image->canvas_width, image->canvas_height); - if (dump_frames) { - ok = ok && DumpFrame(filename, dump_folder, i, curr_rgba, - canvas_width, canvas_height); + if (dump_frames && dump_ok) { + dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba, + image->canvas_width, image->canvas_height); + if (!dump_ok) { // Print error once, but continue decode loop. + fprintf(stderr, "Error dumping frames to %s\n", dump_folder); + } } + + ++frame_index; + prev_frame_timestamp = timestamp; } - WebPDemuxReleaseIterator(&prev_iter); - WebPDemuxReleaseIterator(&curr_iter); - WebPDemuxDelete(demux); + ok = dump_ok; + + End: + WebPAnimDecoderDelete(dec); return ok; } diff --git a/makefile.unix b/makefile.unix index 3dbd475e..8570e411 100644 --- a/makefile.unix +++ b/makefile.unix @@ -123,6 +123,7 @@ DEC_OBJS = \ src/dec/webp.o \ DEMUX_OBJS = \ + src/demux/anim_decode.o \ src/demux/demux.o \ DSP_DEC_OBJS = \ diff --git a/src/demux/Makefile.am b/src/demux/Makefile.am index 507bcb2a..ff593920 100644 --- a/src/demux/Makefile.am +++ b/src/demux/Makefile.am @@ -1,7 +1,7 @@ lib_LTLIBRARIES = libwebpdemux.la libwebpdemux_la_SOURCES = -libwebpdemux_la_SOURCES += demux.c +libwebpdemux_la_SOURCES += anim_decode.c demux.c libwebpdemuxinclude_HEADERS = libwebpdemuxinclude_HEADERS += ../webp/demux.h diff --git a/src/demux/anim_decode.c b/src/demux/anim_decode.c new file mode 100644 index 00000000..fa68ffc7 --- /dev/null +++ b/src/demux/anim_decode.c @@ -0,0 +1,351 @@ +// Copyright 2015 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. +// ----------------------------------------------------------------------------- +// +// AnimDecoder implementation. +// + +#ifdef HAVE_CONFIG_H +#include "../webp/config.h" +#endif + +#include +#include + +#include "../utils/utils.h" +#include "../webp/decode.h" +#include "../webp/demux.h" + +#define NUM_CHANNELS 4 + +struct WebPAnimDecoder { + WebPDemuxer* demux_; // Demuxer created from given WebP bitstream. + WebPAnimInfo info_; // Global info about the animation. + uint8_t* curr_frame_; // Current canvas (not disposed). + uint8_t* prev_frame_disposed_; // Previous canvas (properly disposed). + int prev_frame_timestamp_; // Previous frame timestamp (milliseconds). + WebPIterator prev_iter_; // Iterator object for previous frame. + int prev_frame_was_keyframe_; // True if previous frame was a keyframe. + int next_frame_; // Index of the next frame to be decoded + // (starting from 1). +}; + +WebPAnimDecoder* WebPAnimDecoderNewInternal(const WebPData* webp_data, + int abi_version) { + WebPAnimDecoder* dec = NULL; + if (webp_data == NULL || + WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_DEMUX_ABI_VERSION)) { + return NULL; + } + + // Note: calloc() so that the pointer members are initialized to NULL. + dec = (WebPAnimDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + if (dec == NULL) goto Error; + + dec->demux_ = WebPDemux(webp_data); + if (dec->demux_ == NULL) goto Error; + + dec->info_.canvas_width = WebPDemuxGetI(dec->demux_, WEBP_FF_CANVAS_WIDTH); + dec->info_.canvas_height = WebPDemuxGetI(dec->demux_, WEBP_FF_CANVAS_HEIGHT); + dec->info_.loop_count = WebPDemuxGetI(dec->demux_, WEBP_FF_LOOP_COUNT); + dec->info_.bgcolor = WebPDemuxGetI(dec->demux_, WEBP_FF_BACKGROUND_COLOR); + dec->info_.frame_count = WebPDemuxGetI(dec->demux_, WEBP_FF_FRAME_COUNT); + + { + const int canvas_bytes = + dec->info_.canvas_width * NUM_CHANNELS * dec->info_.canvas_height; + // Note: calloc() because we fill frame with zeroes as well. + dec->curr_frame_ = WebPSafeCalloc(1ULL, canvas_bytes); + if (dec->curr_frame_ == NULL) goto Error; + dec->prev_frame_disposed_ = WebPSafeCalloc(1ULL, canvas_bytes); + if (dec->prev_frame_disposed_ == NULL) goto Error; + } + + WebPAnimDecoderReset(dec); + + return dec; + + Error: + WebPAnimDecoderDelete(dec); + return NULL; +} + +int WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, WebPAnimInfo* info) { + if (dec == NULL || info == NULL) return 0; + *info = dec->info_; + return 1; +} + +// Returns true if the frame covers the full canvas. +static int IsFullFrame(int width, int height, int canvas_width, + int canvas_height) { + return (width == canvas_width && height == canvas_height); +} + +// Clear the canvas to transparent. +static void ZeroFillCanvas(uint8_t* rgba, uint32_t canvas_width, + uint32_t canvas_height) { + memset(rgba, 0, canvas_width * NUM_CHANNELS * canvas_height); +} + +// Clear given frame rectangle to transparent. +static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset, + int y_offset, int width, int height) { + int j; + assert(width * NUM_CHANNELS <= rgba_stride); + rgba += y_offset * rgba_stride + x_offset * NUM_CHANNELS; + for (j = 0; j < height; ++j) { + memset(rgba, 0, width * NUM_CHANNELS); + rgba += rgba_stride; + } +} + +// Copy width * height pixels from 'src' to 'dst'. +static void CopyCanvas(const uint8_t* src, uint8_t* dst, + uint32_t width, uint32_t height) { + assert(src != NULL && dst != NULL); + memcpy(dst, src, width * NUM_CHANNELS * height); +} + +// Returns true if the current frame is a key-frame. +static int IsKeyFrame(const WebPIterator* const curr, + const WebPIterator* const prev, + int prev_frame_was_key_frame, + int canvas_width, int canvas_height) { + if (curr->frame_num == 1) { + return 1; + } else if ((!curr->has_alpha || curr->blend_method == WEBP_MUX_NO_BLEND) && + IsFullFrame(curr->width, curr->height, + canvas_width, canvas_height)) { + return 1; + } else { + return (prev->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) && + (IsFullFrame(prev->width, prev->height, canvas_width, + canvas_height) || + prev_frame_was_key_frame); + } +} + + +// Blend a single channel of 'src' over 'dst', given their alpha channel values. +static uint8_t BlendChannel(uint32_t src, uint8_t src_a, uint32_t dst, + uint8_t dst_a, uint32_t scale, int shift) { + const uint8_t src_channel = (src >> shift) & 0xff; + const uint8_t dst_channel = (dst >> shift) & 0xff; + const uint32_t blend_unscaled = src_channel * src_a + dst_channel * dst_a; + assert(blend_unscaled < (1ULL << 32) / scale); + return (blend_unscaled * scale) >> 24; +} + +// Blend 'src' over 'dst' assuming they are NOT pre-multiplied by alpha. +static uint32_t BlendPixel(uint32_t src, uint32_t dst) { + const uint8_t src_a = (src >> 24) & 0xff; + + if (src_a == 0) { + return dst; + } else { + const uint8_t dst_a = (dst >> 24) & 0xff; + // This is the approximate integer arithmetic for the actual formula: + // dst_factor_a = (dst_a * (255 - src_a)) / 255. + const uint8_t dst_factor_a = (dst_a * (256 - src_a)) >> 8; + const uint8_t blend_a = src_a + dst_factor_a; + const uint32_t scale = (1UL << 24) / blend_a; + + const uint8_t blend_r = + BlendChannel(src, src_a, dst, dst_factor_a, scale, 0); + const uint8_t blend_g = + BlendChannel(src, src_a, dst, dst_factor_a, scale, 8); + const uint8_t blend_b = + BlendChannel(src, src_a, dst, dst_factor_a, scale, 16); + assert(src_a + dst_factor_a < 256); + + return (blend_r << 0) | + (blend_g << 8) | + (blend_b << 16) | + ((uint32_t)blend_a << 24); + } +} + +// Returns two ranges ( pairs) at row 'canvas_y', that belong to +// 'src' but not 'dst'. A point range is empty if the corresponding width is 0. +static void FindBlendRangeAtRow(const WebPIterator* const src, + const WebPIterator* const dst, int canvas_y, + int* const left1, int* const width1, + int* const left2, int* const width2) { + const int src_max_x = src->x_offset + src->width; + const int dst_max_x = dst->x_offset + dst->width; + const int dst_max_y = dst->y_offset + dst->height; + assert(canvas_y >= src->y_offset && canvas_y < (src->y_offset + src->height)); + *left1 = -1; + *width1 = 0; + *left2 = -1; + *width2 = 0; + + if (canvas_y < dst->y_offset || canvas_y >= dst_max_y || + src->x_offset >= dst_max_x || src_max_x <= dst->x_offset) { + *left1 = src->x_offset; + *width1 = src->width; + return; + } + + if (src->x_offset < dst->x_offset) { + *left1 = src->x_offset; + *width1 = dst->x_offset - src->x_offset; + } + + if (src_max_x > dst_max_x) { + *left2 = dst_max_x; + *width2 = src_max_x - dst_max_x; + } +} + +// Blend 'num_pixels' in 'src' over 'dst'. +static void BlendPixelRow(uint32_t* const src, const uint32_t* const dst, + int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + uint32_t* const src_pixel_ptr = &src[i]; + const uint8_t src_alpha = (*src_pixel_ptr >> 24) & 0xff; + if (src_alpha != 0xff) { + const uint32_t dst_pixel = dst[i]; + *src_pixel_ptr = BlendPixel(*src_pixel_ptr, dst_pixel); + } + } +} + +int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, + uint8_t** rgba_ptr, int* timestamp_ptr) { + WebPIterator iter; + uint32_t width; + uint32_t height; + int is_key_frame; + int timestamp; + + if (dec == NULL || rgba_ptr == NULL || timestamp_ptr == NULL) return 0; + if (!WebPAnimDecoderHasMoreFrames(dec)) return 0; + + width = dec->info_.canvas_width; + height = dec->info_.canvas_height; + + // Get compressed frame. + if (!WebPDemuxGetFrame(dec->demux_, dec->next_frame_, &iter)) { + return 0; + } + timestamp = dec->prev_frame_timestamp_ + iter.duration; + + // Initialize. + is_key_frame = IsKeyFrame(&iter, &dec->prev_iter_, + dec->prev_frame_was_keyframe_, width, height); + if (is_key_frame) { + ZeroFillCanvas(dec->curr_frame_, width, height); + } else { + CopyCanvas(dec->prev_frame_disposed_, dec->curr_frame_, width, height); + } + + // Decode. + { + const uint8_t* input = iter.fragment.bytes; + const size_t input_size = iter.fragment.size; + const size_t output_offset = + (iter.y_offset * width + iter.x_offset) * NUM_CHANNELS; + uint8_t* output = dec->curr_frame_ + output_offset; + const int output_stride = NUM_CHANNELS * width; + const size_t output_size = output_stride * iter.height; + + if (WebPDecodeRGBAInto(input, input_size, output, output_size, + output_stride) == NULL) { + goto Error; + } + } + + // During the decoding of current frame, we may have set some pixels to be + // transparent (i.e. alpha < 255). However, the value of each of these + // pixels should have been determined by blending it against the value of + // that pixel in the previous frame if blending method of is WEBP_MUX_BLEND. + if (iter.frame_num > 1 && iter.blend_method == WEBP_MUX_BLEND && + !is_key_frame) { + if (dec->prev_iter_.dispose_method == WEBP_MUX_DISPOSE_NONE) { + int y; + // Blend transparent pixels with pixels in previous canvas. + for (y = 0; y < iter.height; ++y) { + const size_t offset = + (iter.y_offset + y) * width + iter.x_offset; + BlendPixelRow((uint32_t*)dec->curr_frame_ + offset, + (uint32_t*)dec->prev_frame_disposed_ + offset, + iter.width); + } + } else { + int y; + assert(dec->prev_iter_.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND); + // We need to blend a transparent pixel with its value just after + // initialization. That is, blend it with: + // * Fully transparent pixel if it belongs to prevRect <-- No-op. + // * The pixel in the previous canvas otherwise <-- Need alpha-blending. + for (y = 0; y < iter.height; ++y) { + const int canvas_y = iter.y_offset + y; + int left1, width1, left2, width2; + FindBlendRangeAtRow(&iter, &dec->prev_iter_, canvas_y, &left1, &width1, + &left2, &width2); + if (width1 > 0) { + const size_t offset1 = canvas_y * width + left1; + BlendPixelRow((uint32_t*)dec->curr_frame_ + offset1, + (uint32_t*)dec->prev_frame_disposed_ + offset1, width1); + } + if (width2 > 0) { + const size_t offset2 = canvas_y * width + left2; + BlendPixelRow((uint32_t*)dec->curr_frame_ + offset2, + (uint32_t*)dec->prev_frame_disposed_ + offset2, width2); + } + } + } + } + + // Update info of the previous frame and dispose it for the next iteration. + dec->prev_frame_timestamp_ = timestamp; + dec->prev_iter_ = iter; + dec->prev_frame_was_keyframe_ = is_key_frame; + CopyCanvas(dec->curr_frame_, dec->prev_frame_disposed_, width, height); + if (dec->prev_iter_.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { + ZeroFillFrameRect(dec->prev_frame_disposed_, width * NUM_CHANNELS, + dec->prev_iter_.x_offset, dec->prev_iter_.y_offset, + dec->prev_iter_.width, dec->prev_iter_.height); + } + ++dec->next_frame_; + + // All OK, fill in the values. + *rgba_ptr = dec->curr_frame_; + *timestamp_ptr = timestamp; + return 1; + + Error: + WebPDemuxReleaseIterator(&iter); + return 0; +} + +int WebPAnimDecoderHasMoreFrames(const WebPAnimDecoder* dec) { + if (dec == NULL) return 0; + return (dec->next_frame_ <= (int)dec->info_.frame_count); +} + +void WebPAnimDecoderReset(WebPAnimDecoder* dec) { + if (dec != NULL) { + dec->prev_frame_timestamp_ = 0; + memset(&dec->prev_iter_, 0, sizeof(dec->prev_iter_)); + dec->prev_frame_was_keyframe_ = 0; + dec->next_frame_ = 1; + } +} + +void WebPAnimDecoderDelete(WebPAnimDecoder* dec) { + if (dec != NULL) { + WebPDemuxDelete(dec->demux_); + WebPSafeFree(dec->curr_frame_); + WebPSafeFree(dec->prev_frame_disposed_); + WebPSafeFree(dec); + } +} diff --git a/src/webp/demux.h b/src/webp/demux.h index 2da3239d..b62ab5c2 100644 --- a/src/webp/demux.h +++ b/src/webp/demux.h @@ -54,7 +54,7 @@ extern "C" { #endif -#define WEBP_DEMUX_ABI_VERSION 0x0101 // MAJOR(8b) + MINOR(8b) +#define WEBP_DEMUX_ABI_VERSION 0x0102 // MAJOR(8b) + MINOR(8b) // Note: forward declaring enumerations is not allowed in (strict) C and C++, // the types are left here for reference. @@ -63,6 +63,7 @@ extern "C" { typedef struct WebPDemuxer WebPDemuxer; typedef struct WebPIterator WebPIterator; typedef struct WebPChunkIterator WebPChunkIterator; +typedef struct WebPAnimInfo WebPAnimInfo; //------------------------------------------------------------------------------ @@ -216,6 +217,98 @@ WEBP_EXTERN(int) WebPDemuxPrevChunk(WebPChunkIterator* iter); WEBP_EXTERN(void) WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter); //------------------------------------------------------------------------------ +// WebPAnimDecoder API +// +// This API allows decoding (possibly) animated WebP images. +// +// Code Example: +/* + WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data); + WebPAnimInfo anim_info; + WebPAnimDecoderGetInfo(dec, &anim_info); + for (uint32_t i = 0; i < anim_info.loop_count; ++i) { + while (WebPAnimDecoderHasMoreFrames(dec)) { + uint8_t* frame_rgba; + int timestamp; + WebPAnimDecoderGetNext(dec, &frame_rgba, ×tamp); + // ... (Render 'frame_rgba' based on 'timestamp'). + } + WebPAnimDecoderReset(dec); + } + WebPAnimDecoderDelete(dec); +*/ + +typedef struct WebPAnimDecoder WebPAnimDecoder; // Main opaque object. + +// Internal, version-checked, entry point. +WEBP_EXTERN(WebPAnimDecoder*) WebPAnimDecoderNewInternal(const WebPData*, int); + +// Creates and initializes a WebPAnimDecoder object. +// Parameters: +// webp_data - (in) WebP bitstream. This should remain unchanged during the +// lifetime of the output WebPAnimDecoder object. +// Returns: +// A pointer to the newly created WebPAnimDecoder object, or NULL in case of +// parsing/memory error. +static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew( + const WebPData* webp_data) { + return WebPAnimDecoderNewInternal(webp_data, WEBP_DEMUX_ABI_VERSION); +} + +// Global information about the animation.. +struct WebPAnimInfo { + uint32_t canvas_width; + uint32_t canvas_height; + uint32_t loop_count; + uint32_t bgcolor; + uint32_t frame_count; + uint32_t pad[4]; // padding for later use +}; + +// Get global information about the animation. +// Parameters: +// dec - (in) decoder instance to get information from. +// info - (out) global information fetched from the animation. +// Returns: +// True on success. +WEBP_EXTERN(int) WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, + WebPAnimInfo* info); + +// Fetch the next frame from 'dec' in RGBA format. This will be a fully +// reconstructed canvas of size 'canvas_width * 4 * canvas_height', and not just +// the frame sub-rectangle. +// The returned 'rgba' buffer is valid only until the next call to +// WebPAnimDecoderGetNext(), WebPAnimDecoderReset() or WebPAnimDecoderDelete(). +// Parameters: +// dec - (in/out) decoder instance from which the next frame is to be fetched. +// rgba - (out) decoded frame in RGBA format. +// timestamp - (out) timestamp of the frame in milliseconds. +// Returns: +// False if any of the arguments are NULL, or if there is a parsing or +// decoding error, or if there are no more frames. Otherwise, returns true. +WEBP_EXTERN(int) WebPAnimDecoderGetNext(WebPAnimDecoder* dec, + uint8_t** rgba, int* timestamp); + +// Check if there are more frames left to decode. +// Parameters: +// dec - (in) decoder instance to be checked. +// Returns: +// True if 'dec' is not NULL and some frames are yet to be decoded. +// Otherwise, returns false. +WEBP_EXTERN(int) WebPAnimDecoderHasMoreFrames(const WebPAnimDecoder* dec); + +// Resets the WebPAnimDecoder object, so that next call to +// WebPAnimDecoderGetNext() will restart decoding from 1st frame. This would be +// helpful when all frames need to be decoded multiple times (e.g. +// info.loop_count times) without destroying and recreating the 'dec' object. +// Parameters: +// dec - (in/out) decoder instance to be reset +WEBP_EXTERN(void) WebPAnimDecoderReset(WebPAnimDecoder* dec); + +// Deletes the WebPAnimDecoder object. +// Parameters: +// dec - (in/out) decoder instance to be deleted +WEBP_EXTERN(void) WebPAnimDecoderDelete(WebPAnimDecoder* dec); #ifdef __cplusplus } // extern "C"