diff --git a/README.mux b/README.mux index 4156f1a8..2df6039c 100644 --- a/README.mux +++ b/README.mux @@ -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. diff --git a/examples/webpmux.c b/examples/webpmux.c index 7fb90173..4647dca0 100644 --- a/examples/webpmux.c +++ b/examples/webpmux.c @@ -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, ¶ms); + 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, ¶ms); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n", + ErrorString(err), Err2); + } break; + } case FEATURE_FRGM: mux = WebPMuxNew(); diff --git a/man/webpmux.1 b/man/webpmux.1 index 5af6c8ea..5ccd5958 100644 --- a/man/webpmux.1 +++ b/man/webpmux.1 @@ -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 diff --git a/src/mux/demux.c b/src/mux/demux.c index 4d8c18ba..89cf5a8b 100644 --- a/src/mux/demux.c +++ b/src/mux/demux.c @@ -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; diff --git a/src/mux/muxedit.c b/src/mux/muxedit.c index 295368fb..e84a89fa 100644 --- a/src/mux/muxedit.c +++ b/src/mux/muxedit.c @@ -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); diff --git a/src/mux/muxi.h b/src/mux/muxi.h index 60c43c45..477bed11 100644 --- a/src/mux/muxi.h +++ b/src/mux/muxi.h @@ -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, diff --git a/src/mux/muxinternal.c b/src/mux/muxinternal.c index f1f31f90..06882bbf 100644 --- a/src/mux/muxinternal.c +++ b/src/mux/muxinternal.c @@ -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; } } diff --git a/src/mux/muxread.c b/src/mux/muxread.c index 7ae904f7..239a36e0 100644 --- a/src/mux/muxread.c +++ b/src/mux/muxread.c @@ -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; } diff --git a/src/webp/format_constants.h b/src/webp/format_constants.h index 1926d4f6..daa511f0 100644 --- a/src/webp/format_constants.h +++ b/src/webp/format_constants.h @@ -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. diff --git a/src/webp/mux.h b/src/webp/mux.h index 127a1170..b00f5fdf 100644 --- a/src/webp/mux.h +++ b/src/webp/mux.h @@ -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