mirror of
https://github.com/webmproject/libwebp.git
synced 2025-04-11 11:26:47 +02:00
gif2webp: Separate out each step into a method
We now have ReadFrame(), OptimizeFrame(), EncodeFrame() and DisposeFrame() methods. Change-Id: I522834bad18dd6a7a3ddac7c00dfd829c48248f8
This commit is contained in:
parent
bef7e9ccd1
commit
3b80bc4859
@ -34,7 +34,13 @@
|
|||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
static int transparent_index = -1; // No transparency by default.
|
// Global variables gathered in a struct.
|
||||||
|
static struct {
|
||||||
|
int transparent_index; // Index of transparent color in the color map.
|
||||||
|
int is_first_frame;
|
||||||
|
size_t count_since_key_frame; // Frames seen since the last key frame.
|
||||||
|
size_t key_frame_interval; // Max distance between key frames.
|
||||||
|
} kParams;
|
||||||
|
|
||||||
static void ClearRectangle(WebPPicture* const picture,
|
static void ClearRectangle(WebPPicture* const picture,
|
||||||
int left, int top, int width, int height) {
|
int left, int top, int width, int height) {
|
||||||
@ -104,7 +110,7 @@ static void Remap(const uint8_t* const src, const GifFileType* const gif,
|
|||||||
|
|
||||||
for (i = 0; i < len; ++i) {
|
for (i = 0; i < len; ++i) {
|
||||||
const GifColorType c = colors[src[i]];
|
const GifColorType c = colors[src[i]];
|
||||||
dst[i] = (src[i] == transparent_index) ? TRANSPARENT_COLOR
|
dst[i] = (src[i] == kParams.transparent_index) ? TRANSPARENT_COLOR
|
||||||
: c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24);
|
: c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,6 +123,8 @@ static int IsKeyFrame(const WebPPicture* const curr,
|
|||||||
int i, j;
|
int i, j;
|
||||||
int is_key_frame = 1;
|
int is_key_frame = 1;
|
||||||
|
|
||||||
|
if (kParams.is_first_frame) return 1;
|
||||||
|
|
||||||
// If previous canvas (with previous frame disposed) is all transparent,
|
// If previous canvas (with previous frame disposed) is all transparent,
|
||||||
// current frame is a key frame.
|
// current frame is a key frame.
|
||||||
for (i = 0; i < prev->width; ++i) {
|
for (i = 0; i < prev->width; ++i) {
|
||||||
@ -183,11 +191,9 @@ static void ConvertToKeyFrame(WebPPicture* const curr,
|
|||||||
rect->height = curr->height;
|
rect->height = curr->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ReadSubImage(GifFileType* gif, WebPPicture* const prev,
|
// Read the GIF image frame.
|
||||||
int is_first_frame, size_t key_frame_interval,
|
static int ReadFrame(GifFileType* const gif, WebPFrameRect* const gif_rect,
|
||||||
size_t* const count_since_key_frame,
|
WebPPicture* const sub_image, WebPPicture* const curr) {
|
||||||
WebPFrameRect* const gif_rect, WebPPicture* sub_image,
|
|
||||||
WebPMuxFrameInfo* const info, WebPPicture* const curr) {
|
|
||||||
const GifImageDesc image_desc = gif->Image;
|
const GifImageDesc image_desc = gif->Image;
|
||||||
uint32_t* dst = NULL;
|
uint32_t* dst = NULL;
|
||||||
uint8_t* tmp = NULL;
|
uint8_t* tmp = NULL;
|
||||||
@ -229,6 +235,19 @@ static int ReadSubImage(GifFileType* gif, WebPPicture* const prev,
|
|||||||
Remap(tmp, gif, dst + y * sub_image->argb_stride, rect.width);
|
Remap(tmp, gif, dst + y * sub_image->argb_stride, rect.width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ok = 1;
|
||||||
|
|
||||||
|
End:
|
||||||
|
free(tmp);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize the image frame for WebP.
|
||||||
|
static void OptimizeFrame(const WebPFrameRect* const gif_rect,
|
||||||
|
WebPPicture* const curr, WebPPicture* const prev,
|
||||||
|
WebPPicture* const sub_image,
|
||||||
|
WebPMuxFrameInfo* const info) {
|
||||||
|
WebPFrameRect rect = *gif_rect;
|
||||||
|
|
||||||
// Snap to even offsets (and adjust dimensions if needed).
|
// Snap to even offsets (and adjust dimensions if needed).
|
||||||
rect.width += (rect.x_offset & 1);
|
rect.width += (rect.x_offset & 1);
|
||||||
@ -237,17 +256,17 @@ static int ReadSubImage(GifFileType* gif, WebPPicture* const prev,
|
|||||||
rect.y_offset &= ~1;
|
rect.y_offset &= ~1;
|
||||||
|
|
||||||
// Make this a key frame if needed.
|
// Make this a key frame if needed.
|
||||||
if (is_first_frame || IsKeyFrame(curr, &rect, prev)) {
|
if (IsKeyFrame(curr, &rect, prev)) {
|
||||||
*count_since_key_frame = 0;
|
kParams.count_since_key_frame = 0;
|
||||||
} else {
|
} else {
|
||||||
++*count_since_key_frame;
|
++kParams.count_since_key_frame;
|
||||||
if (*count_since_key_frame > key_frame_interval) {
|
if (kParams.count_since_key_frame > kParams.key_frame_interval) {
|
||||||
ConvertToKeyFrame(curr, prev, &rect);
|
ConvertToKeyFrame(curr, prev, &rect);
|
||||||
*count_since_key_frame = 0;
|
kParams.count_since_key_frame = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*count_since_key_frame == 0) {
|
if (kParams.count_since_key_frame == 0) {
|
||||||
info->blend_method = WEBP_MUX_NO_BLEND; // Key frame, so no need to blend.
|
info->blend_method = WEBP_MUX_NO_BLEND; // Key frame, so no need to blend.
|
||||||
} else {
|
} else {
|
||||||
info->blend_method = WEBP_MUX_BLEND; // The blending method in GIF.
|
info->blend_method = WEBP_MUX_BLEND; // The blending method in GIF.
|
||||||
@ -266,16 +285,52 @@ static int ReadSubImage(GifFileType* gif, WebPPicture* const prev,
|
|||||||
sub_image);
|
sub_image);
|
||||||
info->x_offset = rect.x_offset;
|
info->x_offset = rect.x_offset;
|
||||||
info->y_offset = rect.y_offset;
|
info->y_offset = rect.y_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int EncodeFrame(const WebPConfig* const config,
|
||||||
|
WebPPicture* const sub_image,
|
||||||
|
WebPData* const encoded_data) {
|
||||||
|
int ok = 0;
|
||||||
|
WebPMemoryWriter memory;
|
||||||
|
if (!config->lossless) {
|
||||||
|
// We need to call BGRA variant because of the way we do Remap(). Note
|
||||||
|
// that 'sub_image' will no longer be a view and own some memory.
|
||||||
|
if (!WebPPictureImportBGRA(
|
||||||
|
sub_image, (uint8_t*)sub_image->argb,
|
||||||
|
sub_image->argb_stride * sizeof(*sub_image->argb))) {
|
||||||
|
goto End;
|
||||||
|
}
|
||||||
|
sub_image->use_argb = 0;
|
||||||
|
} else {
|
||||||
|
sub_image->use_argb = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub_image->writer = WebPMemoryWrite;
|
||||||
|
sub_image->custom_ptr = &memory;
|
||||||
|
WebPMemoryWriterInit(&memory);
|
||||||
|
if (!WebPEncode(config, sub_image)) goto End;
|
||||||
|
|
||||||
|
encoded_data->bytes = memory.mem;
|
||||||
|
encoded_data->size = memory.size;
|
||||||
ok = 1;
|
ok = 1;
|
||||||
|
|
||||||
End:
|
End:
|
||||||
free(tmp);
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void DisposeFrame(WebPMuxAnimDispose dispose_method,
|
||||||
|
const WebPFrameRect* const gif_rect,
|
||||||
|
WebPPicture* const frame, WebPPicture* const canvas) {
|
||||||
|
if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
||||||
|
ClearPicture(frame, NULL);
|
||||||
|
ClearPicture(canvas, gif_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int GetBackgroundColor(const ColorMapObject* const color_map,
|
static int GetBackgroundColor(const ColorMapObject* const color_map,
|
||||||
GifWord bgcolor_idx, uint32_t* const bgcolor) {
|
GifWord bgcolor_idx, uint32_t* const bgcolor) {
|
||||||
if (transparent_index != -1 && bgcolor_idx == transparent_index) {
|
if (kParams.transparent_index != -1 &&
|
||||||
|
bgcolor_idx == kParams.transparent_index) {
|
||||||
*bgcolor = TRANSPARENT_COLOR; // Special case.
|
*bgcolor = TRANSPARENT_COLOR; // Special case.
|
||||||
return 1;
|
return 1;
|
||||||
} else if (color_map == NULL || color_map->Colors == NULL
|
} else if (color_map == NULL || color_map->Colors == NULL
|
||||||
@ -358,9 +413,6 @@ int main(int argc, const char *argv[]) {
|
|||||||
WebPMuxFrameInfo frame;
|
WebPMuxFrameInfo frame;
|
||||||
WebPMuxAnimParams anim = { WHITE_COLOR, 0 };
|
WebPMuxAnimParams anim = { WHITE_COLOR, 0 };
|
||||||
|
|
||||||
int is_first_frame = 1;
|
|
||||||
size_t count_since_key_frame = 0; // Frames seen since the last key frame.
|
|
||||||
size_t key_frame_interval = 9; // Max distance between key frames.
|
|
||||||
int done;
|
int done;
|
||||||
int c;
|
int c;
|
||||||
int quiet = 0;
|
int quiet = 0;
|
||||||
@ -370,6 +422,12 @@ int main(int argc, const char *argv[]) {
|
|||||||
int stored_icc = 0; // Whether we have already stored an ICC profile.
|
int stored_icc = 0; // Whether we have already stored an ICC profile.
|
||||||
int stored_xmp = 0;
|
int stored_xmp = 0;
|
||||||
|
|
||||||
|
// Initialize global variables.
|
||||||
|
kParams.transparent_index = -1; // No transparency by default.
|
||||||
|
kParams.is_first_frame = 1;
|
||||||
|
kParams.key_frame_interval = 9;
|
||||||
|
kParams.count_since_key_frame = 0;
|
||||||
|
|
||||||
memset(&frame, 0, sizeof(frame));
|
memset(&frame, 0, sizeof(frame));
|
||||||
frame.id = WEBP_CHUNK_ANMF;
|
frame.id = WEBP_CHUNK_ANMF;
|
||||||
frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
|
frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
|
||||||
@ -400,8 +458,8 @@ int main(int argc, const char *argv[]) {
|
|||||||
} else if (!strcmp(argv[c], "-m") && c < argc - 1) {
|
} else if (!strcmp(argv[c], "-m") && c < argc - 1) {
|
||||||
config.method = strtol(argv[++c], NULL, 0);
|
config.method = strtol(argv[++c], NULL, 0);
|
||||||
} else if (!strcmp(argv[c], "-kmax") && c < argc - 1) {
|
} else if (!strcmp(argv[c], "-kmax") && c < argc - 1) {
|
||||||
key_frame_interval = strtoul(argv[++c], NULL, 0);
|
kParams.key_frame_interval = strtoul(argv[++c], NULL, 0);
|
||||||
if (key_frame_interval == 0) key_frame_interval = ~0;
|
if (kParams.key_frame_interval == 0) kParams.key_frame_interval = ~0;
|
||||||
} else if (!strcmp(argv[c], "-f") && c < argc - 1) {
|
} else if (!strcmp(argv[c], "-f") && c < argc - 1) {
|
||||||
config.filter_strength = strtol(argv[++c], NULL, 0);
|
config.filter_strength = strtol(argv[++c], NULL, 0);
|
||||||
} else if (!strcmp(argv[c], "-version")) {
|
} else if (!strcmp(argv[c], "-version")) {
|
||||||
@ -467,31 +525,16 @@ int main(int argc, const char *argv[]) {
|
|||||||
case IMAGE_DESC_RECORD_TYPE: {
|
case IMAGE_DESC_RECORD_TYPE: {
|
||||||
WebPPicture sub_image;
|
WebPPicture sub_image;
|
||||||
WebPFrameRect gif_rect;
|
WebPFrameRect gif_rect;
|
||||||
WebPMemoryWriter memory;
|
|
||||||
|
|
||||||
if (!DGifGetImageDesc(gif)) goto End;
|
if (!DGifGetImageDesc(gif)) goto End;
|
||||||
if (!ReadSubImage(gif, &previous, is_first_frame, key_frame_interval,
|
if (!ReadFrame(gif, &gif_rect, &sub_image, ¤t)) {
|
||||||
&count_since_key_frame, &gif_rect, &sub_image, &frame,
|
|
||||||
¤t)) {
|
|
||||||
goto End;
|
goto End;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.lossless) {
|
OptimizeFrame(&gif_rect, ¤t, &previous, &sub_image, &frame);
|
||||||
// We need to call BGRA variant because of the way we do Remap(). Note
|
|
||||||
// that 'sub_image' will no longer be a view and own some memory.
|
|
||||||
WebPPictureImportBGRA(
|
|
||||||
&sub_image, (uint8_t*)sub_image.argb,
|
|
||||||
sub_image.argb_stride * sizeof(*sub_image.argb));
|
|
||||||
sub_image.use_argb = 0;
|
|
||||||
} else {
|
|
||||||
sub_image.use_argb = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub_image.writer = WebPMemoryWrite;
|
if (!EncodeFrame(&config, &sub_image, &frame.bitstream)) {
|
||||||
sub_image.custom_ptr = &memory;
|
fprintf(stderr, "Error! Cannot encode frame as WebP\n");
|
||||||
WebPMemoryWriterInit(&memory);
|
|
||||||
if (!WebPEncode(&config, &sub_image)) {
|
|
||||||
fprintf(stderr, "Error! Cannot encode current as WebP\n");
|
|
||||||
fprintf(stderr, "Error code: %d\n", sub_image.error_code);
|
fprintf(stderr, "Error code: %d\n", sub_image.error_code);
|
||||||
goto End;
|
goto End;
|
||||||
}
|
}
|
||||||
@ -499,8 +542,6 @@ int main(int argc, const char *argv[]) {
|
|||||||
// Now we have all the info about the frame, as a Graphic Control
|
// Now we have all the info about the frame, as a Graphic Control
|
||||||
// Extension Block always appears before the Image Descriptor Block.
|
// Extension Block always appears before the Image Descriptor Block.
|
||||||
// So add the frame to mux.
|
// So add the frame to mux.
|
||||||
frame.bitstream.bytes = memory.mem;
|
|
||||||
frame.bitstream.size = memory.size;
|
|
||||||
err = WebPMuxPushFrame(mux, &frame, 1);
|
err = WebPMuxPushFrame(mux, &frame, 1);
|
||||||
if (err != WEBP_MUX_OK) {
|
if (err != WEBP_MUX_OK) {
|
||||||
fprintf(stderr, "ERROR (%s): Could not add animation frame.\n",
|
fprintf(stderr, "ERROR (%s): Could not add animation frame.\n",
|
||||||
@ -513,16 +554,14 @@ int main(int argc, const char *argv[]) {
|
|||||||
frame.x_offset, frame.y_offset,
|
frame.x_offset, frame.y_offset,
|
||||||
frame.duration);
|
frame.duration);
|
||||||
printf("dispose:%d blend:%d transparent index:%d\n",
|
printf("dispose:%d blend:%d transparent index:%d\n",
|
||||||
frame.dispose_method, frame.blend_method, transparent_index);
|
frame.dispose_method, frame.blend_method,
|
||||||
|
kParams.transparent_index);
|
||||||
}
|
}
|
||||||
WebPDataClear(&frame.bitstream);
|
WebPDataClear(&frame.bitstream);
|
||||||
WebPPictureFree(&sub_image);
|
WebPPictureFree(&sub_image);
|
||||||
is_first_frame = 0;
|
kParams.is_first_frame = 0;
|
||||||
|
|
||||||
if (frame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
DisposeFrame(frame.dispose_method, &gif_rect, ¤t, &previous);
|
||||||
ClearPicture(¤t, NULL);
|
|
||||||
ClearPicture(&previous, &gif_rect);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EXTENSION_RECORD_TYPE: {
|
case EXTENSION_RECORD_TYPE: {
|
||||||
@ -551,8 +590,9 @@ int main(int argc, const char *argv[]) {
|
|||||||
(dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND
|
(dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND
|
||||||
: WEBP_MUX_DISPOSE_NONE;
|
: WEBP_MUX_DISPOSE_NONE;
|
||||||
}
|
}
|
||||||
transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1;
|
kParams.transparent_index =
|
||||||
if (is_first_frame) {
|
(flags & GIF_TRANSPARENT_MASK) ? data[4] : -1;
|
||||||
|
if (kParams.is_first_frame) {
|
||||||
if (!GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
|
if (!GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
|
||||||
&anim.bgcolor)) {
|
&anim.bgcolor)) {
|
||||||
fprintf(stderr, "GIF decode warning: invalid background color "
|
fprintf(stderr, "GIF decode warning: invalid background color "
|
||||||
|
Loading…
x
Reference in New Issue
Block a user