Update mux code to match the spec wrt animation

- Allow a duration of 0
- Rename LOOP chunk to ANIM and add the background color field to it.
- Add a disposal method field for each animation frame.
- Modify webpmux.c binary interface to allow the input of background color
  and disposal methods. Also make '-loop' and '-bgcolor' arguments optional
  with some default values.

Change-Id: I807372a61cdb8a0d3080ae3552caf2848070bf4d
This commit is contained in:
Urvang Joshi 2012-11-01 15:34:46 -07:00
parent d9c5fbefa4
commit fa30c86323
10 changed files with 287 additions and 162 deletions

View File

@ -26,7 +26,8 @@ Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT
webpmux -set SET_OPTIONS INPUT -o OUTPUT webpmux -set SET_OPTIONS INPUT -o OUTPUT
webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT
webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT
webpmux -frame FRAME_OPTIONS [-frame...] -loop LOOP_COUNT -o OUTPUT webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]
[-bgcolor BACKGROUND_COLOR] -o OUTPUT
webpmux -info INPUT webpmux -info INPUT
webpmux [-h|-help] webpmux [-h|-help]
@ -61,12 +62,24 @@ FRAGMENT_OPTIONS(i):
FRAME_OPTIONS(i): FRAME_OPTIONS(i):
Create animation. Create animation.
file_i +xi+yi+di file_i +xi+yi+di+mi
where: 'file_i' is the i'th animation frame (WebP format), where: 'file_i' is the i'th animation frame (WebP format),
'xi','yi' specify the image offset for this frame. 'xi','yi' specify the image offset for this frame.
'di' is the pause duration before next frame. 'di' is the pause duration before next frame.
'mi' is the dispose method for this frame (0 or 1).
INPUT and OUTPUT are in WebP format. LOOP_COUNT:
Number of times to repeat the animation.
Valid range is 0 to 65535 [Default: 0 (infinite)].
BACKGROUND_COLOR:
Background color of the canvas.
A,R,G,B
where: 'A', 'R', 'G' and 'B' are integers in the range 0 to 255 specifying
the Alpha, Red, Green and Blue component values respectively
[Default: 255,255,255,255].
INPUT & OUTPUT are in WebP format.
Note: The nature of EXIF, XMP and ICC data is not checked and is assumed to be Note: The nature of EXIF, XMP and ICC data is not checked and is assumed to be
valid. valid.

View File

@ -23,11 +23,11 @@
-frgm fragment_4.webp +960+576 \ -frgm fragment_4.webp +960+576 \
-o out_fragment_container.webp -o out_fragment_container.webp
webpmux -frame anim_1.webp +0+0+0 \ webpmux -frame anim_1.webp +0+0+0+0 \
-frame anim_2.webp +25+25+100 \ -frame anim_2.webp +25+25+100+1 \
-frame anim_3.webp +50+50+100 \ -frame anim_3.webp +50+50+100+1 \
-frame anim_4.webp +0+0+100 \ -frame anim_4.webp +0+0+100+0 \
-loop 10 \ -loop 10 -bgcolor 255,255,255,255 \
-o out_animation_container.webp -o out_animation_container.webp
webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp
@ -72,8 +72,9 @@ typedef enum {
typedef enum { typedef enum {
NIL_SUBTYPE = 0, NIL_SUBTYPE = 0,
SUBTYPE_FRM, SUBTYPE_ANMF,
SUBTYPE_LOOP SUBTYPE_LOOP,
SUBTYPE_BGCOLOR
} FeatureSubType; } FeatureSubType;
typedef struct { typedef struct {
@ -140,10 +141,6 @@ static const char* ErrorString(WebPMuxError err) {
return kErrorMessages[-err]; return kErrorMessages[-err];
} }
static int IsNotCompatible(int count1, int count2) {
return (count1 > 0) != (count2 > 0);
}
#define RETURN_IF_ERROR(ERR_MSG) \ #define RETURN_IF_ERROR(ERR_MSG) \
if (err != WEBP_MUX_OK) { \ if (err != WEBP_MUX_OK) { \
fprintf(stderr, ERR_MSG); \ fprintf(stderr, ERR_MSG); \
@ -211,10 +208,11 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) {
int nFrames; int nFrames;
if (is_anim) { if (is_anim) {
int loop_count; WebPMuxAnimParams params;
err = WebPMuxGetLoopCount(mux, &loop_count); err = WebPMuxGetAnimationParams(mux, &params);
RETURN_IF_ERROR("Failed to retrieve loop count\n"); RETURN_IF_ERROR("Failed to retrieve animation parameters\n");
printf("Loop Count : %d\n", loop_count); printf("Background color : 0x%.8X Loop Count : %d\n",
params.bgcolor, params.loop_count);
} }
err = WebPMuxNumChunks(mux, id, &nFrames); err = WebPMuxNumChunks(mux, id, &nFrames);
@ -224,14 +222,14 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) {
if (nFrames > 0) { if (nFrames > 0) {
int i; int i;
printf("No.: x_offset y_offset "); printf("No.: x_offset y_offset ");
if (is_anim) printf("duration "); if (is_anim) printf("duration dispose ");
printf("image_size\n"); printf("image_size\n");
for (i = 1; i <= nFrames; i++) { for (i = 1; i <= nFrames; i++) {
WebPMuxFrameInfo frame; WebPMuxFrameInfo frame;
err = WebPMuxGetFrame(mux, i, &frame); err = WebPMuxGetFrame(mux, i, &frame);
RETURN_IF_ERROR3("Failed to retrieve %s#%d\n", type_str, i); RETURN_IF_ERROR3("Failed to retrieve %s#%d\n", type_str, i);
printf("%3d: %8d %8d ", i, frame.x_offset, frame.y_offset); printf("%3d: %8d %8d ", i, frame.x_offset, frame.y_offset);
if (is_anim) printf("%8d ", frame.duration); if (is_anim) printf("%8d %7d ", frame.duration, frame.dispose_method);
printf("%10zu\n", frame.bitstream.size); printf("%10zu\n", frame.bitstream.size);
WebPDataClear(&frame.bitstream); WebPDataClear(&frame.bitstream);
} }
@ -274,8 +272,9 @@ static void PrintHelp(void) {
printf(" webpmux -set SET_OPTIONS INPUT -o OUTPUT\n"); printf(" webpmux -set SET_OPTIONS INPUT -o OUTPUT\n");
printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n"); printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n");
printf(" webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT\n"); printf(" webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT\n");
printf(" webpmux -frame FRAME_OPTIONS [-frame...]"); printf(" webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]"
printf(" -loop LOOP_COUNT -o OUTPUT\n"); "\n");
printf(" [-bgcolor BACKGROUND_COLOR] -o OUTPUT\n");
printf(" webpmux -info INPUT\n"); printf(" webpmux -info INPUT\n");
printf(" webpmux [-h|-help]\n"); printf(" webpmux [-h|-help]\n");
@ -316,15 +315,31 @@ static void PrintHelp(void) {
printf("\n"); printf("\n");
printf("FRAME_OPTIONS(i):\n"); printf("FRAME_OPTIONS(i):\n");
printf(" Create animation.\n"); printf(" Create animation.\n");
printf(" file_i +xi+yi+di\n"); printf(" file_i +xi+yi+di+mi\n");
printf(" where: 'file_i' is the i'th animation frame (WebP format),\n"); printf(" where: 'file_i' is the i'th animation frame (WebP format),\n");
printf(" 'xi','yi' specify the image offset for this frame.\n"); printf(" 'xi','yi' specify the image offset for this frame.\n");
printf(" 'di' is the pause duration before next frame.\n"); printf(" 'di' is the pause duration before next frame.\n");
printf(" 'mi' is the dispose method for this frame (0 or 1).\n");
printf("\n");
printf("LOOP_COUNT:\n");
printf(" Number of times to repeat the animation.\n");
printf(" Valid range is 0 to 65535 [Default: 0 (infinite)].\n");
printf("\n");
printf("BACKGROUND_COLOR:\n");
printf(" Background color of the canvas.\n");
printf(" A,R,G,B\n");
printf(" where: 'A', 'R', 'G' and 'B' are integers in the range 0 to 255 "
"specifying\n");
printf(" the Alpha, Red, Green and Blue component values "
"respectively\n");
printf(" [Default: 255,255,255,255].\n");
printf("\nINPUT & OUTPUT are in WebP format.\n"); printf("\nINPUT & OUTPUT are in WebP format.\n");
printf("\nNote: The nature of EXIF, XMP and ICC data is not checked and is " printf("\nNote: The nature of EXIF, XMP and ICC data is not checked");
"assumed to be valid.\n"); printf(" and is assumed to be\nvalid.\n");
} }
static int ReadFileToWebPData(const char* const filename, static int ReadFileToWebPData(const char* const filename,
@ -379,14 +394,29 @@ static int WriteWebP(WebPMux* const mux, const char* filename) {
} }
static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) { static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) {
return (sscanf(args, "+%d+%d+%d", int dispose_method;
&info->x_offset, &info->y_offset, &info->duration) == 3); if (sscanf(args, "+%d+%d+%d+%d", &info->x_offset, &info->y_offset,
&info->duration, &dispose_method) != 4) {
return 0;
}
// Note: The sanity of the following conversion is checked by
// WebPMuxSetAnimationParams().
info->dispose_method = (WebPMuxAnimDispose)dispose_method;
return 1;
} }
static int ParseFragmentArgs(const char* args, WebPMuxFrameInfo* const info) { static int ParseFragmentArgs(const char* args, WebPMuxFrameInfo* const info) {
return (sscanf(args, "+%d+%d", &info->x_offset, &info->y_offset) == 2); return (sscanf(args, "+%d+%d", &info->x_offset, &info->y_offset) == 2);
} }
static int ParseBgcolorArgs(const char* args, uint32_t* const bgcolor) {
uint32_t a, r, g, b;
if (sscanf(args, "%u,%u,%u,%u", &a, &r, &g, &b) != 4) return 0;
if (a >= 256 || r >= 256 || g >= 256 || b >= 256) return 0;
*bgcolor = (a << 24) | (r << 16) | (g << 8) | (b << 0);
return 1;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Clean-up. // Clean-up.
@ -409,6 +439,7 @@ static int ValidateCommandLine(int argc, const char* argv[],
int num_frame_args; int num_frame_args;
int num_frgm_args; int num_frgm_args;
int num_loop_args; int num_loop_args;
int num_bgcolor_args;
int ok = 1; int ok = 1;
assert(num_feature_args != NULL); assert(num_feature_args != NULL);
@ -435,14 +466,18 @@ static int ValidateCommandLine(int argc, const char* argv[],
num_frame_args = CountOccurrences(argv, argc, "-frame"); num_frame_args = CountOccurrences(argv, argc, "-frame");
num_frgm_args = CountOccurrences(argv, argc, "-frgm"); num_frgm_args = CountOccurrences(argv, argc, "-frgm");
num_loop_args = CountOccurrences(argv, argc, "-loop"); num_loop_args = CountOccurrences(argv, argc, "-loop");
num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor");
if (num_loop_args > 1) { if (num_loop_args > 1) {
ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate); ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
} }
if (num_bgcolor_args > 1) {
ERROR_GOTO1("ERROR: Multiple background colors specified.\n", ErrValidate);
}
if (IsNotCompatible(num_frame_args, num_loop_args)) { if ((num_frame_args == 0) && (num_loop_args + num_bgcolor_args > 0)) {
ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n", ERROR_GOTO1("ERROR: Loop count and background color are relevant only in "
ErrValidate); "case of animation.\n", ErrValidate);
} }
if (num_frame_args > 0 && num_frgm_args > 0) { if (num_frame_args > 0 && num_frgm_args > 0) {
ERROR_GOTO1("ERROR: Only one of frames & fragments can be specified at a " ERROR_GOTO1("ERROR: Only one of frames & fragments can be specified at a "
@ -456,7 +491,7 @@ static int ValidateCommandLine(int argc, const char* argv[],
} else { } else {
// Multiple arguments ('set' action for animation or fragmented image). // Multiple arguments ('set' action for animation or fragmented image).
if (num_frame_args > 0) { if (num_frame_args > 0) {
*num_feature_args = num_frame_args + num_loop_args; *num_feature_args = num_frame_args + num_loop_args + num_bgcolor_args;
} else { } else {
*num_feature_args = num_frgm_args; *num_feature_args = num_frgm_args;
} }
@ -528,12 +563,12 @@ static int ParseCommandLine(int argc, const char* argv[],
} else { } else {
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
} }
arg->subtype_ = SUBTYPE_FRM; arg->subtype_ = SUBTYPE_ANMF;
arg->filename_ = argv[i + 1]; arg->filename_ = argv[i + 1];
arg->params_ = argv[i + 2]; arg->params_ = argv[i + 2];
++feature_arg_index; ++feature_arg_index;
i += 3; i += 3;
} else if (!strcmp(argv[i], "-loop")) { } else if (!strcmp(argv[i], "-loop") || !strcmp(argv[i], "-bgcolor")) {
CHECK_NUM_ARGS_LESS(2, ErrParse); CHECK_NUM_ARGS_LESS(2, ErrParse);
if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) { if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
config->action_type_ = ACTION_SET; config->action_type_ = ACTION_SET;
@ -545,7 +580,8 @@ static int ParseCommandLine(int argc, const char* argv[],
} else { } else {
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
} }
arg->subtype_ = SUBTYPE_LOOP; arg->subtype_ =
!strcmp(argv[i], "-loop") ? SUBTYPE_LOOP : SUBTYPE_BGCOLOR;
arg->params_ = argv[i + 1]; arg->params_ = argv[i + 1];
++feature_arg_index; ++feature_arg_index;
i += 2; i += 2;
@ -790,45 +826,71 @@ static int Process(const WebPMuxConfig* config) {
case ACTION_SET: case ACTION_SET:
switch (feature->type_) { switch (feature->type_) {
case FEATURE_ANMF: case FEATURE_ANMF: {
WebPMuxAnimParams params = { 0xFFFFFFFF, 0 };
mux = WebPMuxNew(); mux = WebPMuxNew();
if (mux == NULL) { if (mux == NULL) {
ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n", ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
ErrorString(WEBP_MUX_MEMORY_ERROR), Err2); ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
} }
for (index = 0; index < feature->arg_count_; ++index) { for (index = 0; index < feature->arg_count_; ++index) {
if (feature->args_[index].subtype_ == SUBTYPE_LOOP) { switch (feature->args_[index].subtype_) {
const long num = strtol(feature->args_[index].params_, NULL, 10); case SUBTYPE_BGCOLOR: {
if (num < 0) { uint32_t bgcolor;
ERROR_GOTO1("ERROR: Loop count must be non-negative.\n", Err2); ok = ParseBgcolorArgs(feature->args_[index].params_, &bgcolor);
if (!ok) {
ERROR_GOTO1("ERROR: Could not parse the background color \n",
Err2);
}
params.bgcolor = bgcolor;
break;
} }
err = WebPMuxSetLoopCount(mux, num); case SUBTYPE_LOOP: {
if (err != WEBP_MUX_OK) { const long loop_count =
ERROR_GOTO2("ERROR (%s): Could not set loop count.\n", strtol(feature->args_[index].params_, NULL, 10);
ErrorString(err), Err2); if (loop_count != (int)loop_count) {
// Note: This is only a 'necessary' condition for loop_count
// to be valid. The 'sufficient' conditioned in checked in
// WebPMuxSetAnimationParams() method called later.
ERROR_GOTO1("ERROR: Loop count must be in the range 0 to "
"65535.\n", Err2);
}
params.loop_count = (int)loop_count;
break;
} }
} else if (feature->args_[index].subtype_ == SUBTYPE_FRM) { case SUBTYPE_ANMF: {
WebPMuxFrameInfo frame; WebPMuxFrameInfo frame;
ok = ReadFileToWebPData(feature->args_[index].filename_, frame.id = WEBP_CHUNK_ANMF;
&frame.bitstream); ok = ReadFileToWebPData(feature->args_[index].filename_,
if (!ok) goto Err2; &frame.bitstream);
ok = ParseFrameArgs(feature->args_[index].params_, &frame); if (!ok) goto Err2;
if (!ok) { ok = ParseFrameArgs(feature->args_[index].params_, &frame);
if (!ok) {
WebPDataClear(&frame.bitstream);
ERROR_GOTO1("ERROR: Could not parse frame properties.\n",
Err2);
}
err = WebPMuxPushFrame(mux, &frame, 1);
WebPDataClear(&frame.bitstream); WebPDataClear(&frame.bitstream);
ERROR_GOTO1("ERROR: Could not parse frame properties.\n", Err2); if (err != WEBP_MUX_OK) {
ERROR_GOTO3("ERROR (%s): Could not add a frame at index %d."
"\n", ErrorString(err), index, Err2);
}
break;
} }
frame.id = WEBP_CHUNK_ANMF; default: {
err = WebPMuxPushFrame(mux, &frame, 1); ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
WebPDataClear(&frame.bitstream); break;
if (err != WEBP_MUX_OK) {
ERROR_GOTO3("ERROR (%s): Could not add a frame at index %d.\n",
ErrorString(err), index, Err2);
} }
} else {
ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
} }
} }
err = WebPMuxSetAnimationParams(mux, &params);
if (err != WEBP_MUX_OK) {
ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n",
ErrorString(err), Err2);
}
break; break;
}
case FEATURE_FRGM: case FEATURE_FRGM:
mux = WebPMuxNew(); mux = WebPMuxNew();

View File

@ -1,5 +1,5 @@
.\" Hey, EMACS: -*- nroff -*- .\" Hey, EMACS: -*- nroff -*-
.TH WEBPMUX 1 "January 24, 2012" .TH WEBPMUX 1 "October 23, 2012"
.SH NAME .SH NAME
webpmux \- command line tool to create WebP Mux/container file. webpmux \- command line tool to create WebP Mux/container file.
.SH SYNOPSIS .SH SYNOPSIS
@ -23,15 +23,21 @@ webpmux \- command line tool to create WebP Mux/container file.
.br .br
.B webpmux \-frgm .B webpmux \-frgm
.I FRAGMENT_OPTIONS .I FRAGMENT_OPTIONS
.B [\-frgm...] \-o .B [ \-frgm ... ] \-o
.I OUTPUT .I OUTPUT
.br .br
.B webpmux \-frame .B webpmux \-frame
.I FRAME_OPTIONS .I FRAME_OPTIONS
.B [\-frame...] \-loop .B [ \-frame ... ] [ \-loop
.I LOOP_COUNT .I LOOP_COUNT
.B \-o .B ]
.br
.RS 8
.B [ \-bgcolor
.I BACKGROUND_COLOR
.B ] \-o
.I OUTPUT .I OUTPUT
.RE
.br .br
.B webpmux \-info .B webpmux \-info
.I INPUT .I INPUT
@ -98,12 +104,21 @@ image offset for this fragment.
.SS FRAME_OPTIONS (\-frame) .SS FRAME_OPTIONS (\-frame)
.TP .TP
.B file_i +xi+yi+di .B file_i +xi+yi+di+mi
Where: 'file_i' is the i'th frame (WebP format), 'xi','yi' specify the image Where: 'file_i' is the i'th frame (WebP format), 'xi','yi' specify the image
offset for this frame and 'di' is the pause duration before next frame. offset for this frame, 'di' is the pause duration before next frame and 'mi' is
the dispose method for this frame (0 or 1).
.TP .TP
.B \-loop n .B \-loop n
Loop the frames n number of times. 0 indicates the frames should loop forever. Loop the frames n number of times. 0 indicates the frames should loop forever.
Valid range is 0 to 65535 [Default: 0 (infinite)].
.TP
.B \-bgcolor A,R,G,B
Background color of the canvas.
.br
where: 'A', 'R', 'G' and 'B' are integers in the range 0 to 255 specifying the
Alpha, Red, Green and Blue component values respectively
[Default: 255,255,255,255].
.SS INPUT .SS INPUT
.TP .TP
@ -144,7 +159,10 @@ webpmux \-get exif exif_container.webp \-o image_metadata.exif
webpmux \-strip exif exif_container.webp \-o without_exif.webp webpmux \-strip exif exif_container.webp \-o without_exif.webp
.br .br
webpmux \-frame anim_1.webp +0+0+0 \-frame anim_2.webp +50+50+0 \-loop 10 webpmux \-frame anim_1.webp +0+0+0 \-frame anim_2.webp +50+50+0 \-loop 10
\-o anim_container.webp .br
.RS 8
\-bgcolor 255,255,255,255 \-o anim_container.webp
.RE
.br .br
webpmux \-get frame 2 anim_container.webp \-o frame_2.webp webpmux \-get frame 2 anim_container.webp \-o frame_2.webp
.br .br

View File

@ -39,6 +39,7 @@ typedef struct Frame {
int x_offset_, y_offset_; int x_offset_, y_offset_;
int width_, height_; int width_, height_;
int duration_; int duration_;
WebPMuxAnimDispose dispose_method_;
int is_fragment_; // this is a frame fragment (and not a full frame). int is_fragment_; // this is a frame fragment (and not a full frame).
int frame_num_; // the referent frame number for use in assembling fragments. int frame_num_; // the referent frame number for use in assembling fragments.
int complete_; // img_components_ contains a full image. int complete_; // img_components_ contains a full image.
@ -58,6 +59,7 @@ struct WebPDemuxer {
uint32_t feature_flags_; uint32_t feature_flags_;
int canvas_width_, canvas_height_; int canvas_width_, canvas_height_;
int loop_count_; int loop_count_;
uint32_t bgcolor_;
int num_frames_; int num_frames_;
Frame* frames_; Frame* frames_;
Chunk* chunks_; // non-image chunks Chunk* chunks_; // non-image chunks
@ -290,9 +292,7 @@ static ParseStatus NewFrame(const MemBuffer* const mem,
static ParseStatus ParseFrame( static ParseStatus ParseFrame(
WebPDemuxer* const dmux, uint32_t frame_chunk_size) { WebPDemuxer* const dmux, uint32_t frame_chunk_size) {
const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG);
const uint32_t padding = (ANMF_CHUNK_SIZE & 1); const uint32_t anmf_payload_size = frame_chunk_size - ANMF_CHUNK_SIZE;
const uint32_t anmf_payload_size =
frame_chunk_size - (ANMF_CHUNK_SIZE + padding);
int added_frame = 0; int added_frame = 0;
MemBuffer* const mem = &dmux->mem_; MemBuffer* const mem = &dmux->mem_;
Frame* frame; Frame* frame;
@ -300,12 +300,12 @@ static ParseStatus ParseFrame(
NewFrame(mem, ANMF_CHUNK_SIZE, frame_chunk_size, &frame); NewFrame(mem, ANMF_CHUNK_SIZE, frame_chunk_size, &frame);
if (status != PARSE_OK) return status; if (status != PARSE_OK) return status;
frame->x_offset_ = 2 * GetLE24s(mem); frame->x_offset_ = 2 * GetLE24s(mem);
frame->y_offset_ = 2 * GetLE24s(mem); frame->y_offset_ = 2 * GetLE24s(mem);
frame->width_ = 1 + GetLE24s(mem); frame->width_ = 1 + GetLE24s(mem);
frame->height_ = 1 + GetLE24s(mem); frame->height_ = 1 + GetLE24s(mem);
frame->duration_ = 1 + GetLE24s(mem); frame->duration_ = GetLE24s(mem);
Skip(mem, padding); frame->dispose_method_ = (WebPMuxAnimDispose)(GetByte(mem) & 1);
if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) { if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) {
return PARSE_ERROR; return PARSE_ERROR;
} }
@ -331,9 +331,7 @@ static ParseStatus ParseFrame(
static ParseStatus ParseFragment(WebPDemuxer* const dmux, static ParseStatus ParseFragment(WebPDemuxer* const dmux,
uint32_t fragment_chunk_size) { uint32_t fragment_chunk_size) {
const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG); const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG);
const uint32_t padding = (FRGM_CHUNK_SIZE & 1); const uint32_t frgm_payload_size = fragment_chunk_size - FRGM_CHUNK_SIZE;
const uint32_t frgm_payload_size =
fragment_chunk_size - (FRGM_CHUNK_SIZE + padding);
int added_fragment = 0; int added_fragment = 0;
MemBuffer* const mem = &dmux->mem_; MemBuffer* const mem = &dmux->mem_;
Frame* frame; Frame* frame;
@ -344,7 +342,6 @@ static ParseStatus ParseFragment(WebPDemuxer* const dmux,
frame->is_fragment_ = 1; frame->is_fragment_ = 1;
frame->x_offset_ = 2 * GetLE24s(mem); frame->x_offset_ = 2 * GetLE24s(mem);
frame->y_offset_ = 2 * GetLE24s(mem); frame->y_offset_ = 2 * GetLE24s(mem);
Skip(mem, padding);
// Store a fragment only if the fragments flag is set and all data for this // Store a fragment only if the fragments flag is set and all data for this
// fragment is available. // fragment is available.
@ -444,7 +441,7 @@ static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) {
static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { static ParseStatus ParseVP8X(WebPDemuxer* const dmux) {
MemBuffer* const mem = &dmux->mem_; MemBuffer* const mem = &dmux->mem_;
int loop_chunks = 0; int anim_chunks = 0;
uint32_t vp8x_size; uint32_t vp8x_size;
ParseStatus status = PARSE_OK; ParseStatus status = PARSE_OK;
@ -493,15 +490,16 @@ static ParseStatus ParseVP8X(WebPDemuxer* const dmux) {
status = ParseSingleImage(dmux); status = ParseSingleImage(dmux);
break; break;
} }
case MKFOURCC('L', 'O', 'O', 'P'): { case MKFOURCC('A', 'N', 'I', 'M'): {
if (chunk_size_padded < LOOP_CHUNK_SIZE) return PARSE_ERROR; if (chunk_size_padded < ANIM_CHUNK_SIZE) return PARSE_ERROR;
if (MemDataSize(mem) < chunk_size_padded) { if (MemDataSize(mem) < chunk_size_padded) {
status = PARSE_NEED_MORE_DATA; status = PARSE_NEED_MORE_DATA;
} else if (loop_chunks == 0) { } else if (anim_chunks == 0) {
++loop_chunks; ++anim_chunks;
dmux->bgcolor_ = GetLE32(mem);
dmux->loop_count_ = GetLE16s(mem); dmux->loop_count_ = GetLE16s(mem);
Skip(mem, chunk_size_padded - LOOP_CHUNK_SIZE); Skip(mem, chunk_size_padded - ANIM_CHUNK_SIZE);
} else { } else {
store_chunk = 0; store_chunk = 0;
goto Skip; goto Skip;
@ -629,6 +627,7 @@ static int IsValidExtendedFormat(const WebPDemuxer* const dmux) {
static void InitDemux(WebPDemuxer* const dmux, const MemBuffer* const mem) { static void InitDemux(WebPDemuxer* const dmux, const MemBuffer* const mem) {
dmux->state_ = WEBP_DEMUX_PARSING_HEADER; dmux->state_ = WEBP_DEMUX_PARSING_HEADER;
dmux->loop_count_ = 1; dmux->loop_count_ = 1;
dmux->bgcolor_ = 0xFFFFFFFF; // White background by default.
dmux->canvas_width_ = -1; dmux->canvas_width_ = -1;
dmux->canvas_height_ = -1; dmux->canvas_height_ = -1;
dmux->mem_ = *mem; dmux->mem_ = *mem;
@ -696,10 +695,11 @@ uint32_t WebPDemuxGetI(const WebPDemuxer* dmux, WebPFormatFeature feature) {
if (dmux == NULL) return 0; if (dmux == NULL) return 0;
switch (feature) { switch (feature) {
case WEBP_FF_FORMAT_FLAGS: return dmux->feature_flags_; case WEBP_FF_FORMAT_FLAGS: return dmux->feature_flags_;
case WEBP_FF_CANVAS_WIDTH: return (uint32_t)dmux->canvas_width_; case WEBP_FF_CANVAS_WIDTH: return (uint32_t)dmux->canvas_width_;
case WEBP_FF_CANVAS_HEIGHT: return (uint32_t)dmux->canvas_height_; case WEBP_FF_CANVAS_HEIGHT: return (uint32_t)dmux->canvas_height_;
case WEBP_FF_LOOP_COUNT: return (uint32_t)dmux->loop_count_; case WEBP_FF_LOOP_COUNT: return (uint32_t)dmux->loop_count_;
case WEBP_FF_BACKGROUND_COLOR: return dmux->bgcolor_;
} }
return 0; return 0;
} }
@ -778,6 +778,7 @@ static int SynthesizeFrame(const WebPDemuxer* const dmux,
iter->width = fragment->width_; iter->width = fragment->width_;
iter->height = fragment->height_; iter->height = fragment->height_;
iter->duration = fragment->duration_; iter->duration = fragment->duration_;
iter->dispose_method = fragment->dispose_method_;
iter->complete = fragment->complete_; iter->complete = fragment->complete_;
iter->fragment.bytes = payload; iter->fragment.bytes = payload;
iter->fragment.size = payload_size; iter->fragment.size = payload_size;

View File

@ -47,7 +47,7 @@ static void MuxRelease(WebPMux* const mux) {
MuxImageDeleteAll(&mux->images_); MuxImageDeleteAll(&mux->images_);
DeleteAllChunks(&mux->vp8x_); DeleteAllChunks(&mux->vp8x_);
DeleteAllChunks(&mux->iccp_); DeleteAllChunks(&mux->iccp_);
DeleteAllChunks(&mux->loop_); DeleteAllChunks(&mux->anim_);
DeleteAllChunks(&mux->exif_); DeleteAllChunks(&mux->exif_);
DeleteAllChunks(&mux->xmp_); DeleteAllChunks(&mux->xmp_);
DeleteAllChunks(&mux->unknown_); DeleteAllChunks(&mux->unknown_);
@ -82,7 +82,7 @@ static WebPMuxError MuxSet(WebPMux* const mux, CHUNK_INDEX idx, uint32_t nth,
ChunkInit(&chunk); ChunkInit(&chunk);
SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_); SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_);
SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_); SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_);
SWITCH_ID_LIST(IDX_LOOP, &mux->loop_); SWITCH_ID_LIST(IDX_ANIM, &mux->anim_);
SWITCH_ID_LIST(IDX_EXIF, &mux->exif_); SWITCH_ID_LIST(IDX_EXIF, &mux->exif_);
SWITCH_ID_LIST(IDX_XMP, &mux->xmp_); SWITCH_ID_LIST(IDX_XMP, &mux->xmp_);
if (idx == IDX_UNKNOWN && data->size > TAG_SIZE) { if (idx == IDX_UNKNOWN && data->size > TAG_SIZE) {
@ -109,11 +109,10 @@ static WebPMuxError MuxAddChunk(WebPMux* const mux, uint32_t nth, uint32_t tag,
} }
// Create data for frame/tile given image data, offsets and duration. // Create data for frame/tile given image data, offsets and duration.
static WebPMuxError CreateFrameFragmentData(const WebPData* const image, static WebPMuxError CreateFrameFragmentData(
int x_offset, int y_offset, const WebPData* const image, int x_offset, int y_offset, int duration,
int duration, int is_lossless, WebPMuxAnimDispose dispose_method, int is_lossless, int is_frame,
int is_frame, WebPData* const frame_frgm) {
WebPData* const frame_frgm) {
int width; int width;
int height; int height;
uint8_t* frame_frgm_bytes; uint8_t* frame_frgm_bytes;
@ -124,7 +123,8 @@ static WebPMuxError CreateFrameFragmentData(const WebPData* const image,
VP8GetInfo(image->bytes, image->size, image->size, &width, &height); VP8GetInfo(image->bytes, image->size, image->size, &width, &height);
if (!ok) return WEBP_MUX_INVALID_ARGUMENT; if (!ok) return WEBP_MUX_INVALID_ARGUMENT;
assert(width > 0 && height > 0 && duration > 0); assert(width > 0 && height > 0 && duration >= 0);
assert(dispose_method == (dispose_method & 1));
// Note: assertion on upper bounds is done in PutLE24(). // Note: assertion on upper bounds is done in PutLE24().
frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size); frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size);
@ -136,7 +136,8 @@ static WebPMuxError CreateFrameFragmentData(const WebPData* const image,
if (is_frame) { if (is_frame) {
PutLE24(frame_frgm_bytes + 6, width - 1); PutLE24(frame_frgm_bytes + 6, width - 1);
PutLE24(frame_frgm_bytes + 9, height - 1); PutLE24(frame_frgm_bytes + 9, height - 1);
PutLE24(frame_frgm_bytes + 12, duration - 1); PutLE24(frame_frgm_bytes + 12, duration);
frame_frgm_bytes[15] = (dispose_method & 1);
} }
frame_frgm->bytes = frame_frgm_bytes; frame_frgm->bytes = frame_frgm_bytes;
@ -200,11 +201,6 @@ static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {
return DeleteChunks(chunk_list, tag); return DeleteChunks(chunk_list, tag);
} }
static WebPMuxError DeleteLoopCount(WebPMux* const mux) {
if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
return MuxDeleteAllNamedData(mux, kChunks[IDX_LOOP].tag);
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Set API(s). // Set API(s).
@ -333,16 +329,20 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame,
const int x_offset = frame->x_offset & ~1; // Snap offsets to even. const int x_offset = frame->x_offset & ~1; // Snap offsets to even.
const int y_offset = frame->y_offset & ~1; const int y_offset = frame->y_offset & ~1;
const int duration = is_frame ? frame->duration : 1 /* unused */; const int duration = is_frame ? frame->duration : 1 /* unused */;
const WebPMuxAnimDispose dispose_method =
is_frame ? frame->dispose_method : 0 /* unused */;
const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag; const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag;
WebPData frame_frgm; WebPData frame_frgm;
if (x_offset < 0 || x_offset >= MAX_POSITION_OFFSET || if (x_offset < 0 || x_offset >= MAX_POSITION_OFFSET ||
y_offset < 0 || y_offset >= MAX_POSITION_OFFSET || y_offset < 0 || y_offset >= MAX_POSITION_OFFSET ||
(duration <= 0 || duration > MAX_DURATION)) { (duration < 0 || duration >= MAX_DURATION) ||
dispose_method != (dispose_method & 1)) {
err = WEBP_MUX_INVALID_ARGUMENT; err = WEBP_MUX_INVALID_ARGUMENT;
goto Err; goto Err;
} }
err = CreateFrameFragmentData(&wpi.img_->data_, x_offset, y_offset, err = CreateFrameFragmentData(&wpi.img_->data_, x_offset, y_offset,
duration, is_lossless, is_frame, &frame_frgm); duration, dispose_method, is_lossless,
is_frame, &frame_frgm);
if (err != WEBP_MUX_OK) goto Err; if (err != WEBP_MUX_OK) goto Err;
// Add frame/fragment chunk (with copy_data = 1). // Add frame/fragment chunk (with copy_data = 1).
err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_); err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_);
@ -362,20 +362,24 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame,
return err; return err;
} }
WebPMuxError WebPMuxSetLoopCount(WebPMux* mux, int loop_count) { WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,
const WebPMuxAnimParams* params) {
WebPMuxError err; WebPMuxError err;
uint8_t data[LOOP_CHUNK_SIZE]; uint8_t data[ANIM_CHUNK_SIZE];
if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
if (loop_count >= MAX_LOOP_COUNT) return WEBP_MUX_INVALID_ARGUMENT; if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {
return WEBP_MUX_INVALID_ARGUMENT;
}
// Delete the existing LOOP chunk(s). // Delete any existing ANIM chunk(s).
err = DeleteLoopCount(mux); err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
// Add the given loop count. // Set the animation parameters.
PutLE16(data, loop_count); PutLE32(data, params->bgcolor);
return MuxAddChunk(mux, 1, kChunks[IDX_LOOP].tag, data, sizeof(data), 1); PutLE16(data + 4, params->loop_count);
return MuxAddChunk(mux, 1, kChunks[IDX_ANIM].tag, data, sizeof(data), 1);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -408,7 +412,7 @@ static WebPMuxError GetFrameFragmentInfo(
*x_offset = 2 * GetLE24(data->bytes + 0); *x_offset = 2 * GetLE24(data->bytes + 0);
*y_offset = 2 * GetLE24(data->bytes + 3); *y_offset = 2 * GetLE24(data->bytes + 3);
if (is_frame) *duration = 1 + GetLE24(data->bytes + 12); if (is_frame) *duration = GetLE24(data->bytes + 12);
return WEBP_MUX_OK; return WEBP_MUX_OK;
} }
@ -586,21 +590,21 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
uint8_t* data = NULL; uint8_t* data = NULL;
uint8_t* dst = NULL; uint8_t* dst = NULL;
int num_frames; int num_frames;
int num_loop_chunks; int num_anim_chunks;
WebPMuxError err; WebPMuxError err;
if (mux == NULL || assembled_data == NULL) { if (mux == NULL || assembled_data == NULL) {
return WEBP_MUX_INVALID_ARGUMENT; return WEBP_MUX_INVALID_ARGUMENT;
} }
// Remove LOOP chunk if unnecessary. // Remove ANIM chunk if unnecessary.
err = WebPMuxNumChunks(mux, kChunks[IDX_LOOP].id, &num_loop_chunks); err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);
if (err != WEBP_MUX_OK) return err; if (err != WEBP_MUX_OK) return err;
if (num_loop_chunks >= 1) { if (num_anim_chunks >= 1) {
err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames); err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames);
if (err != WEBP_MUX_OK) return err; if (err != WEBP_MUX_OK) return err;
if (num_frames == 0) { if (num_frames == 0) {
err = DeleteLoopCount(mux); err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
if (err != WEBP_MUX_OK) return err; if (err != WEBP_MUX_OK) return err;
} }
} }
@ -611,7 +615,7 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
// Allocate data. // Allocate data.
size = ChunksListDiskSize(mux->vp8x_) + ChunksListDiskSize(mux->iccp_) size = ChunksListDiskSize(mux->vp8x_) + ChunksListDiskSize(mux->iccp_)
+ ChunksListDiskSize(mux->loop_) + MuxImageListDiskSize(mux->images_) + ChunksListDiskSize(mux->anim_) + MuxImageListDiskSize(mux->images_)
+ ChunksListDiskSize(mux->exif_) + ChunksListDiskSize(mux->xmp_) + ChunksListDiskSize(mux->exif_) + ChunksListDiskSize(mux->xmp_)
+ ChunksListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE; + ChunksListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE;
@ -622,7 +626,7 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
dst = MuxEmitRiffHeader(data, size); dst = MuxEmitRiffHeader(data, size);
dst = ChunkListEmit(mux->vp8x_, dst); dst = ChunkListEmit(mux->vp8x_, dst);
dst = ChunkListEmit(mux->iccp_, dst); dst = ChunkListEmit(mux->iccp_, dst);
dst = ChunkListEmit(mux->loop_, dst); dst = ChunkListEmit(mux->anim_, dst);
dst = MuxImageListEmit(mux->images_, dst); dst = MuxImageListEmit(mux->images_, dst);
dst = ChunkListEmit(mux->exif_, dst); dst = ChunkListEmit(mux->exif_, dst);
dst = ChunkListEmit(mux->xmp_, dst); dst = ChunkListEmit(mux->xmp_, dst);

View File

@ -30,7 +30,7 @@ typedef struct WebPChunk WebPChunk;
struct WebPChunk { struct WebPChunk {
uint32_t tag_; uint32_t tag_;
int owner_; // True if *data_ memory is owned internally. int owner_; // True if *data_ memory is owned internally.
// VP8X, Loop, and other internally created chunks // VP8X, ANIM, and other internally created chunks
// like ANMF/FRGM are always owned. // like ANMF/FRGM are always owned.
WebPData data_; WebPData data_;
WebPChunk* next_; WebPChunk* next_;
@ -53,7 +53,7 @@ struct WebPMux {
WebPChunk* iccp_; WebPChunk* iccp_;
WebPChunk* exif_; WebPChunk* exif_;
WebPChunk* xmp_; WebPChunk* xmp_;
WebPChunk* loop_; WebPChunk* anim_;
WebPChunk* vp8x_; WebPChunk* vp8x_;
WebPChunk* unknown_; WebPChunk* unknown_;
@ -66,7 +66,7 @@ struct WebPMux {
typedef enum { typedef enum {
IDX_VP8X = 0, IDX_VP8X = 0,
IDX_ICCP, IDX_ICCP,
IDX_LOOP, IDX_ANIM,
IDX_ANMF, IDX_ANMF,
IDX_FRGM, IDX_FRGM,
IDX_ALPHA, IDX_ALPHA,

View File

@ -22,7 +22,7 @@ extern "C" {
const ChunkInfo kChunks[] = { const ChunkInfo kChunks[] = {
{ MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_CHUNK_SIZE }, { MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_CHUNK_SIZE },
{ MKFOURCC('I', 'C', 'C', 'P'), WEBP_CHUNK_ICCP, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('I', 'C', 'C', 'P'), WEBP_CHUNK_ICCP, UNDEFINED_CHUNK_SIZE },
{ MKFOURCC('L', 'O', 'O', 'P'), WEBP_CHUNK_LOOP, LOOP_CHUNK_SIZE }, { MKFOURCC('A', 'N', 'I', 'M'), WEBP_CHUNK_ANIM, ANIM_CHUNK_SIZE },
{ MKFOURCC('A', 'N', 'M', 'F'), WEBP_CHUNK_ANMF, ANMF_CHUNK_SIZE }, { MKFOURCC('A', 'N', 'M', 'F'), WEBP_CHUNK_ANMF, ANMF_CHUNK_SIZE },
{ MKFOURCC('F', 'R', 'G', 'M'), WEBP_CHUNK_FRGM, FRGM_CHUNK_SIZE }, { MKFOURCC('F', 'R', 'G', 'M'), WEBP_CHUNK_FRGM, FRGM_CHUNK_SIZE },
{ MKFOURCC('A', 'L', 'P', 'H'), WEBP_CHUNK_ALPHA, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('A', 'L', 'P', 'H'), WEBP_CHUNK_ALPHA, UNDEFINED_CHUNK_SIZE },
@ -135,7 +135,7 @@ static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth,
WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data, WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
int copy_data, uint32_t tag) { int copy_data, uint32_t tag) {
// For internally allocated chunks, always copy data & make it owner of data. // For internally allocated chunks, always copy data & make it owner of data.
if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_LOOP].tag) { if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {
copy_data = 1; copy_data = 1;
} }
@ -462,10 +462,10 @@ uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {
WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) { WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
assert(mux != NULL); assert(mux != NULL);
switch(id) { switch (id) {
case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_; case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_;
case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp_; case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp_;
case WEBP_CHUNK_LOOP: return (WebPChunk**)&mux->loop_; case WEBP_CHUNK_ANIM: return (WebPChunk**)&mux->anim_;
case WEBP_CHUNK_EXIF: return (WebPChunk**)&mux->exif_; case WEBP_CHUNK_EXIF: return (WebPChunk**)&mux->exif_;
case WEBP_CHUNK_XMP: return (WebPChunk**)&mux->xmp_; case WEBP_CHUNK_XMP: return (WebPChunk**)&mux->xmp_;
case WEBP_CHUNK_UNKNOWN: return (WebPChunk**)&mux->unknown_; case WEBP_CHUNK_UNKNOWN: return (WebPChunk**)&mux->unknown_;
@ -518,7 +518,7 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
int num_iccp; int num_iccp;
int num_exif; int num_exif;
int num_xmp; int num_xmp;
int num_loop_chunks; int num_anim;
int num_frames; int num_frames;
int num_fragments; int num_fragments;
int num_vp8x; int num_vp8x;
@ -548,19 +548,19 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp); err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);
if (err != WEBP_MUX_OK) return err; if (err != WEBP_MUX_OK) return err;
// Animation: ANIMATION_FLAG, loop chunk and frame chunk(s) are consistent. // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.
// At most one loop chunk. // At most one ANIM chunk.
err = ValidateChunk(mux, IDX_LOOP, NO_FLAG, flags, 1, &num_loop_chunks); err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);
if (err != WEBP_MUX_OK) return err; if (err != WEBP_MUX_OK) return err;
err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames); err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);
if (err != WEBP_MUX_OK) return err; if (err != WEBP_MUX_OK) return err;
{ {
const int has_animation = !!(flags & ANIMATION_FLAG); const int has_animation = !!(flags & ANIMATION_FLAG);
if (has_animation && (num_loop_chunks == 0 || num_frames == 0)) { if (has_animation && (num_anim == 0 || num_frames == 0)) {
return WEBP_MUX_INVALID_ARGUMENT; return WEBP_MUX_INVALID_ARGUMENT;
} }
if (!has_animation && (num_loop_chunks == 1 || num_frames > 0)) { if (!has_animation && (num_anim == 1 || num_frames > 0)) {
return WEBP_MUX_INVALID_ARGUMENT; return WEBP_MUX_INVALID_ARGUMENT;
} }
} }

View File

@ -41,7 +41,7 @@ static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_); SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);
SWITCH_ID_LIST(IDX_ICCP, mux->iccp_); SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);
SWITCH_ID_LIST(IDX_LOOP, mux->loop_); SWITCH_ID_LIST(IDX_ANIM, mux->anim_);
SWITCH_ID_LIST(IDX_EXIF, mux->exif_); SWITCH_ID_LIST(IDX_EXIF, mux->exif_);
SWITCH_ID_LIST(IDX_XMP, mux->xmp_); SWITCH_ID_LIST(IDX_XMP, mux->xmp_);
SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_); SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_);
@ -376,7 +376,10 @@ static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi,
// Extract info. // Extract info.
frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0); frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0);
frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3); frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3);
frame->duration = is_frame ? 1 + GetLE24(frame_frgm_data->bytes + 12) : 1; frame->duration = is_frame ? GetLE24(frame_frgm_data->bytes + 12) : 1;
frame->dispose_method =
is_frame ? (WebPMuxAnimDispose)(frame_frgm_data->bytes[15] & 1)
: WEBP_MUX_DISPOSE_NONE;
frame->id = ChunkGetIdFromTag(wpi->header_->tag_); frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
return SynthesizeBitstream(wpi, &frame->bitstream); return SynthesizeBitstream(wpi, &frame->bitstream);
} }
@ -403,16 +406,18 @@ WebPMuxError WebPMuxGetFrame(
} }
} }
WebPMuxError WebPMuxGetLoopCount(const WebPMux* mux, int* loop_count) { WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
WebPData image; WebPMuxAnimParams* params) {
WebPData anim;
WebPMuxError err; WebPMuxError err;
if (mux == NULL || loop_count == NULL) return WEBP_MUX_INVALID_ARGUMENT; if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
err = MuxGet(mux, IDX_LOOP, 1, &image); err = MuxGet(mux, IDX_ANIM, 1, &anim);
if (err != WEBP_MUX_OK) return err; if (err != WEBP_MUX_OK) return err;
if (image.size < kChunks[WEBP_CHUNK_LOOP].size) return WEBP_MUX_BAD_DATA; if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;
*loop_count = GetLE16(image.bytes); params->bgcolor = GetLE32(anim.bytes);
params->loop_count = GetLE16(anim.bytes + 4);
return WEBP_MUX_OK; return WEBP_MUX_OK;
} }

View File

@ -65,8 +65,8 @@ typedef enum {
#define CHUNK_SIZE_BYTES 4 // Size needed to store chunk's size. #define CHUNK_SIZE_BYTES 4 // Size needed to store chunk's size.
#define CHUNK_HEADER_SIZE 8 // Size of a chunk header. #define CHUNK_HEADER_SIZE 8 // Size of a chunk header.
#define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP"). #define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP").
#define ANMF_CHUNK_SIZE 15 // Size of a ANMF chunk. #define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk.
#define LOOP_CHUNK_SIZE 2 // Size of a LOOP chunk. #define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk.
#define FRGM_CHUNK_SIZE 6 // Size of a FRGM chunk. #define FRGM_CHUNK_SIZE 6 // Size of a FRGM chunk.
#define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk. #define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk.

View File

@ -59,8 +59,10 @@ typedef struct WebPData WebPData;
typedef enum WebPMuxError WebPMuxError; typedef enum WebPMuxError WebPMuxError;
typedef enum WebPFeatureFlags WebPFeatureFlags; typedef enum WebPFeatureFlags WebPFeatureFlags;
typedef enum WebPChunkId WebPChunkId; typedef enum WebPChunkId WebPChunkId;
typedef enum WebPMuxAnimDispose WebPMuxAnimDispose;
#endif #endif
typedef struct WebPMuxFrameInfo WebPMuxFrameInfo; typedef struct WebPMuxFrameInfo WebPMuxFrameInfo;
typedef struct WebPMuxAnimParams WebPMuxAnimParams;
typedef struct WebPDemuxer WebPDemuxer; typedef struct WebPDemuxer WebPDemuxer;
#if !(defined(__cplusplus) || defined(c_plusplus)) #if !(defined(__cplusplus) || defined(c_plusplus))
@ -94,7 +96,7 @@ enum WebPFeatureFlags {
enum WebPChunkId { enum WebPChunkId {
WEBP_CHUNK_VP8X, // VP8X WEBP_CHUNK_VP8X, // VP8X
WEBP_CHUNK_ICCP, // ICCP WEBP_CHUNK_ICCP, // ICCP
WEBP_CHUNK_LOOP, // LOOP WEBP_CHUNK_ANIM, // ANIM
WEBP_CHUNK_ANMF, // ANMF WEBP_CHUNK_ANMF, // ANMF
WEBP_CHUNK_FRGM, // FRGM WEBP_CHUNK_FRGM, // FRGM
WEBP_CHUNK_ALPHA, // ALPH WEBP_CHUNK_ALPHA, // ALPH
@ -220,6 +222,13 @@ WEBP_EXTERN(WebPMuxError) WebPMuxDeleteChunk(
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Images. // Images.
// Dispose method (animation only). Indicates how the area used by the current
// frame is to be treated before rendering the next frame on the canvas.
enum WebPMuxAnimDispose {
WEBP_MUX_DISPOSE_NONE, // Do not dispose.
WEBP_MUX_DISPOSE_BACKGROUND // Dispose to background color.
};
// Encapsulates data about a single frame/tile. // Encapsulates data about a single frame/tile.
struct WebPMuxFrameInfo { struct WebPMuxFrameInfo {
WebPData bitstream; // image data: can either be a raw VP8/VP8L bitstream WebPData bitstream; // image data: can either be a raw VP8/VP8L bitstream
@ -230,7 +239,8 @@ struct WebPMuxFrameInfo {
WebPChunkId id; // frame type: should be one of WEBP_CHUNK_ANMF, WebPChunkId id; // frame type: should be one of WEBP_CHUNK_ANMF,
// WEBP_CHUNK_FRGM or WEBP_CHUNK_IMAGE // WEBP_CHUNK_FRGM or WEBP_CHUNK_IMAGE
uint32_t pad[3]; // padding for later use WebPMuxAnimDispose dispose_method; // Disposal method for the frame.
uint32_t pad[2]; // padding for later use
}; };
// Sets the (non-animated and non-fragmented) image in the mux object. // Sets the (non-animated and non-fragmented) image in the mux object.
@ -300,28 +310,38 @@ WEBP_EXTERN(WebPMuxError) WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Animation. // Animation.
// Sets the animation loop count in the mux object. Any existing loop count // Animation parameters.
// value(s) will be removed. struct WebPMuxAnimParams {
uint32_t bgcolor; // Background color of the canvas stored (in MSB order) as:
// Bits 00 to 07: Alpha.
// Bits 08 to 15: Red.
// Bits 16 to 23: Green.
// Bits 24 to 31: Blue.
int loop_count; // Number of times to repeat the animation [0 = infinite].
};
// Sets the animation parameters in the mux object. Any existing ANIM chunks
// will be removed.
// Parameters: // Parameters:
// mux - (in/out) object in which loop chunk is to be set/added // mux - (in/out) object in which ANIM chunk is to be set/added
// loop_count - (in) animation loop count value. // params - (in) animation parameters.
// Note that loop_count of zero denotes infinite loop.
// Returns: // Returns:
// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL // WEBP_MUX_INVALID_ARGUMENT - if either mux or params is NULL
// WEBP_MUX_MEMORY_ERROR - on memory allocation error. // WEBP_MUX_MEMORY_ERROR - on memory allocation error.
// WEBP_MUX_OK - on success. // WEBP_MUX_OK - on success.
WEBP_EXTERN(WebPMuxError) WebPMuxSetLoopCount(WebPMux* mux, int loop_count); WEBP_EXTERN(WebPMuxError) WebPMuxSetAnimationParams(
WebPMux* mux, const WebPMuxAnimParams* params);
// Gets the animation loop count from the mux object. // Gets the animation parameters from the mux object.
// Parameters: // Parameters:
// mux - (in) object from which the loop count is to be fetched // mux - (in) object from which the animation parameters to be fetched
// loop_count - (out) the loop_count value present in the LOOP chunk // params - (out) animation parameters extracted from the ANIM chunk
// Returns: // Returns:
// WEBP_MUX_INVALID_ARGUMENT - if either of mux or loop_count is NULL // WEBP_MUX_INVALID_ARGUMENT - if either of mux or params is NULL
// WEBP_MUX_NOT_FOUND - if loop chunk is not present in mux object. // WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object.
// WEBP_MUX_OK - on success. // WEBP_MUX_OK - on success.
WEBP_EXTERN(WebPMuxError) WebPMuxGetLoopCount(const WebPMux* mux, WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams(
int* loop_count); const WebPMux* mux, WebPMuxAnimParams* params);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Misc Utilities. // Misc Utilities.
@ -413,7 +433,8 @@ enum WebPFormatFeature {
WEBP_FF_FORMAT_FLAGS, // Extended format flags present in the 'VP8X' chunk. WEBP_FF_FORMAT_FLAGS, // Extended format flags present in the 'VP8X' chunk.
WEBP_FF_CANVAS_WIDTH, WEBP_FF_CANVAS_WIDTH,
WEBP_FF_CANVAS_HEIGHT, WEBP_FF_CANVAS_HEIGHT,
WEBP_FF_LOOP_COUNT WEBP_FF_LOOP_COUNT,
WEBP_FF_BACKGROUND_COLOR
}; };
// Get the 'feature' value from the 'dmux'. // Get the 'feature' value from the 'dmux'.
@ -433,6 +454,7 @@ struct WebPIterator {
int x_offset, y_offset; // offset relative to the canvas. int x_offset, y_offset; // offset relative to the canvas.
int width, height; // dimensions of this frame or fragment. int width, height; // dimensions of this frame or fragment.
int duration; // display duration in milliseconds. int duration; // display duration in milliseconds.
WebPMuxAnimDispose dispose_method; // dispose method for the frame.
int complete; // true if 'fragment' contains a full frame. partial images int complete; // true if 'fragment' contains a full frame. partial images
// may still be decoded with the WebP incremental decoder. // may still be decoded with the WebP incremental decoder.
WebPData fragment; // The frame or fragment given by 'frame_num' and WebPData fragment; // The frame or fragment given by 'frame_num' and