Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 94 additions & 6 deletions apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ typedef struct
avifHeaderFormatFlags headerFormat;
uint64_t creationTime;
uint64_t modificationTime;
uint32_t width;
uint32_t height;

avifBool paspPresent;
uint32_t paspValues[2];
Expand Down Expand Up @@ -241,6 +243,8 @@ static void syntaxLong(void)
printf(" --progressive : Automatically set parameters to encode a simple layered image supporting progressive rendering from a single input frame.\n");
printf(" --layered : Encode a layered AVIF. Each input is encoded as one layer and at most %d layers can be encoded.\n",
AVIF_MAX_AV1_LAYER_COUNT);
printf(" --render-size WxH : Set the rendered size of the AVIF image.\n");
printf(" Supported for still images and layered still images only.\n");
printf(" -g,--grid MxN : Encode a single-image grid AVIF with M cols & N rows. Either supply MxN identical W/H/D images, or a single\n");
printf(" image that can be evenly split into the MxN grid and follow AVIF grid image restrictions. The grid will adopt\n");
printf(" the color profile of the first image supplied.\n");
Expand Down Expand Up @@ -452,6 +456,35 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, uint32_t clapVal
return AVIF_TRUE;
}

static avifBool avifSettingsUsesRenderedSizeOverride(const avifSettings * settings)
{
return (settings->width != 0) || (settings->height != 0);
}

static void avifSettingsGetEffectiveOutputDimensions(const avifSettings * settings, const avifImage * image, uint32_t * width, uint32_t * height)
{
*width = settings->width ? settings->width : image->width;
*height = settings->height ? settings->height : image->height;
}

static avifBool avifSettingsVerifyRenderedSizeBounds(const avifSettings * settings, const avifImage * image, const char * filename)
{
if (!avifSettingsUsesRenderedSizeOverride(settings)) {
return AVIF_TRUE;
}
if ((image->width > settings->width) || (image->height > settings->height)) {
fprintf(stderr,
"ERROR: Input image dimensions [%ux%u] exceed rendered size [%ux%u]: %s\n",
image->width,
image->height,
settings->width,
settings->height,
filename);
return AVIF_FALSE;
}
return AVIF_TRUE;
}

static avifBool avifInputAddCachedImage(avifInput * input)
{
avifImage * newImage = avifImageCreateEmpty();
Expand Down Expand Up @@ -837,10 +870,11 @@ static avifBool avifEncodeUpdateEncoderSettings(avifEncoder * encoder, const avi
static avifBool avifEncoderVerifyImageCompatibility(const avifImage * refImage,
const avifImage * testImage,
const char * seriesType,
const char * filename)
const char * filename,
avifBool allowDimensionChange)
{
// Verify that this frame's properties matches the first frame's properties
if ((refImage->width != testImage->width) || (refImage->height != testImage->height)) {
if (!allowDimensionChange && ((refImage->width != testImage->width) || (refImage->height != testImage->height))) {
fprintf(stderr,
"ERROR: Image %s dimensions mismatch, [%ux%u] vs [%ux%u]: %s\n",
seriesType,
Expand Down Expand Up @@ -930,7 +964,14 @@ static avifBool avifEncodeRestOfImageSequence(avifEncoder * encoder,
settings->inputFormat)) {
goto cleanup;
}
if (!avifEncoderVerifyImageCompatibility(firstImage, nextImage, "sequence", avifPrettyFilename(nextFile->filename))) {
if (!avifSettingsVerifyRenderedSizeBounds(settings, nextImage, avifPrettyFilename(nextFile->filename))) {
goto cleanup;
}
if (!avifEncoderVerifyImageCompatibility(firstImage,
nextImage,
"sequence",
avifPrettyFilename(nextFile->filename),
/*allowDimensionChange=*/AVIF_FALSE)) {
goto cleanup;
}
if (!avifEncodeUpdateEncoderSettings(encoder, nextSettings)) {
Expand Down Expand Up @@ -1036,14 +1077,21 @@ static avifBool avifEncodeRestOfLayeredImage(avifEncoder * encoder,
settings->inputFormat)) {
goto cleanup;
}
if (!avifSettingsVerifyRenderedSizeBounds(settings, nextImage, avifPrettyFilename(nextFile->filename))) {
goto cleanup;
}
// frameIter is NULL if y4m reached end, so single frame y4m is still supported.
if (input->frameIter) {
fprintf(stderr,
"ERROR: Layered encoding does not support input with multiple frames: %s.\n",
avifPrettyFilename(nextFile->filename));
goto cleanup;
}
if (!avifEncoderVerifyImageCompatibility(firstImage, nextImage, "layer", avifPrettyFilename(nextFile->filename))) {
if (!avifEncoderVerifyImageCompatibility(firstImage,
nextImage,
"layer",
avifPrettyFilename(nextFile->filename),
avifSettingsUsesRenderedSizeOverride(settings))) {
goto cleanup;
}
if (!avifEncodeUpdateEncoderSettings(encoder, nextSettings)) {
Expand Down Expand Up @@ -1104,6 +1152,8 @@ static avifBool avifEncodeImagesFixedQuality(const avifSettings * settings,
encoder->creationTime = settings->creationTime;
encoder->modificationTime = settings->modificationTime;
encoder->extraLayerCount = settings->layers - 1;
encoder->width = settings->width;
encoder->height = settings->height;
if (!avifEncodeUpdateEncoderSettings(encoder, &firstFile->settings)) {
goto cleanup;
}
Expand Down Expand Up @@ -1748,6 +1798,15 @@ int main(int argc, char * argv[])
goto cleanup;
}
settings.layered = AVIF_TRUE;
} else if (!strcmp(arg, "--render-size")) {
uint32_t renderSize[2] = { 0 };
NEXTARG();
if (!parseU32List(renderSize, 2, arg, 'x') || (renderSize[0] == 0) || (renderSize[1] == 0)) {
fprintf(stderr, "ERROR: Invalid render size: %s\n", arg);
goto cleanup;
}
settings.width = renderSize[0];
settings.height = renderSize[1];
} else if (!strcmp(arg, "--scaling-mode") || strpre(arg, "--scaling-mode:")) {
avifOptionSuffixType type = parseOptionSuffix(arg, input.filesCount != 0);
if (type == AVIF_OPTION_SUFFIX_INVALID) {
Expand Down Expand Up @@ -2106,6 +2165,11 @@ int main(int argc, char * argv[])
fprintf(stderr, "WARNING: Trailing options with update suffix has no effect. Place them before the input you intend to apply to.\n");
}

if (avifSettingsUsesRenderedSizeOverride(&settings) && ((settings.width == 0) || (settings.height == 0))) {
fprintf(stderr, "ERROR: --render-size must be specified as WxH.\n");
goto cleanup;
}

// Check layer config
if (settings.progressive) {
assert(!settings.layered);
Expand Down Expand Up @@ -2133,6 +2197,14 @@ int main(int argc, char * argv[])
fprintf(stderr, "Layered grid image unimplemented in avifenc.\n");
goto cleanup;
}
if (avifSettingsUsesRenderedSizeOverride(&settings) && settings.gridDimsPresent) {
fprintf(stderr, "ERROR: --render-size is not supported with --grid.\n");
goto cleanup;
}
if (avifSettingsUsesRenderedSizeOverride(&settings) && (settings.layers == 1) && (input.filesCount > 1)) {
fprintf(stderr, "ERROR: --render-size is not supported with image sequences. Use --layered for multiple still inputs.\n");
goto cleanup;
}

for (int i = 0; i < input.filesCount; ++i) {
avifInputFile * file = &input.files[i];
Expand Down Expand Up @@ -2352,6 +2424,19 @@ int main(int argc, char * argv[])
goto cleanup;
}

if (!avifSettingsVerifyRenderedSizeBounds(&settings, image, avifPrettyFilename(firstFile->filename))) {
goto cleanup;
}

if (avifSettingsUsesRenderedSizeOverride(&settings) && (settings.layers == 1) && input.frameIter) {
fprintf(stderr, "ERROR: --render-size is not supported with image sequences.\n");
goto cleanup;
}

uint32_t outputImageWidth;
uint32_t outputImageHeight;
avifSettingsGetEffectiveOutputDimensions(&settings, image, &outputImageWidth, &outputImageHeight);

printf("Successfully loaded: %s\n", avifPrettyFilename(firstFile->filename));

// Prepare image timings
Expand Down Expand Up @@ -2404,7 +2489,7 @@ int main(int argc, char * argv[])
image->pasp.vSpacing = settings.paspValues[1];
}
if (cropConversionRequired) {
if (!convertCropToClap(image->width, image->height, settings.clapValues)) {
if (!convertCropToClap(outputImageWidth, outputImageHeight, settings.clapValues)) {
goto cleanup;
}
settings.clapValid = AVIF_TRUE;
Expand All @@ -2424,7 +2509,7 @@ int main(int argc, char * argv[])
avifCropRect cropRect;
avifDiagnostics diag;
avifDiagnosticsClearError(&diag);
if (!avifCropRectFromCleanApertureBox(&cropRect, &image->clap, image->width, image->height, &diag)) {
if (!avifCropRectFromCleanApertureBox(&cropRect, &image->clap, outputImageWidth, outputImageHeight, &diag)) {
fprintf(stderr,
"ERROR: Invalid clap: width:[%d / %d], height:[%d / %d], horizOff:[%d / %d], vertOff:[%d / %d] - %s\n",
(int32_t)image->clap.widthN,
Expand Down Expand Up @@ -2609,6 +2694,9 @@ int main(int argc, char * argv[])
settings.gridDims[0],
settings.gridDims[1],
settings.layers > 1 ? AVIF_PROGRESSIVE_STATE_AVAILABLE : AVIF_PROGRESSIVE_STATE_UNAVAILABLE);
if (avifSettingsUsesRenderedSizeOverride(&settings)) {
printf(" * Rendered Size : %ux%u\n", settings.width, settings.height);
}

avifEncodedByteSizes byteSizes = { 0, 0, 0 };
if (!avifEncodeImages(&settings, &input, firstFile, image, (const avifImage **)gridCells, &raw, &byteSizes)) {
Expand Down
8 changes: 8 additions & 0 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,7 @@ typedef struct avifEncoder
avifBool autoTiling;

// Up/down scaling of the image to perform before encoding.
// This cannot be used together with encoder->width / encoder->height.
avifScalingMode scalingMode;

// --------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -1622,6 +1623,13 @@ typedef struct avifEncoder

// Version 1.4.0 ends here. Add any new members after this line.
// --------------------------------------------------------------------------------------------

// Override the rendered size of the encoded image.
// Defaults to 0. When set to 0, libavif uses the width and height of the first added image.
// Typical use case is layered image, where these are set to the size of the last layer,
// while encoding smaller images as the previous layers.
uint32_t width;
uint32_t height;
} avifEncoder;

// Creates an encoder initialized with default settings values.
Expand Down
16 changes: 13 additions & 3 deletions src/codec_aom.c
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
cfg->g_input_bit_depth = image->depth;
cfg->g_w = image->width;
cfg->g_h = image->height;
cfg->g_forced_max_frame_width = encoder->width;
cfg->g_forced_max_frame_height = encoder->height;

// Detect the libaom v3.6.0 bug described in
// https://crbug.com/aomedia/2871#c12. See the changes to
Expand All @@ -880,10 +882,12 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
}
}

if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
if ((addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) && (encoder->width == 0) && (encoder->height == 0)) {
// Set the maximum number of frames to encode to 1. This instructs
// libaom to set still_picture and reduced_still_picture_header to
// 1 in AV1 sequence headers.
// Still picture header requires image size and render size to match
// or the produced file is not decodable.
cfg->g_limit = 1;
}
if (useAllIntra) {
Expand Down Expand Up @@ -922,6 +926,11 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
if (disableLaggedOutput) {
cfg->g_lag_in_frames = 0;
}
if ((encoder->width || encoder->height) && (cfg->g_lag_in_frames > 1)) {
// libaom does not allow changing frame dimension if
// g_lag_in_frames > 1.
cfg->g_lag_in_frames = 1;
}
if (encoder->maxThreads > 1) {
// libaom fails if cfg->g_threads is greater than 64 threads. See MAX_NUM_THREADS in
// aom/aom_util/aom_thread.h.
Expand Down Expand Up @@ -1077,8 +1086,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
} else {
avifBool dimensionsChanged = AVIF_FALSE;
if ((cfg->g_w != image->width) || (cfg->g_h != image->height)) {
// We are not ready for dimension change for now.
return AVIF_RESULT_NOT_IMPLEMENTED;
cfg->g_w = image->width;
cfg->g_h = image->height;
dimensionsChanged = AVIF_TRUE;
}
if (alpha) {
if (encoderChanges & (AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA | AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA)) {
Expand Down
5 changes: 5 additions & 0 deletions src/codec_avm.c
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ static avifResult avmCodecEncodeImage(avifCodec * codec,
// two fields.
encoderChanges &= ~AVIF_ENCODER_CHANGE_SCALING_MODE;

if (encoder->width || encoder->height) {
avifDiagnosticsPrintf(codec->diag, "AVM does not support rendered-size override");
return AVIF_RESULT_NOT_IMPLEMENTED;
}

if (!codec->internal->encoderInitialized) {
int avmCpuUsed = -1;
if (encoder->speed != AVIF_SPEED_DEFAULT) {
Expand Down
6 changes: 6 additions & 0 deletions src/codec_rav1e.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec,
// rav1e does not support disabling lagged output. See https://github.com/xiph/rav1e/issues/2267. Ignore this setting.
(void)disableLaggedOutput;

// rav1e does not support overriding maximum frame width/height in sequence header
if (encoder->width || encoder->height) {
avifDiagnosticsPrintf(codec->diag, "rav1e does not support rendered-size override");
return AVIF_RESULT_NOT_IMPLEMENTED;
}

avifResult result = AVIF_RESULT_UNKNOWN_ERROR;

RaConfig * rav1eConfig = NULL;
Expand Down
7 changes: 7 additions & 0 deletions src/codec_svt.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ static avifResult svtCodecEncodeImage(avifCodec * codec,
// SVT-AV1 does not support disabling lagged output. Ignore this setting.
(void)disableLaggedOutput;

if (encoder->width || encoder->height) {
avifDiagnosticsPrintf(codec->diag, "SVT-AV1 does not support rendered-size override");
return AVIF_RESULT_NOT_IMPLEMENTED;
}

avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
EbColorFormat color_format = EB_YUV420;
uint8_t * uvPlanes = NULL; // 4:2:0 U and V placeholder for alpha because SVT-AV1 does not support 4:0:0.
Expand Down Expand Up @@ -165,6 +170,8 @@ static avifResult svtCodecEncodeImage(avifCodec * codec,
#endif
svt_config->source_width = image->width;
svt_config->source_height = image->height;
svt_config->forced_max_frame_width = encoder->width;
svt_config->forced_max_frame_height = encoder->height;
#if SVT_AV1_CHECK_VERSION(3, 0, 0)
svt_config->level_of_parallelism = encoder->maxThreads;
#else
Expand Down
Loading
Loading