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 -strip STRIP_OPTIONS INPUT -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 [-h|-help]
@ -61,12 +62,24 @@ FRAGMENT_OPTIONS(i):
FRAME_OPTIONS(i):
Create animation.
file_i +xi+yi+di
file_i +xi+yi+di+mi
where: 'file_i' is the i'th animation frame (WebP format),
'xi','yi' specify the image offset for this 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
valid.

View File

@ -23,11 +23,11 @@
-frgm fragment_4.webp +960+576 \
-o out_fragment_container.webp
webpmux -frame anim_1.webp +0+0+0 \
-frame anim_2.webp +25+25+100 \
-frame anim_3.webp +50+50+100 \
-frame anim_4.webp +0+0+100 \
-loop 10 \
webpmux -frame anim_1.webp +0+0+0+0 \
-frame anim_2.webp +25+25+100+1 \
-frame anim_3.webp +50+50+100+1 \
-frame anim_4.webp +0+0+100+0 \
-loop 10 -bgcolor 255,255,255,255 \
-o out_animation_container.webp
webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp
@ -72,8 +72,9 @@ typedef enum {
typedef enum {
NIL_SUBTYPE = 0,
SUBTYPE_FRM,
SUBTYPE_LOOP
SUBTYPE_ANMF,
SUBTYPE_LOOP,
SUBTYPE_BGCOLOR
} FeatureSubType;
typedef struct {
@ -140,10 +141,6 @@ static const char* ErrorString(WebPMuxError err) {
return kErrorMessages[-err];
}
static int IsNotCompatible(int count1, int count2) {
return (count1 > 0) != (count2 > 0);
}
#define RETURN_IF_ERROR(ERR_MSG) \
if (err != WEBP_MUX_OK) { \
fprintf(stderr, ERR_MSG); \
@ -211,10 +208,11 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) {
int nFrames;
if (is_anim) {
int loop_count;
err = WebPMuxGetLoopCount(mux, &loop_count);
RETURN_IF_ERROR("Failed to retrieve loop count\n");
printf("Loop Count : %d\n", loop_count);
WebPMuxAnimParams params;
err = WebPMuxGetAnimationParams(mux, &params);
RETURN_IF_ERROR("Failed to retrieve animation parameters\n");
printf("Background color : 0x%.8X Loop Count : %d\n",
params.bgcolor, params.loop_count);
}
err = WebPMuxNumChunks(mux, id, &nFrames);
@ -224,14 +222,14 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) {
if (nFrames > 0) {
int i;
printf("No.: x_offset y_offset ");
if (is_anim) printf("duration ");
if (is_anim) printf("duration dispose ");
printf("image_size\n");
for (i = 1; i <= nFrames; i++) {
WebPMuxFrameInfo frame;
err = WebPMuxGetFrame(mux, i, &frame);
RETURN_IF_ERROR3("Failed to retrieve %s#%d\n", type_str, i);
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);
WebPDataClear(&frame.bitstream);
}
@ -274,8 +272,9 @@ static void PrintHelp(void) {
printf(" webpmux -set SET_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 -frame FRAME_OPTIONS [-frame...]");
printf(" -loop LOOP_COUNT -o OUTPUT\n");
printf(" webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]"
"\n");
printf(" [-bgcolor BACKGROUND_COLOR] -o OUTPUT\n");
printf(" webpmux -info INPUT\n");
printf(" webpmux [-h|-help]\n");
@ -316,15 +315,31 @@ static void PrintHelp(void) {
printf("\n");
printf("FRAME_OPTIONS(i):\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(" 'xi','yi' specify the image offset for this 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("\nNote: The nature of EXIF, XMP and ICC data is not checked and is "
"assumed to be valid.\n");
printf("\nNote: The nature of EXIF, XMP and ICC data is not checked");
printf(" and is assumed to be\nvalid.\n");
}
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) {
return (sscanf(args, "+%d+%d+%d",
&info->x_offset, &info->y_offset, &info->duration) == 3);
int dispose_method;
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) {
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.
@ -409,6 +439,7 @@ static int ValidateCommandLine(int argc, const char* argv[],
int num_frame_args;
int num_frgm_args;
int num_loop_args;
int num_bgcolor_args;
int ok = 1;
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_frgm_args = CountOccurrences(argv, argc, "-frgm");
num_loop_args = CountOccurrences(argv, argc, "-loop");
num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor");
if (num_loop_args > 1) {
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)) {
ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n",
ErrValidate);
if ((num_frame_args == 0) && (num_loop_args + num_bgcolor_args > 0)) {
ERROR_GOTO1("ERROR: Loop count and background color are relevant only in "
"case of animation.\n", ErrValidate);
}
if (num_frame_args > 0 && num_frgm_args > 0) {
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 {
// Multiple arguments ('set' action for animation or fragmented image).
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 {
*num_feature_args = num_frgm_args;
}
@ -528,12 +563,12 @@ static int ParseCommandLine(int argc, const char* argv[],
} else {
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
}
arg->subtype_ = SUBTYPE_FRM;
arg->subtype_ = SUBTYPE_ANMF;
arg->filename_ = argv[i + 1];
arg->params_ = argv[i + 2];
++feature_arg_index;
i += 3;
} else if (!strcmp(argv[i], "-loop")) {
} else if (!strcmp(argv[i], "-loop") || !strcmp(argv[i], "-bgcolor")) {
CHECK_NUM_ARGS_LESS(2, ErrParse);
if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
config->action_type_ = ACTION_SET;
@ -545,7 +580,8 @@ static int ParseCommandLine(int argc, const char* argv[],
} else {
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];
++feature_arg_index;
i += 2;
@ -790,45 +826,71 @@ static int Process(const WebPMuxConfig* config) {
case ACTION_SET:
switch (feature->type_) {
case FEATURE_ANMF:
case FEATURE_ANMF: {
WebPMuxAnimParams params = { 0xFFFFFFFF, 0 };
mux = WebPMuxNew();
if (mux == NULL) {
ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
}
for (index = 0; index < feature->arg_count_; ++index) {
if (feature->args_[index].subtype_ == SUBTYPE_LOOP) {
const long num = strtol(feature->args_[index].params_, NULL, 10);
if (num < 0) {
ERROR_GOTO1("ERROR: Loop count must be non-negative.\n", Err2);
switch (feature->args_[index].subtype_) {
case SUBTYPE_BGCOLOR: {
uint32_t bgcolor;
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);
if (err != WEBP_MUX_OK) {
ERROR_GOTO2("ERROR (%s): Could not set loop count.\n",
ErrorString(err), Err2);
case SUBTYPE_LOOP: {
const long loop_count =
strtol(feature->args_[index].params_, NULL, 10);
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) {
WebPMuxFrameInfo frame;
ok = ReadFileToWebPData(feature->args_[index].filename_,
&frame.bitstream);
if (!ok) goto Err2;
ok = ParseFrameArgs(feature->args_[index].params_, &frame);
if (!ok) {
case SUBTYPE_ANMF: {
WebPMuxFrameInfo frame;
frame.id = WEBP_CHUNK_ANMF;
ok = ReadFileToWebPData(feature->args_[index].filename_,
&frame.bitstream);
if (!ok) goto Err2;
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);
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;
err = WebPMuxPushFrame(mux, &frame, 1);
WebPDataClear(&frame.bitstream);
if (err != WEBP_MUX_OK) {
ERROR_GOTO3("ERROR (%s): Could not add a frame at index %d.\n",
ErrorString(err), index, Err2);
default: {
ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
break;
}
} 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;
}
case FEATURE_FRGM:
mux = WebPMuxNew();

View File

@ -1,5 +1,5 @@
.\" Hey, EMACS: -*- nroff -*-
.TH WEBPMUX 1 "January 24, 2012"
.TH WEBPMUX 1 "October 23, 2012"
.SH NAME
webpmux \- command line tool to create WebP Mux/container file.
.SH SYNOPSIS
@ -23,15 +23,21 @@ webpmux \- command line tool to create WebP Mux/container file.
.br
.B webpmux \-frgm
.I FRAGMENT_OPTIONS
.B [\-frgm...] \-o
.B [ \-frgm ... ] \-o
.I OUTPUT
.br
.B webpmux \-frame
.I FRAME_OPTIONS
.B [\-frame...] \-loop
.B [ \-frame ... ] [ \-loop
.I LOOP_COUNT
.B \-o
.B ]
.br
.RS 8
.B [ \-bgcolor
.I BACKGROUND_COLOR
.B ] \-o
.I OUTPUT
.RE
.br
.B webpmux \-info
.I INPUT
@ -98,12 +104,21 @@ image offset for this fragment.
.SS FRAME_OPTIONS (\-frame)
.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
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
.B \-loop n
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
.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
.br
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
webpmux \-get frame 2 anim_container.webp \-o frame_2.webp
.br

View File

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

View File

@ -47,7 +47,7 @@ static void MuxRelease(WebPMux* const mux) {
MuxImageDeleteAll(&mux->images_);
DeleteAllChunks(&mux->vp8x_);
DeleteAllChunks(&mux->iccp_);
DeleteAllChunks(&mux->loop_);
DeleteAllChunks(&mux->anim_);
DeleteAllChunks(&mux->exif_);
DeleteAllChunks(&mux->xmp_);
DeleteAllChunks(&mux->unknown_);
@ -82,7 +82,7 @@ static WebPMuxError MuxSet(WebPMux* const mux, CHUNK_INDEX idx, uint32_t nth,
ChunkInit(&chunk);
SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_);
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_XMP, &mux->xmp_);
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.
static WebPMuxError CreateFrameFragmentData(const WebPData* const image,
int x_offset, int y_offset,
int duration, int is_lossless,
int is_frame,
WebPData* const frame_frgm) {
static WebPMuxError CreateFrameFragmentData(
const WebPData* const image, int x_offset, int y_offset, int duration,
WebPMuxAnimDispose dispose_method, int is_lossless, int is_frame,
WebPData* const frame_frgm) {
int width;
int height;
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);
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().
frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size);
@ -136,7 +136,8 @@ static WebPMuxError CreateFrameFragmentData(const WebPData* const image,
if (is_frame) {
PutLE24(frame_frgm_bytes + 6, width - 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;
@ -200,11 +201,6 @@ static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t 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).
@ -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 y_offset = frame->y_offset & ~1;
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;
WebPData frame_frgm;
if (x_offset < 0 || x_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;
goto Err;
}
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;
// Add frame/fragment chunk (with copy_data = 1).
err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_);
@ -362,20 +362,24 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame,
return err;
}
WebPMuxError WebPMuxSetLoopCount(WebPMux* mux, int loop_count) {
WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,
const WebPMuxAnimParams* params) {
WebPMuxError err;
uint8_t data[LOOP_CHUNK_SIZE];
uint8_t data[ANIM_CHUNK_SIZE];
if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
if (loop_count >= MAX_LOOP_COUNT) return WEBP_MUX_INVALID_ARGUMENT;
if (mux == NULL || params == NULL) 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).
err = DeleteLoopCount(mux);
// Delete any existing ANIM chunk(s).
err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
// Add the given loop count.
PutLE16(data, loop_count);
return MuxAddChunk(mux, 1, kChunks[IDX_LOOP].tag, data, sizeof(data), 1);
// Set the animation parameters.
PutLE32(data, params->bgcolor);
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);
*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;
}
@ -586,21 +590,21 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
uint8_t* data = NULL;
uint8_t* dst = NULL;
int num_frames;
int num_loop_chunks;
int num_anim_chunks;
WebPMuxError err;
if (mux == NULL || assembled_data == NULL) {
return WEBP_MUX_INVALID_ARGUMENT;
}
// Remove LOOP chunk if unnecessary.
err = WebPMuxNumChunks(mux, kChunks[IDX_LOOP].id, &num_loop_chunks);
// Remove ANIM chunk if unnecessary.
err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);
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);
if (err != WEBP_MUX_OK) return err;
if (num_frames == 0) {
err = DeleteLoopCount(mux);
err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
if (err != WEBP_MUX_OK) return err;
}
}
@ -611,7 +615,7 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
// Allocate data.
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->unknown_) + RIFF_HEADER_SIZE;
@ -622,7 +626,7 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
dst = MuxEmitRiffHeader(data, size);
dst = ChunkListEmit(mux->vp8x_, dst);
dst = ChunkListEmit(mux->iccp_, dst);
dst = ChunkListEmit(mux->loop_, dst);
dst = ChunkListEmit(mux->anim_, dst);
dst = MuxImageListEmit(mux->images_, dst);
dst = ChunkListEmit(mux->exif_, dst);
dst = ChunkListEmit(mux->xmp_, dst);

View File

@ -30,7 +30,7 @@ typedef struct WebPChunk WebPChunk;
struct WebPChunk {
uint32_t tag_;
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.
WebPData data_;
WebPChunk* next_;
@ -53,7 +53,7 @@ struct WebPMux {
WebPChunk* iccp_;
WebPChunk* exif_;
WebPChunk* xmp_;
WebPChunk* loop_;
WebPChunk* anim_;
WebPChunk* vp8x_;
WebPChunk* unknown_;
@ -66,7 +66,7 @@ struct WebPMux {
typedef enum {
IDX_VP8X = 0,
IDX_ICCP,
IDX_LOOP,
IDX_ANIM,
IDX_ANMF,
IDX_FRGM,
IDX_ALPHA,

View File

@ -22,7 +22,7 @@ extern "C" {
const ChunkInfo kChunks[] = {
{ MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_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('F', 'R', 'G', 'M'), WEBP_CHUNK_FRGM, FRGM_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,
int copy_data, uint32_t tag) {
// 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;
}
@ -462,10 +462,10 @@ uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {
WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
assert(mux != NULL);
switch(id) {
switch (id) {
case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_;
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_XMP: return (WebPChunk**)&mux->xmp_;
case WEBP_CHUNK_UNKNOWN: return (WebPChunk**)&mux->unknown_;
@ -518,7 +518,7 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
int num_iccp;
int num_exif;
int num_xmp;
int num_loop_chunks;
int num_anim;
int num_frames;
int num_fragments;
int num_vp8x;
@ -548,19 +548,19 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);
if (err != WEBP_MUX_OK) return err;
// Animation: ANIMATION_FLAG, loop chunk and frame chunk(s) are consistent.
// At most one loop chunk.
err = ValidateChunk(mux, IDX_LOOP, NO_FLAG, flags, 1, &num_loop_chunks);
// Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.
// At most one ANIM chunk.
err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);
if (err != WEBP_MUX_OK) return err;
err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);
if (err != WEBP_MUX_OK) return err;
{
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;
}
if (!has_animation && (num_loop_chunks == 1 || num_frames > 0)) {
if (!has_animation && (num_anim == 1 || num_frames > 0)) {
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_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_XMP, mux->xmp_);
SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_);
@ -376,7 +376,10 @@ static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi,
// Extract info.
frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0);
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_);
return SynthesizeBitstream(wpi, &frame->bitstream);
}
@ -403,16 +406,18 @@ WebPMuxError WebPMuxGetFrame(
}
}
WebPMuxError WebPMuxGetLoopCount(const WebPMux* mux, int* loop_count) {
WebPData image;
WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
WebPMuxAnimParams* params) {
WebPData anim;
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 (image.size < kChunks[WEBP_CHUNK_LOOP].size) return WEBP_MUX_BAD_DATA;
*loop_count = GetLE16(image.bytes);
if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;
params->bgcolor = GetLE32(anim.bytes);
params->loop_count = GetLE16(anim.bytes + 4);
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_HEADER_SIZE 8 // Size of a chunk header.
#define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP").
#define ANMF_CHUNK_SIZE 15 // Size of a ANMF chunk.
#define LOOP_CHUNK_SIZE 2 // Size of a LOOP chunk.
#define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk.
#define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk.
#define FRGM_CHUNK_SIZE 6 // Size of a FRGM 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 WebPFeatureFlags WebPFeatureFlags;
typedef enum WebPChunkId WebPChunkId;
typedef enum WebPMuxAnimDispose WebPMuxAnimDispose;
#endif
typedef struct WebPMuxFrameInfo WebPMuxFrameInfo;
typedef struct WebPMuxAnimParams WebPMuxAnimParams;
typedef struct WebPDemuxer WebPDemuxer;
#if !(defined(__cplusplus) || defined(c_plusplus))
@ -94,7 +96,7 @@ enum WebPFeatureFlags {
enum WebPChunkId {
WEBP_CHUNK_VP8X, // VP8X
WEBP_CHUNK_ICCP, // ICCP
WEBP_CHUNK_LOOP, // LOOP
WEBP_CHUNK_ANIM, // ANIM
WEBP_CHUNK_ANMF, // ANMF
WEBP_CHUNK_FRGM, // FRGM
WEBP_CHUNK_ALPHA, // ALPH
@ -220,6 +222,13 @@ WEBP_EXTERN(WebPMuxError) WebPMuxDeleteChunk(
//------------------------------------------------------------------------------
// 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.
struct WebPMuxFrameInfo {
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,
// 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.
@ -300,28 +310,38 @@ WEBP_EXTERN(WebPMuxError) WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth);
//------------------------------------------------------------------------------
// Animation.
// Sets the animation loop count in the mux object. Any existing loop count
// value(s) will be removed.
// Animation parameters.
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:
// mux - (in/out) object in which loop chunk is to be set/added
// loop_count - (in) animation loop count value.
// Note that loop_count of zero denotes infinite loop.
// mux - (in/out) object in which ANIM chunk is to be set/added
// params - (in) animation parameters.
// 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_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:
// mux - (in) object from which the loop count is to be fetched
// loop_count - (out) the loop_count value present in the LOOP chunk
// mux - (in) object from which the animation parameters to be fetched
// params - (out) animation parameters extracted from the ANIM chunk
// Returns:
// WEBP_MUX_INVALID_ARGUMENT - if either of mux or loop_count is NULL
// WEBP_MUX_NOT_FOUND - if loop chunk is not present in mux object.
// WEBP_MUX_INVALID_ARGUMENT - if either of mux or params is NULL
// WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object.
// WEBP_MUX_OK - on success.
WEBP_EXTERN(WebPMuxError) WebPMuxGetLoopCount(const WebPMux* mux,
int* loop_count);
WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams(
const WebPMux* mux, WebPMuxAnimParams* params);
//------------------------------------------------------------------------------
// Misc Utilities.
@ -413,7 +433,8 @@ enum WebPFormatFeature {
WEBP_FF_FORMAT_FLAGS, // Extended format flags present in the 'VP8X' chunk.
WEBP_FF_CANVAS_WIDTH,
WEBP_FF_CANVAS_HEIGHT,
WEBP_FF_LOOP_COUNT
WEBP_FF_LOOP_COUNT,
WEBP_FF_BACKGROUND_COLOR
};
// Get the 'feature' value from the 'dmux'.
@ -433,6 +454,7 @@ struct WebPIterator {
int x_offset, y_offset; // offset relative to the canvas.
int width, height; // dimensions of this frame or fragment.
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
// may still be decoded with the WebP incremental decoder.
WebPData fragment; // The frame or fragment given by 'frame_num' and