mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-26 05:38:22 +01:00
Merge changes Ibeccffc3,Id1585b16
* changes: Use 'frgm' instead of 'tile' in webpmux parameters Design change in ANMF and FRGM chunks:
This commit is contained in:
commit
d2ad4450ce
22
README.mux
22
README.mux
@ -8,9 +8,9 @@ Description:
|
|||||||
============
|
============
|
||||||
|
|
||||||
WebP Mux: library to create a WebP container object for features like
|
WebP Mux: library to create a WebP container object for features like
|
||||||
color profile, metadata, animation and tiling. A reference command line
|
color profile, metadata, animation and fragmented images. A reference command
|
||||||
tool 'webpmux' and WebP container specification 'doc/webp-container-spec.txt'
|
line tool 'webpmux' and WebP container specification
|
||||||
are also provided in this package.
|
'doc/webp-container-spec.txt' are also provided in this package.
|
||||||
|
|
||||||
WebP Mux tool:
|
WebP Mux tool:
|
||||||
==============
|
==============
|
||||||
@ -25,7 +25,7 @@ A list of options is available using the -help command line flag:
|
|||||||
Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT
|
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 -tile TILE_OPTIONS [-tile...] -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 -o OUTPUT
|
||||||
webpmux -info INPUT
|
webpmux -info INPUT
|
||||||
webpmux [-h|-help]
|
webpmux [-h|-help]
|
||||||
@ -35,7 +35,7 @@ GET_OPTIONS:
|
|||||||
icc Get ICC profile.
|
icc Get ICC profile.
|
||||||
exif Get EXIF metadata.
|
exif Get EXIF metadata.
|
||||||
xmp Get XMP metadata.
|
xmp Get XMP metadata.
|
||||||
tile n Get nth tile.
|
frgm n Get nth fragment.
|
||||||
frame n Get nth frame.
|
frame n Get nth frame.
|
||||||
|
|
||||||
SET_OPTIONS:
|
SET_OPTIONS:
|
||||||
@ -53,11 +53,11 @@ STRIP_OPTIONS:
|
|||||||
exif Strip EXIF metadata.
|
exif Strip EXIF metadata.
|
||||||
xmp Strip XMP metadata.
|
xmp Strip XMP metadata.
|
||||||
|
|
||||||
TILE_OPTIONS(i):
|
FRAGMENT_OPTIONS(i):
|
||||||
Create tiled image.
|
Create fragmented image.
|
||||||
file_i +xi+yi
|
file_i +xi+yi
|
||||||
where: 'file_i' is the i'th tile (WebP format),
|
where: 'file_i' is the i'th fragment (WebP format),
|
||||||
'xi','yi' specify the image offset for this tile.
|
'xi','yi' specify the image offset for this fragment.
|
||||||
|
|
||||||
FRAME_OPTIONS(i):
|
FRAME_OPTIONS(i):
|
||||||
Create animation.
|
Create animation.
|
||||||
@ -75,8 +75,8 @@ WebP Mux API:
|
|||||||
==============
|
==============
|
||||||
The WebP Mux API contains methods for adding data to and reading data from
|
The WebP Mux API contains methods for adding data to and reading data from
|
||||||
WebPMux (a WebP container object). This API currently supports XMP/EXIF
|
WebPMux (a WebP container object). This API currently supports XMP/EXIF
|
||||||
metadata, ICC profile, animation and tiling. Other features will be added
|
metadata, ICC profile, animation and fragmented images. Other features
|
||||||
in subsequent releases.
|
will be added in subsequent releases.
|
||||||
|
|
||||||
Example#1 (pseudo code): Creating a WebPMux object with image data, color
|
Example#1 (pseudo code): Creating a WebPMux object with image data, color
|
||||||
profile and XMP metadata.
|
profile and XMP metadata.
|
||||||
|
@ -183,7 +183,7 @@ static int Decode(int* const duration) {
|
|||||||
"Frame offsets not yet supported! Forcing offset to 0,0\n");
|
"Frame offsets not yet supported! Forcing offset to 0,0\n");
|
||||||
}
|
}
|
||||||
output_buffer->colorspace = MODE_RGBA;
|
output_buffer->colorspace = MODE_RGBA;
|
||||||
ok = (WebPDecode(iter->tile.bytes, iter->tile.size,
|
ok = (WebPDecode(iter->fragment.bytes, iter->fragment.size,
|
||||||
config) == VP8_STATUS_OK);
|
config) == VP8_STATUS_OK);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
fprintf(stderr, "Decoding of frame #%d failed!\n", iter->frame_num);
|
fprintf(stderr, "Decoding of frame #%d failed!\n", iter->frame_num);
|
||||||
@ -298,8 +298,8 @@ int main(int argc, char *argv[]) {
|
|||||||
goto Error;
|
goto Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & TILE_FLAG) {
|
if (WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & FRAGMENTS_FLAG) {
|
||||||
fprintf(stderr, "Tiling is not supported for now!\n");
|
fprintf(stderr, "Image fragments are not supported for now!\n");
|
||||||
goto Error;
|
goto Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
/* Usage examples:
|
/* Usage examples:
|
||||||
|
|
||||||
Create container WebP file:
|
Create container WebP file:
|
||||||
webpmux -tile tile_1.webp +0+0 \
|
webpmux -frgm fragment_1.webp +0+0 \
|
||||||
-tile tile_2.webp +960+0 \
|
-frgm fragment_2.webp +960+0 \
|
||||||
-tile tile_3.webp +0+576 \
|
-frgm fragment_3.webp +0+576 \
|
||||||
-tile tile_4.webp +960+576 \
|
-frgm fragment_4.webp +960+576 \
|
||||||
-o out_tile_container.webp
|
-o out_fragment_container.webp
|
||||||
|
|
||||||
webpmux -frame anim_1.webp +0+0+0 \
|
webpmux -frame anim_1.webp +0+0+0 \
|
||||||
-frame anim_2.webp +25+25+100 \
|
-frame anim_2.webp +25+25+100 \
|
||||||
@ -35,7 +35,7 @@
|
|||||||
webpmux -set xmp image_metadata.xmp in.webp -o out_xmp_container.webp
|
webpmux -set xmp image_metadata.xmp in.webp -o out_xmp_container.webp
|
||||||
|
|
||||||
Extract relevant data from WebP container file:
|
Extract relevant data from WebP container file:
|
||||||
webpmux -get tile n in.webp -o out_tile.webp
|
webpmux -get frgm n in.webp -o out_fragment.webp
|
||||||
webpmux -get frame n in.webp -o out_frame.webp
|
webpmux -get frame n in.webp -o out_frame.webp
|
||||||
webpmux -get icc in.webp -o image_profile.icc
|
webpmux -get icc in.webp -o image_profile.icc
|
||||||
webpmux -get exif in.webp -o image_metadata.exif
|
webpmux -get exif in.webp -o image_metadata.exif
|
||||||
@ -87,8 +87,8 @@ typedef enum {
|
|||||||
FEATURE_EXIF,
|
FEATURE_EXIF,
|
||||||
FEATURE_XMP,
|
FEATURE_XMP,
|
||||||
FEATURE_ICCP,
|
FEATURE_ICCP,
|
||||||
FEATURE_FRM,
|
FEATURE_ANMF,
|
||||||
FEATURE_TILE,
|
FEATURE_FRGM,
|
||||||
LAST_FEATURE
|
LAST_FEATURE
|
||||||
} FeatureType;
|
} FeatureType;
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ static const char* const kFourccList[LAST_FEATURE] = {
|
|||||||
|
|
||||||
static const char* const kDescriptions[LAST_FEATURE] = {
|
static const char* const kDescriptions[LAST_FEATURE] = {
|
||||||
NULL, "EXIF metadata", "XMP metadata", "ICC profile",
|
NULL, "EXIF metadata", "XMP metadata", "ICC profile",
|
||||||
"Animation frame", "Tile fragment"
|
"Animation frame", "Image fragment"
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -197,17 +197,17 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) {
|
|||||||
// Print the features present.
|
// Print the features present.
|
||||||
printf("Features present:");
|
printf("Features present:");
|
||||||
if (flag & ANIMATION_FLAG) printf(" animation");
|
if (flag & ANIMATION_FLAG) printf(" animation");
|
||||||
if (flag & TILE_FLAG) printf(" tiling");
|
if (flag & FRAGMENTS_FLAG) printf(" image fragments");
|
||||||
if (flag & ICCP_FLAG) printf(" icc profile");
|
if (flag & ICCP_FLAG) printf(" icc profile");
|
||||||
if (flag & EXIF_FLAG) printf(" EXIF metadata");
|
if (flag & EXIF_FLAG) printf(" EXIF metadata");
|
||||||
if (flag & XMP_FLAG) printf(" XMP metadata");
|
if (flag & XMP_FLAG) printf(" XMP metadata");
|
||||||
if (flag & ALPHA_FLAG) printf(" transparency");
|
if (flag & ALPHA_FLAG) printf(" transparency");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
if ((flag & ANIMATION_FLAG) || (flag & TILE_FLAG)) {
|
if ((flag & ANIMATION_FLAG) || (flag & FRAGMENTS_FLAG)) {
|
||||||
const int is_anim = !!(flag & ANIMATION_FLAG);
|
const int is_anim = !!(flag & ANIMATION_FLAG);
|
||||||
const WebPChunkId id = is_anim ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM;
|
const WebPChunkId id = is_anim ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM;
|
||||||
const char* const type_str = is_anim ? "frame" : "tile";
|
const char* const type_str = is_anim ? "frame" : "fragment";
|
||||||
int nFrames;
|
int nFrames;
|
||||||
|
|
||||||
if (is_anim) {
|
if (is_anim) {
|
||||||
@ -259,7 +259,7 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) {
|
|||||||
printf("Size of the XMP metadata: %zu\n", xmp.size);
|
printf("Size of the XMP metadata: %zu\n", xmp.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((flag & ALPHA_FLAG) && !(flag & (ANIMATION_FLAG | TILE_FLAG))) {
|
if ((flag & ALPHA_FLAG) && !(flag & (ANIMATION_FLAG | FRAGMENTS_FLAG))) {
|
||||||
WebPMuxFrameInfo image;
|
WebPMuxFrameInfo image;
|
||||||
err = WebPMuxGetFrame(mux, 1, &image);
|
err = WebPMuxGetFrame(mux, 1, &image);
|
||||||
RETURN_IF_ERROR("Failed to retrieve the image\n");
|
RETURN_IF_ERROR("Failed to retrieve the image\n");
|
||||||
@ -273,7 +273,7 @@ static void PrintHelp(void) {
|
|||||||
printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n");
|
printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n");
|
||||||
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 -tile TILE_OPTIONS [-tile...] -o OUTPUT\n");
|
printf(" webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT\n");
|
||||||
printf(" webpmux -frame FRAME_OPTIONS [-frame...]");
|
printf(" webpmux -frame FRAME_OPTIONS [-frame...]");
|
||||||
printf(" -loop LOOP_COUNT -o OUTPUT\n");
|
printf(" -loop LOOP_COUNT -o OUTPUT\n");
|
||||||
printf(" webpmux -info INPUT\n");
|
printf(" webpmux -info INPUT\n");
|
||||||
@ -285,7 +285,7 @@ static void PrintHelp(void) {
|
|||||||
printf(" icc Get ICC profile.\n");
|
printf(" icc Get ICC profile.\n");
|
||||||
printf(" exif Get EXIF metadata.\n");
|
printf(" exif Get EXIF metadata.\n");
|
||||||
printf(" xmp Get XMP metadata.\n");
|
printf(" xmp Get XMP metadata.\n");
|
||||||
printf(" tile n Get nth tile.\n");
|
printf(" frgm n Get nth fragment.\n");
|
||||||
printf(" frame n Get nth frame.\n");
|
printf(" frame n Get nth frame.\n");
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
@ -306,11 +306,12 @@ static void PrintHelp(void) {
|
|||||||
printf(" xmp Strip XMP metadata.\n");
|
printf(" xmp Strip XMP metadata.\n");
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("TILE_OPTIONS(i):\n");
|
printf("FRAGMENT_OPTIONS(i):\n");
|
||||||
printf(" Create tiled image.\n");
|
printf(" Create fragmented image.\n");
|
||||||
printf(" file_i +xi+yi\n");
|
printf(" file_i +xi+yi\n");
|
||||||
printf(" where: 'file_i' is the i'th tile (WebP format),\n");
|
printf(" where: 'file_i' is the i'th fragment (WebP format),\n");
|
||||||
printf(" 'xi','yi' specify the image offset for this tile.\n");
|
printf(" 'xi','yi' specify the image offset for this fragment."
|
||||||
|
"\n");
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("FRAME_OPTIONS(i):\n");
|
printf("FRAME_OPTIONS(i):\n");
|
||||||
@ -382,7 +383,7 @@ static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) {
|
|||||||
&info->x_offset, &info->y_offset, &info->duration) == 3);
|
&info->x_offset, &info->y_offset, &info->duration) == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ParseTileArgs(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,7 +407,7 @@ static void DeleteConfig(WebPMuxConfig* config) {
|
|||||||
static int ValidateCommandLine(int argc, const char* argv[],
|
static int ValidateCommandLine(int argc, const char* argv[],
|
||||||
int* num_feature_args) {
|
int* num_feature_args) {
|
||||||
int num_frame_args;
|
int num_frame_args;
|
||||||
int num_tile_args;
|
int num_frgm_args;
|
||||||
int num_loop_args;
|
int num_loop_args;
|
||||||
int ok = 1;
|
int ok = 1;
|
||||||
|
|
||||||
@ -432,7 +433,7 @@ static int ValidateCommandLine(int argc, const char* argv[],
|
|||||||
|
|
||||||
// Compound checks.
|
// Compound checks.
|
||||||
num_frame_args = CountOccurrences(argv, argc, "-frame");
|
num_frame_args = CountOccurrences(argv, argc, "-frame");
|
||||||
num_tile_args = CountOccurrences(argv, argc, "-tile");
|
num_frgm_args = CountOccurrences(argv, argc, "-frgm");
|
||||||
num_loop_args = CountOccurrences(argv, argc, "-loop");
|
num_loop_args = CountOccurrences(argv, argc, "-loop");
|
||||||
|
|
||||||
if (num_loop_args > 1) {
|
if (num_loop_args > 1) {
|
||||||
@ -443,21 +444,21 @@ static int ValidateCommandLine(int argc, const char* argv[],
|
|||||||
ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n",
|
ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n",
|
||||||
ErrValidate);
|
ErrValidate);
|
||||||
}
|
}
|
||||||
if (num_frame_args > 0 && num_tile_args > 0) {
|
if (num_frame_args > 0 && num_frgm_args > 0) {
|
||||||
ERROR_GOTO1("ERROR: Only one of frames & tiles can be specified at a time."
|
ERROR_GOTO1("ERROR: Only one of frames & fragments can be specified at a "
|
||||||
"\n", ErrValidate);
|
"time.\n", ErrValidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(ok == 1);
|
assert(ok == 1);
|
||||||
if (num_frame_args == 0 && num_tile_args == 0) {
|
if (num_frame_args == 0 && num_frgm_args == 0) {
|
||||||
// Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action).
|
// Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action).
|
||||||
*num_feature_args = 1;
|
*num_feature_args = 1;
|
||||||
} else {
|
} else {
|
||||||
// Multiple arguments ('set' action for animation or tiling).
|
// 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;
|
||||||
} else {
|
} else {
|
||||||
*num_feature_args = num_tile_args;
|
*num_feature_args = num_frgm_args;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,8 +523,8 @@ static int ParseCommandLine(int argc, const char* argv[],
|
|||||||
} else {
|
} else {
|
||||||
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
||||||
}
|
}
|
||||||
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
|
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) {
|
||||||
feature->type_ = FEATURE_FRM;
|
feature->type_ = FEATURE_ANMF;
|
||||||
} else {
|
} else {
|
||||||
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
||||||
}
|
}
|
||||||
@ -539,8 +540,8 @@ static int ParseCommandLine(int argc, const char* argv[],
|
|||||||
} else {
|
} else {
|
||||||
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
||||||
}
|
}
|
||||||
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
|
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) {
|
||||||
feature->type_ = FEATURE_FRM;
|
feature->type_ = FEATURE_ANMF;
|
||||||
} else {
|
} else {
|
||||||
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
||||||
}
|
}
|
||||||
@ -548,15 +549,15 @@ static int ParseCommandLine(int argc, const char* argv[],
|
|||||||
arg->params_ = argv[i + 1];
|
arg->params_ = argv[i + 1];
|
||||||
++feature_arg_index;
|
++feature_arg_index;
|
||||||
i += 2;
|
i += 2;
|
||||||
} else if (!strcmp(argv[i], "-tile")) {
|
} else if (!strcmp(argv[i], "-frgm")) {
|
||||||
CHECK_NUM_ARGS_LESS(3, ErrParse);
|
CHECK_NUM_ARGS_LESS(3, 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;
|
||||||
} else {
|
} else {
|
||||||
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
||||||
}
|
}
|
||||||
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_TILE) {
|
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRGM) {
|
||||||
feature->type_ = FEATURE_TILE;
|
feature->type_ = FEATURE_FRGM;
|
||||||
} else {
|
} else {
|
||||||
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
||||||
}
|
}
|
||||||
@ -607,11 +608,11 @@ static int ParseCommandLine(int argc, const char* argv[],
|
|||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
} else if ((!strcmp(argv[i], "frame") ||
|
} else if ((!strcmp(argv[i], "frame") ||
|
||||||
!strcmp(argv[i], "tile")) &&
|
!strcmp(argv[i], "frgm")) &&
|
||||||
(config->action_type_ == ACTION_GET)) {
|
(config->action_type_ == ACTION_GET)) {
|
||||||
CHECK_NUM_ARGS_LESS(2, ErrParse);
|
CHECK_NUM_ARGS_LESS(2, ErrParse);
|
||||||
feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_FRM :
|
feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_ANMF :
|
||||||
FEATURE_TILE;
|
FEATURE_FRGM;
|
||||||
arg->params_ = argv[i + 1];
|
arg->params_ = argv[i + 1];
|
||||||
++feature_arg_index;
|
++feature_arg_index;
|
||||||
i += 2;
|
i += 2;
|
||||||
@ -649,8 +650,8 @@ static int ValidateConfig(WebPMuxConfig* config) {
|
|||||||
if (config->input_ == NULL) {
|
if (config->input_ == NULL) {
|
||||||
if (config->action_type_ != ACTION_SET) {
|
if (config->action_type_ != ACTION_SET) {
|
||||||
ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
|
ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
|
||||||
} else if (feature->type_ != FEATURE_FRM &&
|
} else if (feature->type_ != FEATURE_ANMF &&
|
||||||
feature->type_ != FEATURE_TILE) {
|
feature->type_ != FEATURE_FRGM) {
|
||||||
ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
|
ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -708,8 +709,8 @@ static int InitializeConfig(int argc, const char* argv[],
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Processing.
|
// Processing.
|
||||||
|
|
||||||
static int GetFrameTile(const WebPMux* mux,
|
static int GetFrameFragment(const WebPMux* mux,
|
||||||
const WebPMuxConfig* config, int isFrame) {
|
const WebPMuxConfig* config, int isFrame) {
|
||||||
WebPMuxError err = WEBP_MUX_OK;
|
WebPMuxError err = WEBP_MUX_OK;
|
||||||
WebPMux* mux_single = NULL;
|
WebPMux* mux_single = NULL;
|
||||||
long num = 0;
|
long num = 0;
|
||||||
@ -720,7 +721,7 @@ static int GetFrameTile(const WebPMux* mux,
|
|||||||
|
|
||||||
num = strtol(config->feature_.args_[0].params_, NULL, 10);
|
num = strtol(config->feature_.args_[0].params_, NULL, 10);
|
||||||
if (num < 0) {
|
if (num < 0) {
|
||||||
ERROR_GOTO1("ERROR: Frame/Tile index must be non-negative.\n", ErrGet);
|
ERROR_GOTO1("ERROR: Frame/Fragment index must be non-negative.\n", ErrGet);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = WebPMuxGetFrame(mux, num, &info);
|
err = WebPMuxGetFrame(mux, num, &info);
|
||||||
@ -764,10 +765,10 @@ static int Process(const WebPMuxConfig* config) {
|
|||||||
ok = CreateMux(config->input_, &mux);
|
ok = CreateMux(config->input_, &mux);
|
||||||
if (!ok) goto Err2;
|
if (!ok) goto Err2;
|
||||||
switch (feature->type_) {
|
switch (feature->type_) {
|
||||||
case FEATURE_FRM:
|
case FEATURE_ANMF:
|
||||||
case FEATURE_TILE:
|
case FEATURE_FRGM:
|
||||||
ok = GetFrameTile(mux, config,
|
ok = GetFrameFragment(mux, config,
|
||||||
(feature->type_ == FEATURE_FRM) ? 1 : 0);
|
(feature->type_ == FEATURE_ANMF) ? 1 : 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FEATURE_ICCP:
|
case FEATURE_ICCP:
|
||||||
@ -789,7 +790,7 @@ static int Process(const WebPMuxConfig* config) {
|
|||||||
|
|
||||||
case ACTION_SET:
|
case ACTION_SET:
|
||||||
switch (feature->type_) {
|
switch (feature->type_) {
|
||||||
case FEATURE_FRM:
|
case FEATURE_ANMF:
|
||||||
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",
|
||||||
@ -829,27 +830,28 @@ static int Process(const WebPMuxConfig* config) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FEATURE_TILE:
|
case FEATURE_FRGM:
|
||||||
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) {
|
||||||
WebPMuxFrameInfo tile;
|
WebPMuxFrameInfo frgm;
|
||||||
|
frgm.id = WEBP_CHUNK_FRGM;
|
||||||
ok = ReadFileToWebPData(feature->args_[index].filename_,
|
ok = ReadFileToWebPData(feature->args_[index].filename_,
|
||||||
&tile.bitstream);
|
&frgm.bitstream);
|
||||||
if (!ok) goto Err2;
|
if (!ok) goto Err2;
|
||||||
ok = ParseTileArgs(feature->args_[index].params_, &tile);
|
ok = ParseFragmentArgs(feature->args_[index].params_, &frgm);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
WebPDataClear(&tile.bitstream);
|
WebPDataClear(&frgm.bitstream);
|
||||||
ERROR_GOTO1("ERROR: Could not parse tile properties.\n", Err2);
|
ERROR_GOTO1("ERROR: Could not parse fragment properties.\n",
|
||||||
|
Err2);
|
||||||
}
|
}
|
||||||
tile.id = WEBP_CHUNK_FRGM;
|
err = WebPMuxPushFrame(mux, &frgm, 1);
|
||||||
err = WebPMuxPushFrame(mux, &tile, 1);
|
WebPDataClear(&frgm.bitstream);
|
||||||
WebPDataClear(&tile.bitstream);
|
|
||||||
if (err != WEBP_MUX_OK) {
|
if (err != WEBP_MUX_OK) {
|
||||||
ERROR_GOTO3("ERROR (%s): Could not add a tile at index %d.\n",
|
ERROR_GOTO3("ERROR (%s): Could not add a fragment at index %d.\n",
|
||||||
ErrorString(err), index, Err2);
|
ErrorString(err), index, Err2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,9 @@ webpmux \- command line tool to create WebP Mux/container file.
|
|||||||
.B \-o
|
.B \-o
|
||||||
.I OUTPUT
|
.I OUTPUT
|
||||||
.br
|
.br
|
||||||
.B webpmux \-tile
|
.B webpmux \-frgm
|
||||||
.I TILE_OPTIONS
|
.I FRAGMENT_OPTIONS
|
||||||
.B [\-tile...] \-o
|
.B [\-frgm...] \-o
|
||||||
.I OUTPUT
|
.I OUTPUT
|
||||||
.br
|
.br
|
||||||
.B webpmux \-frame
|
.B webpmux \-frame
|
||||||
@ -56,8 +56,8 @@ Get EXIF metadata.
|
|||||||
.B xmp
|
.B xmp
|
||||||
Get XMP metadata.
|
Get XMP metadata.
|
||||||
.TP
|
.TP
|
||||||
.B tile n
|
.B frgm n
|
||||||
Get nth tile.
|
Get nth fragment.
|
||||||
.TP
|
.TP
|
||||||
.B frame n
|
.B frame n
|
||||||
Get nth frame.
|
Get nth frame.
|
||||||
@ -90,11 +90,11 @@ Strip EXIF metadata.
|
|||||||
.B xmp
|
.B xmp
|
||||||
Strip XMP metadata.
|
Strip XMP metadata.
|
||||||
|
|
||||||
.SS TILE_OPTIONS (\-tile)
|
.SS FRAGMENT_OPTIONS (\-frgm)
|
||||||
.TP
|
.TP
|
||||||
.B file_i +xi+yi
|
.B file_i +xi+yi
|
||||||
Where: 'file_i' is the i'th tile (WebP format) and 'xi','yi' specify the image
|
Where: 'file_i' is the i'th fragment (WebP format) and 'xi','yi' specify the
|
||||||
offset for this tile.
|
image offset for this fragment.
|
||||||
|
|
||||||
.SS FRAME_OPTIONS (\-frame)
|
.SS FRAME_OPTIONS (\-frame)
|
||||||
.TP
|
.TP
|
||||||
|
@ -40,8 +40,8 @@ extern "C" {
|
|||||||
// 20..23 VP8X flags bit-map corresponding to the chunk-types present.
|
// 20..23 VP8X flags bit-map corresponding to the chunk-types present.
|
||||||
// 24..26 Width of the Canvas Image.
|
// 24..26 Width of the Canvas Image.
|
||||||
// 27..29 Height of the Canvas Image.
|
// 27..29 Height of the Canvas Image.
|
||||||
// There can be extra chunks after the "VP8X" chunk (ICCP, TILE, FRM, VP8,
|
// There can be extra chunks after the "VP8X" chunk (ICCP, FRGM, ANMF, VP8,
|
||||||
// XMP, EXIF ...)
|
// VP8L, XMP, EXIF ...)
|
||||||
// All sizes are in little-endian order.
|
// All sizes are in little-endian order.
|
||||||
// Note: chunk data size must be padded to multiple of 2 when written.
|
// Note: chunk data size must be padded to multiple of 2 when written.
|
||||||
|
|
||||||
|
144
src/mux/demux.c
144
src/mux/demux.c
@ -39,8 +39,8 @@ typedef struct Frame {
|
|||||||
int x_offset_, y_offset_;
|
int x_offset_, y_offset_;
|
||||||
int width_, height_;
|
int width_, height_;
|
||||||
int duration_;
|
int duration_;
|
||||||
int is_tile_; // this is an image fragment from a tile.
|
int is_fragment_; // this is a frame fragment (and not a full frame).
|
||||||
int frame_num_; // the referent frame number for use in assembling tiles.
|
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.
|
||||||
ChunkData img_components_[2]; // 0=VP8{,L} 1=ALPH
|
ChunkData img_components_[2]; // 0=VP8{,L} 1=ALPH
|
||||||
struct Frame* next_;
|
struct Frame* next_;
|
||||||
@ -194,11 +194,11 @@ static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store image bearing chunks to 'frame'.
|
// Store image bearing chunks to 'frame'.
|
||||||
static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem,
|
static ParseStatus StoreFrame(int frame_num, uint32_t min_size,
|
||||||
Frame* const frame) {
|
MemBuffer* const mem, Frame* const frame) {
|
||||||
int alpha_chunks = 0;
|
int alpha_chunks = 0;
|
||||||
int image_chunks = 0;
|
int image_chunks = 0;
|
||||||
int done = (MemDataSize(mem) < CHUNK_HEADER_SIZE);
|
int done = (MemDataSize(mem) < min_size);
|
||||||
ParseStatus status = PARSE_OK;
|
ParseStatus status = PARSE_OK;
|
||||||
|
|
||||||
if (done) return PARSE_NEED_MORE_DATA;
|
if (done) return PARSE_NEED_MORE_DATA;
|
||||||
@ -275,10 +275,10 @@ static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem,
|
|||||||
// Returns PARSE_OK on success with *frame pointing to the new Frame.
|
// Returns PARSE_OK on success with *frame pointing to the new Frame.
|
||||||
// Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise.
|
// Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise.
|
||||||
static ParseStatus NewFrame(const MemBuffer* const mem,
|
static ParseStatus NewFrame(const MemBuffer* const mem,
|
||||||
uint32_t min_size, uint32_t expected_size,
|
uint32_t min_size, uint32_t actual_size,
|
||||||
uint32_t actual_size, Frame** frame) {
|
Frame** frame) {
|
||||||
if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR;
|
if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR;
|
||||||
if (actual_size < expected_size) return PARSE_ERROR;
|
if (actual_size < min_size) return PARSE_ERROR;
|
||||||
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
|
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
|
||||||
|
|
||||||
*frame = (Frame*)calloc(1, sizeof(**frame));
|
*frame = (Frame*)calloc(1, sizeof(**frame));
|
||||||
@ -290,12 +290,14 @@ 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 min_size = frame_chunk_size + CHUNK_HEADER_SIZE;
|
const uint32_t padding = (ANMF_CHUNK_SIZE & 1);
|
||||||
|
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;
|
||||||
ParseStatus status =
|
ParseStatus status =
|
||||||
NewFrame(mem, min_size, 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);
|
||||||
@ -303,14 +305,14 @@ static ParseStatus ParseFrame(
|
|||||||
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_ = 1 + GetLE24s(mem);
|
||||||
Skip(mem, frame_chunk_size - ANMF_CHUNK_SIZE); // skip any trailing data.
|
Skip(mem, padding);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store a (potentially partial) frame only if the animation flag is set
|
// Store a frame only if the animation flag is set and all data for this frame
|
||||||
// and there is some data in 'frame'.
|
// is available.
|
||||||
status = StoreFrame(dmux->num_frames_ + 1, mem, frame);
|
status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame);
|
||||||
if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) {
|
if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) {
|
||||||
added_frame = AddFrame(dmux, frame);
|
added_frame = AddFrame(dmux, frame);
|
||||||
if (added_frame) {
|
if (added_frame) {
|
||||||
@ -325,33 +327,36 @@ static ParseStatus ParseFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse a 'FRGM' chunk and any image bearing chunks that immediately follow.
|
// Parse a 'FRGM' chunk and any image bearing chunks that immediately follow.
|
||||||
// 'tile_chunk_size' is the previously validated, padded chunk size.
|
// 'fragment_chunk_size' is the previously validated, padded chunk size.
|
||||||
static ParseStatus ParseTile(WebPDemuxer* const dmux,
|
static ParseStatus ParseFragment(WebPDemuxer* const dmux,
|
||||||
uint32_t tile_chunk_size) {
|
uint32_t fragment_chunk_size) {
|
||||||
const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG);
|
const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG);
|
||||||
const uint32_t min_size = tile_chunk_size + CHUNK_HEADER_SIZE;
|
const uint32_t padding = (FRGM_CHUNK_SIZE & 1);
|
||||||
int added_tile = 0;
|
const uint32_t frgm_payload_size =
|
||||||
|
fragment_chunk_size - (FRGM_CHUNK_SIZE + padding);
|
||||||
|
int added_fragment = 0;
|
||||||
MemBuffer* const mem = &dmux->mem_;
|
MemBuffer* const mem = &dmux->mem_;
|
||||||
Frame* frame;
|
Frame* frame;
|
||||||
ParseStatus status =
|
ParseStatus status =
|
||||||
NewFrame(mem, min_size, FRGM_CHUNK_SIZE, tile_chunk_size, &frame);
|
NewFrame(mem, FRGM_CHUNK_SIZE, fragment_chunk_size, &frame);
|
||||||
if (status != PARSE_OK) return status;
|
if (status != PARSE_OK) return status;
|
||||||
|
|
||||||
frame->is_tile_ = 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, tile_chunk_size - FRGM_CHUNK_SIZE); // skip any trailing data.
|
Skip(mem, padding);
|
||||||
|
|
||||||
// Store a (potentially partial) tile only if the tile flag is set
|
// Store a fragment only if the fragments flag is set and all data for this
|
||||||
// and the tile contains some data.
|
// fragment is available.
|
||||||
status = StoreFrame(dmux->num_frames_, mem, frame);
|
status = StoreFrame(dmux->num_frames_, frgm_payload_size, mem, frame);
|
||||||
if (status != PARSE_ERROR && has_tiles && frame->frame_num_ > 0) {
|
if (status != PARSE_ERROR && has_fragments && frame->frame_num_ > 0) {
|
||||||
// Note num_frames_ is incremented only when all tiles have been consumed.
|
// Note num_frames_ is incremented only when all fragments have been
|
||||||
added_tile = AddFrame(dmux, frame);
|
// consumed.
|
||||||
if (!added_tile) status = PARSE_ERROR;
|
added_fragment = AddFrame(dmux, frame);
|
||||||
|
if (!added_fragment) status = PARSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!added_tile) free(frame);
|
if (!added_fragment) free(frame);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,7 +416,9 @@ static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) {
|
|||||||
frame = (Frame*)calloc(1, sizeof(*frame));
|
frame = (Frame*)calloc(1, sizeof(*frame));
|
||||||
if (frame == NULL) return PARSE_ERROR;
|
if (frame == NULL) return PARSE_ERROR;
|
||||||
|
|
||||||
status = StoreFrame(1, &dmux->mem_, frame);
|
// For the single image case, we allow parsing of a partial frame. But we need
|
||||||
|
// at least CHUNK_HEADER_SIZE for parsing.
|
||||||
|
status = StoreFrame(1, CHUNK_HEADER_SIZE, &dmux->mem_, frame);
|
||||||
if (status != PARSE_ERROR) {
|
if (status != PARSE_ERROR) {
|
||||||
const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG);
|
const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG);
|
||||||
// Clear any alpha when the alpha flag is missing.
|
// Clear any alpha when the alpha flag is missing.
|
||||||
@ -507,7 +514,7 @@ static ParseStatus ParseVP8X(WebPDemuxer* const dmux) {
|
|||||||
}
|
}
|
||||||
case MKFOURCC('F', 'R', 'G', 'M'): {
|
case MKFOURCC('F', 'R', 'G', 'M'): {
|
||||||
if (dmux->num_frames_ == 0) dmux->num_frames_ = 1;
|
if (dmux->num_frames_ == 0) dmux->num_frames_ = 1;
|
||||||
status = ParseTile(dmux, chunk_size_padded);
|
status = ParseFragment(dmux, chunk_size_padded);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MKFOURCC('I', 'C', 'C', 'P'): {
|
case MKFOURCC('I', 'C', 'C', 'P'): {
|
||||||
@ -565,7 +572,7 @@ static int IsValidSimpleFormat(const WebPDemuxer* const dmux) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int IsValidExtendedFormat(const WebPDemuxer* const dmux) {
|
static int IsValidExtendedFormat(const WebPDemuxer* const dmux) {
|
||||||
const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG);
|
const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG);
|
||||||
const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG);
|
const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG);
|
||||||
const Frame* f;
|
const Frame* f;
|
||||||
|
|
||||||
@ -577,15 +584,15 @@ static int IsValidExtendedFormat(const WebPDemuxer* const dmux) {
|
|||||||
|
|
||||||
for (f = dmux->frames_; f != NULL; f = f->next_) {
|
for (f = dmux->frames_; f != NULL; f = f->next_) {
|
||||||
const int cur_frame_set = f->frame_num_;
|
const int cur_frame_set = f->frame_num_;
|
||||||
int frame_count = 0, tile_count = 0;
|
int frame_count = 0, fragment_count = 0;
|
||||||
|
|
||||||
// Check frame properties and if the image is composed of tiles that each
|
// Check frame properties and if the image is composed of fragments that
|
||||||
// fragment came from a tile.
|
// each fragment came from a fragment.
|
||||||
for (; f != NULL && f->frame_num_ == cur_frame_set; f = f->next_) {
|
for (; f != NULL && f->frame_num_ == cur_frame_set; f = f->next_) {
|
||||||
const ChunkData* const image = f->img_components_;
|
const ChunkData* const image = f->img_components_;
|
||||||
const ChunkData* const alpha = f->img_components_ + 1;
|
const ChunkData* const alpha = f->img_components_ + 1;
|
||||||
|
|
||||||
if (!has_tiles && f->is_tile_) return 0;
|
if (!has_fragments && f->is_fragment_) return 0;
|
||||||
if (!has_frames && f->frame_num_ > 1) return 0;
|
if (!has_frames && f->frame_num_ > 1) return 0;
|
||||||
if (f->x_offset_ < 0 || f->y_offset_ < 0) return 0;
|
if (f->x_offset_ < 0 || f->y_offset_ < 0) return 0;
|
||||||
if (f->complete_) {
|
if (f->complete_) {
|
||||||
@ -606,11 +613,11 @@ static int IsValidExtendedFormat(const WebPDemuxer* const dmux) {
|
|||||||
if (f->next_ != NULL) return 0;
|
if (f->next_ != NULL) return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
tile_count += f->is_tile_;
|
fragment_count += f->is_fragment_;
|
||||||
++frame_count;
|
++frame_count;
|
||||||
}
|
}
|
||||||
if (!has_tiles && frame_count > 1) return 0;
|
if (!has_fragments && frame_count > 1) return 0;
|
||||||
if (tile_count > 0 && frame_count != tile_count) return 0;
|
if (fragment_count > 0 && frame_count != fragment_count) return 0;
|
||||||
if (f == NULL) break;
|
if (f == NULL) break;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
@ -700,7 +707,8 @@ uint32_t WebPDemuxGetI(const WebPDemuxer* dmux, WebPFormatFeature feature) {
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Frame iteration
|
// Frame iteration
|
||||||
|
|
||||||
// Find the first 'frame_num' frame. There may be multiple in a tiled frame.
|
// Find the first 'frame_num' frame. There may be multiple such frames in a
|
||||||
|
// fragmented frame.
|
||||||
static const Frame* GetFrame(const WebPDemuxer* const dmux, int frame_num) {
|
static const Frame* GetFrame(const WebPDemuxer* const dmux, int frame_num) {
|
||||||
const Frame* f;
|
const Frame* f;
|
||||||
for (f = dmux->frames_; f != NULL; f = f->next_) {
|
for (f = dmux->frames_; f != NULL; f = f->next_) {
|
||||||
@ -709,19 +717,19 @@ static const Frame* GetFrame(const WebPDemuxer* const dmux, int frame_num) {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns tile 'tile_num' and the total count.
|
// Returns fragment 'fragment_num' and the total count.
|
||||||
static const Frame* GetTile(
|
static const Frame* GetFragment(
|
||||||
const Frame* const frame_set, int tile_num, int* const count) {
|
const Frame* const frame_set, int fragment_num, int* const count) {
|
||||||
const int this_frame = frame_set->frame_num_;
|
const int this_frame = frame_set->frame_num_;
|
||||||
const Frame* f = frame_set;
|
const Frame* f = frame_set;
|
||||||
const Frame* tile = NULL;
|
const Frame* fragment = NULL;
|
||||||
int total;
|
int total;
|
||||||
|
|
||||||
for (total = 0; f != NULL && f->frame_num_ == this_frame; f = f->next_) {
|
for (total = 0; f != NULL && f->frame_num_ == this_frame; f = f->next_) {
|
||||||
if (++total == tile_num) tile = f;
|
if (++total == fragment_num) fragment = f;
|
||||||
}
|
}
|
||||||
*count = total;
|
*count = total;
|
||||||
return tile;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const uint8_t* GetFramePayload(const uint8_t* const mem_buf,
|
static const uint8_t* GetFramePayload(const uint8_t* const mem_buf,
|
||||||
@ -751,26 +759,28 @@ static const uint8_t* GetFramePayload(const uint8_t* const mem_buf,
|
|||||||
// Create a whole 'frame' from VP8 (+ alpha) or lossless.
|
// Create a whole 'frame' from VP8 (+ alpha) or lossless.
|
||||||
static int SynthesizeFrame(const WebPDemuxer* const dmux,
|
static int SynthesizeFrame(const WebPDemuxer* const dmux,
|
||||||
const Frame* const first_frame,
|
const Frame* const first_frame,
|
||||||
int tile_num, WebPIterator* const iter) {
|
int fragment_num, WebPIterator* const iter) {
|
||||||
const uint8_t* const mem_buf = dmux->mem_.buf_;
|
const uint8_t* const mem_buf = dmux->mem_.buf_;
|
||||||
int num_tiles;
|
int num_fragments;
|
||||||
size_t payload_size = 0;
|
size_t payload_size = 0;
|
||||||
const Frame* const tile = GetTile(first_frame, tile_num, &num_tiles);
|
const Frame* const fragment =
|
||||||
const uint8_t* const payload = GetFramePayload(mem_buf, tile, &payload_size);
|
GetFragment(first_frame, fragment_num, &num_fragments);
|
||||||
|
const uint8_t* const payload =
|
||||||
|
GetFramePayload(mem_buf, fragment, &payload_size);
|
||||||
if (payload == NULL) return 0;
|
if (payload == NULL) return 0;
|
||||||
|
|
||||||
iter->frame_num = first_frame->frame_num_;
|
iter->frame_num = first_frame->frame_num_;
|
||||||
iter->num_frames = dmux->num_frames_;
|
iter->num_frames = dmux->num_frames_;
|
||||||
iter->tile_num = tile_num;
|
iter->fragment_num = fragment_num;
|
||||||
iter->num_tiles = num_tiles;
|
iter->num_fragments = num_fragments;
|
||||||
iter->x_offset = tile->x_offset_;
|
iter->x_offset = fragment->x_offset_;
|
||||||
iter->y_offset = tile->y_offset_;
|
iter->y_offset = fragment->y_offset_;
|
||||||
iter->width = tile->width_;
|
iter->width = fragment->width_;
|
||||||
iter->height = tile->height_;
|
iter->height = fragment->height_;
|
||||||
iter->duration = tile->duration_;
|
iter->duration = fragment->duration_;
|
||||||
iter->complete = tile->complete_;
|
iter->complete = fragment->complete_;
|
||||||
iter->tile.bytes = payload;
|
iter->fragment.bytes = payload;
|
||||||
iter->tile.size = payload_size;
|
iter->fragment.size = payload_size;
|
||||||
// TODO(jzern): adjust offsets for 'FRGM's embedded in 'ANMF's
|
// TODO(jzern): adjust offsets for 'FRGM's embedded in 'ANMF's
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -805,13 +815,13 @@ int WebPDemuxPrevFrame(WebPIterator* iter) {
|
|||||||
return SetFrame(iter->frame_num - 1, iter);
|
return SetFrame(iter->frame_num - 1, iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
int WebPDemuxSelectTile(WebPIterator* iter, int tile) {
|
int WebPDemuxSelectFragment(WebPIterator* iter, int fragment_num) {
|
||||||
if (iter != NULL && iter->private_ != NULL && tile > 0) {
|
if (iter != NULL && iter->private_ != NULL && fragment_num > 0) {
|
||||||
const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_;
|
const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_;
|
||||||
const Frame* const frame = GetFrame(dmux, iter->frame_num);
|
const Frame* const frame = GetFrame(dmux, iter->frame_num);
|
||||||
if (frame == NULL) return 0;
|
if (frame == NULL) return 0;
|
||||||
|
|
||||||
return SynthesizeFrame(dmux, frame, tile, iter);
|
return SynthesizeFrame(dmux, frame, fragment_num, iter);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -109,15 +109,15 @@ 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 CreateFrameTileData(const WebPData* const image,
|
static WebPMuxError CreateFrameFragmentData(const WebPData* const image,
|
||||||
int x_offset, int y_offset,
|
int x_offset, int y_offset,
|
||||||
int duration, int is_lossless,
|
int duration, int is_lossless,
|
||||||
int is_frame,
|
int is_frame,
|
||||||
WebPData* const frame_tile) {
|
WebPData* const frame_frgm) {
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
uint8_t* frame_tile_bytes;
|
uint8_t* frame_frgm_bytes;
|
||||||
const size_t frame_tile_size = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].size;
|
const size_t frame_frgm_size = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].size;
|
||||||
|
|
||||||
const int ok = is_lossless ?
|
const int ok = is_lossless ?
|
||||||
VP8LGetInfo(image->bytes, image->size, &width, &height, NULL) :
|
VP8LGetInfo(image->bytes, image->size, &width, &height, NULL) :
|
||||||
@ -127,20 +127,20 @@ static WebPMuxError CreateFrameTileData(const WebPData* const image,
|
|||||||
assert(width > 0 && height > 0 && duration > 0);
|
assert(width > 0 && height > 0 && duration > 0);
|
||||||
// Note: assertion on upper bounds is done in PutLE24().
|
// Note: assertion on upper bounds is done in PutLE24().
|
||||||
|
|
||||||
frame_tile_bytes = (uint8_t*)malloc(frame_tile_size);
|
frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size);
|
||||||
if (frame_tile_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
|
if (frame_frgm_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
|
||||||
|
|
||||||
PutLE24(frame_tile_bytes + 0, x_offset / 2);
|
PutLE24(frame_frgm_bytes + 0, x_offset / 2);
|
||||||
PutLE24(frame_tile_bytes + 3, y_offset / 2);
|
PutLE24(frame_frgm_bytes + 3, y_offset / 2);
|
||||||
|
|
||||||
if (is_frame) {
|
if (is_frame) {
|
||||||
PutLE24(frame_tile_bytes + 6, width - 1);
|
PutLE24(frame_frgm_bytes + 6, width - 1);
|
||||||
PutLE24(frame_tile_bytes + 9, height - 1);
|
PutLE24(frame_frgm_bytes + 9, height - 1);
|
||||||
PutLE24(frame_tile_bytes + 12, duration - 1);
|
PutLE24(frame_frgm_bytes + 12, duration - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
frame_tile->bytes = frame_tile_bytes;
|
frame_frgm->bytes = frame_frgm_bytes;
|
||||||
frame_tile->size = frame_tile_size;
|
frame_frgm->size = frame_frgm_size;
|
||||||
return WEBP_MUX_OK;
|
return WEBP_MUX_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,19 +334,19 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame,
|
|||||||
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 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_tile;
|
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)) {
|
||||||
err = WEBP_MUX_INVALID_ARGUMENT;
|
err = WEBP_MUX_INVALID_ARGUMENT;
|
||||||
goto Err;
|
goto Err;
|
||||||
}
|
}
|
||||||
err = CreateFrameTileData(&wpi.img_->data_, x_offset, y_offset, duration,
|
err = CreateFrameFragmentData(&wpi.img_->data_, x_offset, y_offset,
|
||||||
is_lossless, is_frame, &frame_tile);
|
duration, is_lossless, is_frame, &frame_frgm);
|
||||||
if (err != WEBP_MUX_OK) goto Err;
|
if (err != WEBP_MUX_OK) goto Err;
|
||||||
// Add frame/tile chunk (with copy_data = 1).
|
// Add frame/fragment chunk (with copy_data = 1).
|
||||||
err = AddDataToChunkList(&frame_tile, 1, tag, &wpi.header_);
|
err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_);
|
||||||
WebPDataClear(&frame_tile); // frame_tile owned by wpi.header_ now.
|
WebPDataClear(&frame_frgm); // frame_frgm owned by wpi.header_ now.
|
||||||
if (err != WEBP_MUX_OK) goto Err;
|
if (err != WEBP_MUX_OK) goto Err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,15 +394,15 @@ WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Assembly of the WebP RIFF file.
|
// Assembly of the WebP RIFF file.
|
||||||
|
|
||||||
static WebPMuxError GetFrameTileInfo(const WebPChunk* const frame_tile_chunk,
|
static WebPMuxError GetFrameFragmentInfo(
|
||||||
int* const x_offset, int* const y_offset,
|
const WebPChunk* const frame_frgm_chunk,
|
||||||
int* const duration) {
|
int* const x_offset, int* const y_offset, int* const duration) {
|
||||||
const uint32_t tag = frame_tile_chunk->tag_;
|
const uint32_t tag = frame_frgm_chunk->tag_;
|
||||||
const int is_frame = (tag == kChunks[IDX_ANMF].tag);
|
const int is_frame = (tag == kChunks[IDX_ANMF].tag);
|
||||||
const WebPData* const data = &frame_tile_chunk->data_;
|
const WebPData* const data = &frame_frgm_chunk->data_;
|
||||||
const size_t expected_data_size =
|
const size_t expected_data_size =
|
||||||
is_frame ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
|
is_frame ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
|
||||||
assert(frame_tile_chunk != NULL);
|
assert(frame_frgm_chunk != NULL);
|
||||||
assert(tag == kChunks[IDX_ANMF].tag || tag == kChunks[IDX_FRGM].tag);
|
assert(tag == kChunks[IDX_ANMF].tag || tag == kChunks[IDX_FRGM].tag);
|
||||||
if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;
|
if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;
|
||||||
|
|
||||||
@ -437,11 +437,11 @@ static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
|
|||||||
int* const duration,
|
int* const duration,
|
||||||
int* const width, int* const height) {
|
int* const width, int* const height) {
|
||||||
const WebPChunk* const image_chunk = wpi->img_;
|
const WebPChunk* const image_chunk = wpi->img_;
|
||||||
const WebPChunk* const frame_tile_chunk = wpi->header_;
|
const WebPChunk* const frame_frgm_chunk = wpi->header_;
|
||||||
|
|
||||||
// Get offsets and duration from ANMF/FRGM chunk.
|
// Get offsets and duration from ANMF/FRGM chunk.
|
||||||
const WebPMuxError err =
|
const WebPMuxError err =
|
||||||
GetFrameTileInfo(frame_tile_chunk, x_offset, y_offset, duration);
|
GetFrameFragmentInfo(frame_frgm_chunk, x_offset, y_offset, duration);
|
||||||
if (err != WEBP_MUX_OK) return err;
|
if (err != WEBP_MUX_OK) return err;
|
||||||
|
|
||||||
// Get width and height from VP8/VP8L chunk.
|
// Get width and height from VP8/VP8L chunk.
|
||||||
@ -463,7 +463,7 @@ static WebPMuxError GetImageCanvasWidthHeight(
|
|||||||
int max_x = 0;
|
int max_x = 0;
|
||||||
int max_y = 0;
|
int max_y = 0;
|
||||||
int64_t image_area = 0;
|
int64_t image_area = 0;
|
||||||
// Aggregate the bounding box for animation frames & tiled images.
|
// Aggregate the bounding box for animation frames & fragmented images.
|
||||||
for (; wpi != NULL; wpi = wpi->next_) {
|
for (; wpi != NULL; wpi = wpi->next_) {
|
||||||
int x_offset, y_offset, duration, w, h;
|
int x_offset, y_offset, duration, w, h;
|
||||||
const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,
|
const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,
|
||||||
@ -480,11 +480,11 @@ static WebPMuxError GetImageCanvasWidthHeight(
|
|||||||
}
|
}
|
||||||
*width = max_x;
|
*width = max_x;
|
||||||
*height = max_y;
|
*height = max_y;
|
||||||
// Crude check to validate that there are no image overlaps/holes for tile
|
// Crude check to validate that there are no image overlaps/holes for
|
||||||
// images. Check that the aggregated image area for individual tiles exactly
|
// fragmented images. Check that the aggregated image area for individual
|
||||||
// matches the image area of the constructed canvas. However, the area-match
|
// fragments exactly matches the image area of the constructed canvas.
|
||||||
// is necessary but not sufficient condition.
|
// However, the area-match is necessary but not sufficient condition.
|
||||||
if ((flags & TILE_FLAG) && (image_area != (max_x * max_y))) {
|
if ((flags & FRAGMENTS_FLAG) && (image_area != (max_x * max_y))) {
|
||||||
*width = 0;
|
*width = 0;
|
||||||
*height = 0;
|
*height = 0;
|
||||||
return WEBP_MUX_INVALID_ARGUMENT;
|
return WEBP_MUX_INVALID_ARGUMENT;
|
||||||
@ -539,8 +539,8 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
|
|||||||
}
|
}
|
||||||
if (images->header_ != NULL) {
|
if (images->header_ != NULL) {
|
||||||
if (images->header_->tag_ == kChunks[IDX_FRGM].tag) {
|
if (images->header_->tag_ == kChunks[IDX_FRGM].tag) {
|
||||||
// This is a tiled image.
|
// This is a fragmented image.
|
||||||
flags |= TILE_FLAG;
|
flags |= FRAGMENTS_FLAG;
|
||||||
} else if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {
|
} else if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {
|
||||||
// This is an image with animation.
|
// This is an image with animation.
|
||||||
flags |= ANIMATION_FLAG;
|
flags |= ANIMATION_FLAG;
|
||||||
|
@ -399,13 +399,32 @@ size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case as ANMF/FRGM chunk encapsulates other image chunks.
|
||||||
|
static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
|
||||||
|
size_t total_size, uint8_t* dst) {
|
||||||
|
const size_t header_size = header->data_.size;
|
||||||
|
const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
|
||||||
|
assert(header->tag_ == kChunks[IDX_ANMF].tag ||
|
||||||
|
header->tag_ == kChunks[IDX_FRGM].tag);
|
||||||
|
PutLE32(dst + 0, header->tag_);
|
||||||
|
PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
|
||||||
|
assert(header_size == (uint32_t)header_size);
|
||||||
|
memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
|
||||||
|
if (header_size & 1) {
|
||||||
|
dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding.
|
||||||
|
}
|
||||||
|
return dst + ChunkDiskSize(header);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
|
uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
|
||||||
// Ordering of chunks to be emitted is strictly as follows:
|
// Ordering of chunks to be emitted is strictly as follows:
|
||||||
// 1. ANMF/FRGM chunk (if present).
|
// 1. ANMF/FRGM chunk (if present).
|
||||||
// 2. ALPH chunk (if present).
|
// 2. ALPH chunk (if present).
|
||||||
// 3. VP8/VP8L chunk.
|
// 3. VP8/VP8L chunk.
|
||||||
assert(wpi);
|
assert(wpi);
|
||||||
if (wpi->header_ != NULL) dst = ChunkEmit(wpi->header_, dst);
|
if (wpi->header_ != NULL) {
|
||||||
|
dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);
|
||||||
|
}
|
||||||
if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
|
if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
|
||||||
if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
|
if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
|
||||||
return dst;
|
return dst;
|
||||||
@ -457,16 +476,16 @@ WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
|
|||||||
WebPMuxError MuxValidateForImage(const WebPMux* const mux) {
|
WebPMuxError MuxValidateForImage(const WebPMux* const mux) {
|
||||||
const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);
|
const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);
|
||||||
const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);
|
const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);
|
||||||
const int num_tiles = MuxImageCount(mux->images_, WEBP_CHUNK_FRGM);
|
const int num_fragments = MuxImageCount(mux->images_, WEBP_CHUNK_FRGM);
|
||||||
|
|
||||||
if (num_images == 0) {
|
if (num_images == 0) {
|
||||||
// No images in mux.
|
// No images in mux.
|
||||||
return WEBP_MUX_NOT_FOUND;
|
return WEBP_MUX_NOT_FOUND;
|
||||||
} else if (num_images == 1 && num_frames == 0 && num_tiles == 0) {
|
} else if (num_images == 1 && num_frames == 0 && num_fragments == 0) {
|
||||||
// Valid case (single image).
|
// Valid case (single image).
|
||||||
return WEBP_MUX_OK;
|
return WEBP_MUX_OK;
|
||||||
} else {
|
} else {
|
||||||
// Frame/Tile case OR an invalid mux.
|
// Frame/Fragment case OR an invalid mux.
|
||||||
return WEBP_MUX_INVALID_ARGUMENT;
|
return WEBP_MUX_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -501,7 +520,7 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
|
|||||||
int num_xmp;
|
int num_xmp;
|
||||||
int num_loop_chunks;
|
int num_loop_chunks;
|
||||||
int num_frames;
|
int num_frames;
|
||||||
int num_tiles;
|
int num_fragments;
|
||||||
int num_vp8x;
|
int num_vp8x;
|
||||||
int num_images;
|
int num_images;
|
||||||
int num_alpha;
|
int num_alpha;
|
||||||
@ -546,8 +565,8 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tiling: TILE_FLAG and tile chunk(s) are consistent.
|
// Fragmentation: FRAGMENTS_FLAG and FRGM chunk(s) are consistent.
|
||||||
err = ValidateChunk(mux, IDX_FRGM, TILE_FLAG, flags, -1, &num_tiles);
|
err = ValidateChunk(mux, IDX_FRGM, FRAGMENTS_FLAG, flags, -1, &num_fragments);
|
||||||
if (err != WEBP_MUX_OK) return err;
|
if (err != WEBP_MUX_OK) return err;
|
||||||
|
|
||||||
// Verify either VP8X chunk is present OR there is only one elem in
|
// Verify either VP8X chunk is present OR there is only one elem in
|
||||||
@ -567,8 +586,8 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
|
|||||||
if (err != WEBP_MUX_OK) return err;
|
if (err != WEBP_MUX_OK) return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// num_tiles & num_images are consistent.
|
// num_fragments & num_images are consistent.
|
||||||
if (num_tiles > 0 && num_images != num_tiles) {
|
if (num_fragments > 0 && num_images != num_fragments) {
|
||||||
return WEBP_MUX_INVALID_ARGUMENT;
|
return WEBP_MUX_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +51,9 @@ static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
|
|||||||
|
|
||||||
// Fill the chunk with the given data (includes chunk header bytes), after some
|
// Fill the chunk with the given data (includes chunk header bytes), after some
|
||||||
// verifications.
|
// verifications.
|
||||||
static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk,
|
static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
|
||||||
const uint8_t* data,
|
const uint8_t* data, size_t data_size,
|
||||||
size_t data_size, size_t riff_size,
|
size_t riff_size, int copy_data) {
|
||||||
int copy_data) {
|
|
||||||
uint32_t chunk_size;
|
uint32_t chunk_size;
|
||||||
WebPData chunk_data;
|
WebPData chunk_data;
|
||||||
|
|
||||||
@ -74,6 +73,66 @@ static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk,
|
|||||||
return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
|
return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
|
||||||
|
WebPMuxImage* const wpi) {
|
||||||
|
const uint8_t* bytes = chunk->data_.bytes;
|
||||||
|
size_t size = chunk->data_.size;
|
||||||
|
const uint8_t* const last = bytes + size;
|
||||||
|
WebPChunk subchunk;
|
||||||
|
size_t subchunk_size;
|
||||||
|
ChunkInit(&subchunk);
|
||||||
|
|
||||||
|
assert(chunk->tag_ == kChunks[IDX_ANMF].tag ||
|
||||||
|
chunk->tag_ == kChunks[IDX_FRGM].tag);
|
||||||
|
assert(!wpi->is_partial_);
|
||||||
|
|
||||||
|
// ANMF/FRGM.
|
||||||
|
{
|
||||||
|
const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ?
|
||||||
|
ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
|
||||||
|
const WebPData temp = { bytes, hdr_size };
|
||||||
|
ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
|
||||||
|
}
|
||||||
|
ChunkSetNth(&subchunk, &wpi->header_, 1);
|
||||||
|
wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks.
|
||||||
|
|
||||||
|
// Rest of the chunks.
|
||||||
|
subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
|
||||||
|
bytes += subchunk_size;
|
||||||
|
size -= subchunk_size;
|
||||||
|
|
||||||
|
while (bytes != last) {
|
||||||
|
ChunkInit(&subchunk);
|
||||||
|
if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
|
||||||
|
copy_data) != WEBP_MUX_OK) {
|
||||||
|
goto Fail;
|
||||||
|
}
|
||||||
|
switch (ChunkGetIdFromTag(subchunk.tag_)) {
|
||||||
|
case WEBP_CHUNK_ALPHA:
|
||||||
|
if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks.
|
||||||
|
if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
|
||||||
|
wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
|
||||||
|
break;
|
||||||
|
case WEBP_CHUNK_IMAGE:
|
||||||
|
if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
|
||||||
|
wpi->is_partial_ = 0; // wpi is completely filled.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
goto Fail;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
subchunk_size = ChunkDiskSize(&subchunk);
|
||||||
|
bytes += subchunk_size;
|
||||||
|
size -= subchunk_size;
|
||||||
|
}
|
||||||
|
if (wpi->is_partial_) goto Fail;
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
Fail:
|
||||||
|
ChunkRelease(&subchunk);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Create a mux object from WebP-RIFF data.
|
// Create a mux object from WebP-RIFF data.
|
||||||
|
|
||||||
@ -136,42 +195,46 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
|
|||||||
|
|
||||||
// Loop over chunks.
|
// Loop over chunks.
|
||||||
while (data != end) {
|
while (data != end) {
|
||||||
|
size_t data_size;
|
||||||
WebPChunkId id;
|
WebPChunkId id;
|
||||||
WebPMuxError err;
|
WebPChunk** chunk_list;
|
||||||
|
if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
|
||||||
err = ChunkVerifyAndAssignData(&chunk, data, size, riff_size, copy_data);
|
copy_data) != WEBP_MUX_OK) {
|
||||||
if (err != WEBP_MUX_OK) goto Err;
|
goto Err;
|
||||||
|
}
|
||||||
|
data_size = ChunkDiskSize(&chunk);
|
||||||
id = ChunkGetIdFromTag(chunk.tag_);
|
id = ChunkGetIdFromTag(chunk.tag_);
|
||||||
|
switch (id) {
|
||||||
if (IsWPI(id)) { // An image chunk (frame/tile/alpha/vp8).
|
case WEBP_CHUNK_ALPHA:
|
||||||
WebPChunk** wpi_chunk_ptr =
|
if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.
|
||||||
MuxImageGetListFromId(wpi, id); // Image chunk to set.
|
if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
|
||||||
assert(wpi_chunk_ptr != NULL);
|
wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
|
||||||
if (*wpi_chunk_ptr != NULL) goto Err; // Consecutive alpha chunks or
|
break;
|
||||||
// consecutive frame/tile chunks.
|
case WEBP_CHUNK_IMAGE:
|
||||||
if (ChunkSetNth(&chunk, wpi_chunk_ptr, 1) != WEBP_MUX_OK) goto Err;
|
if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
|
||||||
if (id == WEBP_CHUNK_IMAGE) {
|
|
||||||
wpi->is_partial_ = 0; // wpi is completely filled.
|
wpi->is_partial_ = 0; // wpi is completely filled.
|
||||||
|
PushImage:
|
||||||
// Add this to mux->images_ list.
|
// Add this to mux->images_ list.
|
||||||
if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
|
if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
|
||||||
MuxImageInit(wpi); // Reset for reading next image.
|
MuxImageInit(wpi); // Reset for reading next image.
|
||||||
} else {
|
break;
|
||||||
wpi->is_partial_ = 1; // wpi is only partially filled.
|
case WEBP_CHUNK_ANMF:
|
||||||
}
|
case WEBP_CHUNK_FRGM:
|
||||||
} else { // A non-image chunk.
|
if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete.
|
||||||
WebPChunk** chunk_list;
|
if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
|
||||||
if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before
|
ChunkRelease(&chunk);
|
||||||
// getting all chunks of an image.
|
goto PushImage;
|
||||||
chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk.
|
break;
|
||||||
if (chunk_list == NULL) chunk_list = &mux->unknown_;
|
default: // A non-image chunk.
|
||||||
if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
|
if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before
|
||||||
}
|
// getting all chunks of an image.
|
||||||
{
|
chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk.
|
||||||
const size_t data_size = ChunkDiskSize(&chunk);
|
if (chunk_list == NULL) chunk_list = &mux->unknown_;
|
||||||
data += data_size;
|
if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
|
||||||
size -= data_size;
|
break;
|
||||||
}
|
}
|
||||||
|
data += data_size;
|
||||||
|
size -= data_size;
|
||||||
ChunkInit(&chunk);
|
ChunkInit(&chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,19 +364,19 @@ static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,
|
|||||||
return SynthesizeBitstream(wpi, &info->bitstream);
|
return SynthesizeBitstream(wpi, &info->bitstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
static WebPMuxError MuxGetFrameTileInternal(const WebPMuxImage* const wpi,
|
static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi,
|
||||||
WebPMuxFrameInfo* const frame) {
|
WebPMuxFrameInfo* const frame) {
|
||||||
const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
|
const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
|
||||||
const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM;
|
const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM;
|
||||||
const WebPData* frame_tile_data;
|
const WebPData* frame_frgm_data;
|
||||||
assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame().
|
assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame().
|
||||||
// Get frame/tile chunk.
|
// Get frame/fragment chunk.
|
||||||
frame_tile_data = &wpi->header_->data_;
|
frame_frgm_data = &wpi->header_->data_;
|
||||||
if (frame_tile_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA;
|
if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA;
|
||||||
// Extract info.
|
// Extract info.
|
||||||
frame->x_offset = 2 * GetLE24(frame_tile_data->bytes + 0);
|
frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0);
|
||||||
frame->y_offset = 2 * GetLE24(frame_tile_data->bytes + 3);
|
frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3);
|
||||||
frame->duration = is_frame ? 1 + GetLE24(frame_tile_data->bytes + 12) : 1;
|
frame->duration = is_frame ? 1 + GetLE24(frame_frgm_data->bytes + 12) : 1;
|
||||||
frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
|
frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
|
||||||
return SynthesizeBitstream(wpi, &frame->bitstream);
|
return SynthesizeBitstream(wpi, &frame->bitstream);
|
||||||
}
|
}
|
||||||
@ -336,7 +399,7 @@ WebPMuxError WebPMuxGetFrame(
|
|||||||
if (wpi->header_ == NULL) {
|
if (wpi->header_ == NULL) {
|
||||||
return MuxGetImageInternal(wpi, frame);
|
return MuxGetImageInternal(wpi, frame);
|
||||||
} else {
|
} else {
|
||||||
return MuxGetFrameTileInternal(wpi, frame);
|
return MuxGetFrameFragmentInternal(wpi, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ typedef enum {
|
|||||||
#define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height.
|
#define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height.
|
||||||
#define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count
|
#define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count
|
||||||
#define MAX_DURATION (1 << 24) // maximum duration
|
#define MAX_DURATION (1 << 24) // maximum duration
|
||||||
#define MAX_POSITION_OFFSET (1 << 24) // maximum frame/tile x/y offset
|
#define MAX_POSITION_OFFSET (1 << 24) // maximum frame/fragment x/y offset
|
||||||
|
|
||||||
// Maximum chunk payload is such that adding the header and padding won't
|
// Maximum chunk payload is such that adding the header and padding won't
|
||||||
// overflow a uint32_t.
|
// overflow a uint32_t.
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
// Vikas (vikasa@google.com)
|
// Vikas (vikasa@google.com)
|
||||||
|
|
||||||
// This API allows manipulation of WebP container images containing features
|
// This API allows manipulation of WebP container images containing features
|
||||||
// like color profile, metadata, animation and tiling.
|
// like color profile, metadata, animation and fragmented images.
|
||||||
//
|
//
|
||||||
// Code Example#1: Creating a MUX with image data, color profile and XMP
|
// Code Example#1: Creating a MUX with image data, color profile and XMP
|
||||||
// metadata.
|
// metadata.
|
||||||
@ -82,7 +82,7 @@ enum WebPMuxError {
|
|||||||
|
|
||||||
// Flag values for different features used in VP8X chunk.
|
// Flag values for different features used in VP8X chunk.
|
||||||
enum WebPFeatureFlags {
|
enum WebPFeatureFlags {
|
||||||
TILE_FLAG = 0x00000001,
|
FRAGMENTS_FLAG = 0x00000001,
|
||||||
ANIMATION_FLAG = 0x00000002,
|
ANIMATION_FLAG = 0x00000002,
|
||||||
XMP_FLAG = 0x00000004,
|
XMP_FLAG = 0x00000004,
|
||||||
EXIF_FLAG = 0x00000008,
|
EXIF_FLAG = 0x00000008,
|
||||||
@ -233,12 +233,12 @@ struct WebPMuxFrameInfo {
|
|||||||
uint32_t pad[3]; // padding for later use
|
uint32_t pad[3]; // padding for later use
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sets the (non-animated and non-tiled) image in the mux object.
|
// Sets the (non-animated and non-fragmented) image in the mux object.
|
||||||
// Note: Any existing images (including frames/tiles) will be removed.
|
// Note: Any existing images (including frames/fragments) will be removed.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// mux - (in/out) object in which the image is to be set
|
// mux - (in/out) object in which the image is to be set
|
||||||
// bitstream - (in) can either be a raw VP8/VP8L bitstream or a single-image
|
// bitstream - (in) can either be a raw VP8/VP8L bitstream or a single-image
|
||||||
// WebP file (non-animated and non-tiled)
|
// WebP file (non-animated and non-fragmented)
|
||||||
// copy_data - (in) value 1 indicates given data WILL be copied to the mux
|
// copy_data - (in) value 1 indicates given data WILL be copied to the mux
|
||||||
// and value 0 indicates data will NOT be copied.
|
// and value 0 indicates data will NOT be copied.
|
||||||
// Returns:
|
// Returns:
|
||||||
@ -250,8 +250,8 @@ WEBP_EXTERN(WebPMuxError) WebPMuxSetImage(
|
|||||||
|
|
||||||
// Adds a frame at the end of the mux object.
|
// Adds a frame at the end of the mux object.
|
||||||
// Notes: (1) frame.id should be one of WEBP_CHUNK_ANMF or WEBP_CHUNK_FRGM
|
// Notes: (1) frame.id should be one of WEBP_CHUNK_ANMF or WEBP_CHUNK_FRGM
|
||||||
// (2) For setting a non-animated non-tiled image, use WebPMuxSetImage()
|
// (2) For setting a non-animated non-fragmented image, use
|
||||||
// instead.
|
// WebPMuxSetImage() instead.
|
||||||
// (3) Type of frame being pushed must be same as the frames in mux.
|
// (3) Type of frame being pushed must be same as the frames in mux.
|
||||||
// (4) As WebP only supports even offsets, any odd offset will be snapped
|
// (4) As WebP only supports even offsets, any odd offset will be snapped
|
||||||
// to an even location using: offset &= ~1
|
// to an even location using: offset &= ~1
|
||||||
@ -428,22 +428,23 @@ WEBP_EXTERN(uint32_t) WebPDemuxGetI(
|
|||||||
struct WebPIterator {
|
struct WebPIterator {
|
||||||
int frame_num;
|
int frame_num;
|
||||||
int num_frames;
|
int num_frames;
|
||||||
int tile_num;
|
int fragment_num;
|
||||||
int num_tiles;
|
int num_fragments;
|
||||||
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 tile.
|
int width, height; // dimensions of this frame or fragment.
|
||||||
int duration; // display duration in milliseconds.
|
int duration; // display duration in milliseconds.
|
||||||
int complete; // true if 'tile_' contains a full frame. partial images may
|
int complete; // true if 'fragment' contains a full frame. partial images
|
||||||
// still be decoded with the WebP incremental decoder.
|
// may still be decoded with the WebP incremental decoder.
|
||||||
WebPData tile; // The frame or tile given by 'frame_num_' and 'tile_num_'.
|
WebPData fragment; // The frame or fragment given by 'frame_num' and
|
||||||
|
// 'fragment_num'.
|
||||||
|
|
||||||
uint32_t pad[4]; // padding for later use
|
uint32_t pad[4]; // padding for later use
|
||||||
void* private_; // for internal use only.
|
void* private_; // for internal use only.
|
||||||
};
|
};
|
||||||
|
|
||||||
// Retrieves frame 'frame_number' from 'dmux'.
|
// Retrieves frame 'frame_number' from 'dmux'.
|
||||||
// 'iter->tile_' points to the first tile on return from this function.
|
// 'iter->fragment' points to the first fragment on return from this function.
|
||||||
// Individual tiles may be extracted using WebPDemuxSetTile().
|
// Individual fragments may be extracted using WebPDemuxSetFragment().
|
||||||
// Setting 'frame_number' equal to 0 will return the last frame of the image.
|
// Setting 'frame_number' equal to 0 will return the last frame of the image.
|
||||||
// Returns false if 'dmux' is NULL or frame 'frame_number' is not present.
|
// Returns false if 'dmux' is NULL or frame 'frame_number' is not present.
|
||||||
// Call WebPDemuxReleaseIterator() when use of the iterator is complete.
|
// Call WebPDemuxReleaseIterator() when use of the iterator is complete.
|
||||||
@ -451,14 +452,14 @@ struct WebPIterator {
|
|||||||
WEBP_EXTERN(int) WebPDemuxGetFrame(
|
WEBP_EXTERN(int) WebPDemuxGetFrame(
|
||||||
const WebPDemuxer* dmux, int frame_number, WebPIterator* iter);
|
const WebPDemuxer* dmux, int frame_number, WebPIterator* iter);
|
||||||
|
|
||||||
// Sets 'iter->tile_' to point to the next ('iter->frame_num_' + 1) or previous
|
// Sets 'iter->fragment' to point to the next ('iter->frame_num' + 1) or
|
||||||
// ('iter->frame_num_' - 1) frame. These functions do not loop.
|
// previous ('iter->frame_num' - 1) frame. These functions do not loop.
|
||||||
// Returns true on success, false otherwise.
|
// Returns true on success, false otherwise.
|
||||||
WEBP_EXTERN(int) WebPDemuxNextFrame(WebPIterator* iter);
|
WEBP_EXTERN(int) WebPDemuxNextFrame(WebPIterator* iter);
|
||||||
WEBP_EXTERN(int) WebPDemuxPrevFrame(WebPIterator* iter);
|
WEBP_EXTERN(int) WebPDemuxPrevFrame(WebPIterator* iter);
|
||||||
|
|
||||||
// Sets 'iter->tile_' to reflect tile number 'tile_number'.
|
// Sets 'iter->fragment' to reflect fragment number 'fragment_num'.
|
||||||
// Returns true if tile 'tile_number' is present, false otherwise.
|
// Returns true if fragment 'fragment_num' is present, false otherwise.
|
||||||
WEBP_EXTERN(int) WebPDemuxSelectTile(WebPIterator* iter, int tile_number);
|
WEBP_EXTERN(int) WebPDemuxSelectTile(WebPIterator* iter, int tile_number);
|
||||||
|
|
||||||
// Releases any memory associated with 'iter'.
|
// Releases any memory associated with 'iter'.
|
||||||
@ -493,8 +494,8 @@ WEBP_EXTERN(int) WebPDemuxGetChunk(const WebPDemuxer* dmux,
|
|||||||
const char fourcc[4], int chunk_number,
|
const char fourcc[4], int chunk_number,
|
||||||
WebPChunkIterator* iter);
|
WebPChunkIterator* iter);
|
||||||
|
|
||||||
// Sets 'iter->chunk_' to point to the next ('iter->chunk_num_' + 1) or previous
|
// Sets 'iter->chunk' to point to the next ('iter->chunk_num' + 1) or previous
|
||||||
// ('iter->chunk_num_' - 1) chunk. These functions do not loop.
|
// ('iter->chunk_num' - 1) chunk. These functions do not loop.
|
||||||
// Returns true on success, false otherwise.
|
// Returns true on success, false otherwise.
|
||||||
WEBP_EXTERN(int) WebPDemuxNextChunk(WebPChunkIterator* iter);
|
WEBP_EXTERN(int) WebPDemuxNextChunk(WebPChunkIterator* iter);
|
||||||
WEBP_EXTERN(int) WebPDemuxPrevChunk(WebPChunkIterator* iter);
|
WEBP_EXTERN(int) WebPDemuxPrevChunk(WebPChunkIterator* iter);
|
||||||
|
Loading…
Reference in New Issue
Block a user