mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-27 22:28:22 +01:00
AnimEncoder API: Optimize single-frame animated images
Try converting them to a non-animated image and pick that one if it's smaller in size. Change-Id: Ib97438fd2a95b1bfa9b7526a0938a9d85df33a57
This commit is contained in:
parent
abbae27982
commit
9e92b6eac6
@ -14,6 +14,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "../utils/utils.h"
|
#include "../utils/utils.h"
|
||||||
|
#include "../webp/decode.h"
|
||||||
#include "../webp/format_constants.h"
|
#include "../webp/format_constants.h"
|
||||||
#include "../webp/mux.h"
|
#include "../webp/mux.h"
|
||||||
|
|
||||||
@ -38,7 +39,10 @@ struct WebPAnimEncoder {
|
|||||||
const int canvas_height_; // Canvas height.
|
const int canvas_height_; // Canvas height.
|
||||||
const WebPAnimEncoderOptions options_; // Global encoding options.
|
const WebPAnimEncoderOptions options_; // Global encoding options.
|
||||||
|
|
||||||
FrameRect prev_webp_rect; // Previous WebP frame rectangle.
|
FrameRect prev_rect; // Previous WebP frame rectangle.
|
||||||
|
WebPConfig last_config; // Cached in case a re-encode is needed.
|
||||||
|
WebPConfig last_config2; // 2nd cached config; only valid if
|
||||||
|
// 'options_.allow_mixed' is true.
|
||||||
|
|
||||||
WebPPicture* curr_canvas_; // Only pointer; we don't own memory.
|
WebPPicture* curr_canvas_; // Only pointer; we don't own memory.
|
||||||
|
|
||||||
@ -789,7 +793,7 @@ static void PickBestCandidate(WebPAnimEncoder* const enc,
|
|||||||
: WEBP_MUX_DISPOSE_BACKGROUND;
|
: WEBP_MUX_DISPOSE_BACKGROUND;
|
||||||
SetPreviousDisposeMethod(enc, prev_dispose_method);
|
SetPreviousDisposeMethod(enc, prev_dispose_method);
|
||||||
}
|
}
|
||||||
enc->prev_webp_rect = candidates[i].rect_; // save for next frame.
|
enc->prev_rect = candidates[i].rect_; // save for next frame.
|
||||||
} else {
|
} else {
|
||||||
WebPMemoryWriterClear(&candidates[i].mem_);
|
WebPMemoryWriterClear(&candidates[i].mem_);
|
||||||
candidates[i].evaluate_ = 0;
|
candidates[i].evaluate_ = 0;
|
||||||
@ -833,6 +837,8 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc, int duration,
|
|||||||
WebPConfig config_lossy = *config;
|
WebPConfig config_lossy = *config;
|
||||||
config_ll.lossless = 1;
|
config_ll.lossless = 1;
|
||||||
config_lossy.lossless = 0;
|
config_lossy.lossless = 0;
|
||||||
|
enc->last_config = *config;
|
||||||
|
enc->last_config2 = config->lossless ? config_lossy : config_ll;
|
||||||
|
|
||||||
if (!WebPPictureInit(&sub_frame_none) || !WebPPictureInit(&sub_frame_bg)) {
|
if (!WebPPictureInit(&sub_frame_none) || !WebPPictureInit(&sub_frame_bg)) {
|
||||||
return VP8_ENC_ERROR_INVALID_CONFIGURATION;
|
return VP8_ENC_ERROR_INVALID_CONFIGURATION;
|
||||||
@ -850,7 +856,7 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc, int duration,
|
|||||||
// Change-rectangle assuming previous frame was DISPOSE_BACKGROUND.
|
// Change-rectangle assuming previous frame was DISPOSE_BACKGROUND.
|
||||||
WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_;
|
WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_;
|
||||||
CopyPixels(prev_canvas, prev_canvas_disposed);
|
CopyPixels(prev_canvas, prev_canvas_disposed);
|
||||||
DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_webp_rect,
|
DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect,
|
||||||
prev_canvas_disposed);
|
prev_canvas_disposed);
|
||||||
GetSubRect(prev_canvas_disposed, curr_canvas, is_key_frame, is_first_frame,
|
GetSubRect(prev_canvas_disposed, curr_canvas, is_key_frame, is_first_frame,
|
||||||
&rect_bg, &sub_frame_bg);
|
&rect_bg, &sub_frame_bg);
|
||||||
@ -1082,10 +1088,105 @@ int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int duration,
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Bitstream assembly.
|
// Bitstream assembly.
|
||||||
|
|
||||||
static WebPMuxError OptimizeSingleFrame(WebPMux* const mux) {
|
static int DecodeFrameOntoCanvas(const WebPMuxFrameInfo* const frame,
|
||||||
// TODO(urvang): If only one frame, re-encode it as a full frame.
|
WebPPicture* const canvas) {
|
||||||
(void)mux;
|
const WebPData* const image = &frame->bitstream;
|
||||||
return WEBP_MUX_OK;
|
WebPPicture sub_image;
|
||||||
|
WebPDecoderConfig config;
|
||||||
|
WebPInitDecoderConfig(&config);
|
||||||
|
WebPUtilClearPic(canvas, NULL);
|
||||||
|
if (WebPGetFeatures(image->bytes, image->size, &config.input) !=
|
||||||
|
VP8_STATUS_OK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!WebPPictureView(canvas, frame->x_offset, frame->y_offset,
|
||||||
|
config.input.width, config.input.height, &sub_image)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
config.output.is_external_memory = 1;
|
||||||
|
config.output.colorspace = MODE_BGRA;
|
||||||
|
config.output.u.RGBA.rgba = (uint8_t*)sub_image.argb;
|
||||||
|
config.output.u.RGBA.stride = sub_image.argb_stride * 4;
|
||||||
|
config.output.u.RGBA.size = config.output.u.RGBA.stride * sub_image.height;
|
||||||
|
|
||||||
|
if (WebPDecode(image->bytes, image->size, &config) != VP8_STATUS_OK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FrameToFullCanvas(WebPAnimEncoder* const enc,
|
||||||
|
const WebPMuxFrameInfo* const frame,
|
||||||
|
WebPData* const full_image) {
|
||||||
|
WebPPicture* const canvas_buf = &enc->curr_canvas_copy_;
|
||||||
|
WebPMemoryWriter mem1, mem2;
|
||||||
|
WebPMemoryWriterInit(&mem1);
|
||||||
|
WebPMemoryWriterInit(&mem2);
|
||||||
|
|
||||||
|
if (!DecodeFrameOntoCanvas(frame, canvas_buf)) goto Err;
|
||||||
|
if (!EncodeFrame(&enc->last_config, canvas_buf, &mem1)) goto Err;
|
||||||
|
GetEncodedData(&mem1, full_image);
|
||||||
|
|
||||||
|
if (enc->options_.allow_mixed) {
|
||||||
|
if (!EncodeFrame(&enc->last_config, canvas_buf, &mem2)) goto Err;
|
||||||
|
if (mem2.size < mem1.size) {
|
||||||
|
GetEncodedData(&mem2, full_image);
|
||||||
|
WebPMemoryWriterClear(&mem1);
|
||||||
|
} else {
|
||||||
|
WebPMemoryWriterClear(&mem2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
Err:
|
||||||
|
WebPMemoryWriterClear(&mem1);
|
||||||
|
WebPMemoryWriterClear(&mem2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a single-frame animation to a non-animated image if appropriate.
|
||||||
|
// TODO(urvang): Can we pick one of the two heuristically (based on frame
|
||||||
|
// rectangle and/or presence of alpha)?
|
||||||
|
static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc,
|
||||||
|
WebPData* const webp_data) {
|
||||||
|
WebPMuxError err = WEBP_MUX_OK;
|
||||||
|
int canvas_width, canvas_height;
|
||||||
|
WebPMuxFrameInfo frame;
|
||||||
|
WebPData full_image;
|
||||||
|
WebPData webp_data2;
|
||||||
|
WebPMux* const mux = WebPMuxCreate(webp_data, 0);
|
||||||
|
if (mux == NULL) return WEBP_MUX_BAD_DATA;
|
||||||
|
assert(enc->frame_count_ == 1);
|
||||||
|
WebPDataInit(&frame.bitstream);
|
||||||
|
WebPDataInit(&full_image);
|
||||||
|
WebPDataInit(&webp_data2);
|
||||||
|
|
||||||
|
err = WebPMuxGetFrame(mux, 1, &frame);
|
||||||
|
if (err != WEBP_MUX_OK) goto End;
|
||||||
|
if (frame.id != WEBP_CHUNK_ANMF) goto End; // Non-animation: nothing to do.
|
||||||
|
err = WebPMuxGetCanvasSize(mux, &canvas_width, &canvas_height);
|
||||||
|
if (err != WEBP_MUX_OK) goto End;
|
||||||
|
if (!FrameToFullCanvas(enc, &frame, &full_image)) {
|
||||||
|
err = WEBP_MUX_BAD_DATA;
|
||||||
|
goto End;
|
||||||
|
}
|
||||||
|
err = WebPMuxSetImage(mux, &full_image, 1);
|
||||||
|
if (err != WEBP_MUX_OK) goto End;
|
||||||
|
err = WebPMuxAssemble(mux, &webp_data2);
|
||||||
|
if (err != WEBP_MUX_OK) goto End;
|
||||||
|
|
||||||
|
if (webp_data2.size < webp_data->size) { // Pick 'webp_data2' if smaller.
|
||||||
|
WebPDataClear(webp_data);
|
||||||
|
*webp_data = webp_data2;
|
||||||
|
WebPDataInit(&webp_data2);
|
||||||
|
}
|
||||||
|
|
||||||
|
End:
|
||||||
|
WebPDataClear(&frame.bitstream);
|
||||||
|
WebPDataClear(&full_image);
|
||||||
|
WebPMuxDelete(mux);
|
||||||
|
WebPDataClear(&webp_data2);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
|
int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
|
||||||
@ -1116,15 +1217,15 @@ int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
|
|||||||
err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params);
|
err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params);
|
||||||
if (err != WEBP_MUX_OK) goto Err;
|
if (err != WEBP_MUX_OK) goto Err;
|
||||||
|
|
||||||
if (enc->frame_count_ == 1) {
|
|
||||||
err = OptimizeSingleFrame(mux);
|
|
||||||
if (err != WEBP_MUX_OK) goto Err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble into a WebP bitstream.
|
// Assemble into a WebP bitstream.
|
||||||
err = WebPMuxAssemble(mux, webp_data);
|
err = WebPMuxAssemble(mux, webp_data);
|
||||||
if (err != WEBP_MUX_OK) goto Err;
|
if (err != WEBP_MUX_OK) goto Err;
|
||||||
|
|
||||||
|
if (enc->frame_count_ == 1) {
|
||||||
|
err = OptimizeSingleFrame(enc, webp_data);
|
||||||
|
if (err != WEBP_MUX_OK) goto Err;
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
Err:
|
Err:
|
||||||
|
Loading…
Reference in New Issue
Block a user