From d5fc449c4e21dc49883e425d7079b77a3970d3b1 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Wed, 18 Mar 2026 17:32:46 +0800 Subject: [PATCH 1/8] Support rendered size override for layered image --- apps/avifenc.c | 100 +++++++++++++- include/avif/avif.h | 8 ++ src/codec_aom.c | 16 ++- src/codec_avm.c | 5 + src/codec_rav1e.c | 6 + src/codec_svt.c | 7 + src/write.c | 150 +++++++++++++++++++-- tests/data/kodim03_yuv420_8bpc_384x256.y4m | 3 + tests/gtest/avifchangesettingtest.cc | 30 +++++ tests/gtest/avifprogressivetest.cc | 59 ++++++++ tests/test_cmd_progressive.sh | 16 ++- 11 files changed, 377 insertions(+), 23 deletions(-) create mode 100644 tests/data/kodim03_yuv420_8bpc_384x256.y4m diff --git a/apps/avifenc.c b/apps/avifenc.c index e169839dbc..2b8de51a61 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -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]; @@ -242,6 +244,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"); @@ -454,6 +458,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(); @@ -860,10 +893,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, @@ -954,7 +988,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)) { @@ -1061,6 +1102,9 @@ 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, @@ -1068,7 +1112,11 @@ static avifBool avifEncodeRestOfLayeredImage(avifEncoder * encoder, 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)) { @@ -1129,6 +1177,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; } @@ -1774,6 +1824,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) { @@ -2134,6 +2193,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); @@ -2161,6 +2225,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]; @@ -2381,6 +2453,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 @@ -2433,7 +2518,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; @@ -2453,7 +2538,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, @@ -2639,6 +2724,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)) { diff --git a/include/avif/avif.h b/include/avif/avif.h index bd81044fa8..6c57323d75 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -1582,6 +1582,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; // -------------------------------------------------------------------------------------------- @@ -1628,6 +1629,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. diff --git a/src/codec_aom.c b/src/codec_aom.c index ec4a50a949..7e305ba77e 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -871,6 +871,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 @@ -891,10 +893,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) { @@ -933,6 +937,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. @@ -1082,8 +1091,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)) { diff --git a/src/codec_avm.c b/src/codec_avm.c index 6222f7fa6d..01fe18a842 100644 --- a/src/codec_avm.c +++ b/src/codec_avm.c @@ -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) { diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 97c92be3a7..1a24fa5443 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c @@ -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; diff --git a/src/codec_svt.c b/src/codec_svt.c index a76fb5bd53..d5d1b88486 100644 --- a/src/codec_svt.c +++ b/src/codec_svt.c @@ -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. @@ -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 diff --git a/src/write.c b/src/write.c index 6980909fef..b8cd40fa77 100644 --- a/src/write.c +++ b/src/write.c @@ -466,6 +466,49 @@ static avifResult avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, static const avifScalingMode noScaling = { { 1, 1 }, { 1, 1 } }; +static avifBool avifEncoderUsesRenderedSizeOverride(const avifEncoder * encoder) +{ + return (encoder->width != 0) || (encoder->height != 0); +} + +static avifBool avifScalingModeIsNoScaling(const avifScalingMode * scalingMode) +{ + avifFraction horizontal = scalingMode->horizontal; + avifFraction vertical = scalingMode->vertical; + avifFractionSimplify(&horizontal); + avifFractionSimplify(&vertical); + return (horizontal.n == 1) && (horizontal.d == 1) && (vertical.n == 1) && (vertical.d == 1); +} + +static avifBool avifImageHasEquivalentTransformProperties(const avifImage * lhs, const avifImage * rhs) +{ + const uint32_t lhsTransformFlags = lhs->transformFlags & + (AVIF_TRANSFORM_PASP | AVIF_TRANSFORM_CLAP | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR); + const uint32_t rhsTransformFlags = rhs->transformFlags & + (AVIF_TRANSFORM_PASP | AVIF_TRANSFORM_CLAP | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR); + if (lhsTransformFlags != rhsTransformFlags) { + return AVIF_FALSE; + } + if ((lhsTransformFlags & AVIF_TRANSFORM_PASP) && + ((lhs->pasp.hSpacing != rhs->pasp.hSpacing) || (lhs->pasp.vSpacing != rhs->pasp.vSpacing))) { + return AVIF_FALSE; + } + if ((lhsTransformFlags & AVIF_TRANSFORM_CLAP) && + ((lhs->clap.widthN != rhs->clap.widthN) || (lhs->clap.widthD != rhs->clap.widthD) || + (lhs->clap.heightN != rhs->clap.heightN) || (lhs->clap.heightD != rhs->clap.heightD) || + (lhs->clap.horizOffN != rhs->clap.horizOffN) || (lhs->clap.horizOffD != rhs->clap.horizOffD) || + (lhs->clap.vertOffN != rhs->clap.vertOffN) || (lhs->clap.vertOffD != rhs->clap.vertOffD))) { + return AVIF_FALSE; + } + if ((lhsTransformFlags & AVIF_TRANSFORM_IROT) && (lhs->irot.angle != rhs->irot.angle)) { + return AVIF_FALSE; + } + if ((lhsTransformFlags & AVIF_TRANSFORM_IMIR) && (lhs->imir.axis != rhs->imir.axis)) { + return AVIF_FALSE; + } + return AVIF_TRUE; +} + avifEncoder * avifEncoderCreate(void) { avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder)); @@ -486,6 +529,8 @@ avifEncoder * avifEncoderCreate(void) encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY; encoder->minQuantizerAlpha = AVIF_QUANTIZER_BEST_QUALITY; encoder->maxQuantizerAlpha = AVIF_QUANTIZER_WORST_QUALITY; + encoder->width = 0; + encoder->height = 0; encoder->tileRowsLog2 = 0; encoder->tileColsLog2 = 0; encoder->autoTiling = AVIF_FALSE; @@ -533,6 +578,8 @@ static void avifEncoderBackupSettings(avifEncoder * encoder) lastEncoder->timescale = encoder->timescale; lastEncoder->repetitionCount = encoder->repetitionCount; lastEncoder->extraLayerCount = encoder->extraLayerCount; + lastEncoder->width = encoder->width; + lastEncoder->height = encoder->height; lastEncoder->minQuantizer = encoder->minQuantizer; lastEncoder->maxQuantizer = encoder->maxQuantizer; lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha; @@ -561,7 +608,8 @@ static avifBool avifEncoderDetectChanges(const avifEncoder * encoder, avifEncode if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->maxThreads != encoder->maxThreads) || (lastEncoder->speed != encoder->speed) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) || (lastEncoder->timescale != encoder->timescale) || (lastEncoder->repetitionCount != encoder->repetitionCount) || - (lastEncoder->extraLayerCount != encoder->extraLayerCount)) { + (lastEncoder->extraLayerCount != encoder->extraLayerCount) || (lastEncoder->width != encoder->width) || + (lastEncoder->height != encoder->height)) { return AVIF_FALSE; } @@ -1554,6 +1602,69 @@ static avifCodecType avifEncoderGetCodecType(const avifEncoder * encoder) return avifCodecTypeFromChoice(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); } +static avifResult avifEncoderValidateRenderedSizeOverride(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) +{ + if (!avifEncoderUsesRenderedSizeOverride(encoder)) { + return AVIF_RESULT_OK; + } + + if ((encoder->width == 0) || (encoder->height == 0)) { + avifDiagnosticsPrintf(&encoder->diag, + "rendered-size override requires both avifEncoder.width and avifEncoder.height to be non-zero"); + return AVIF_RESULT_INVALID_ARGUMENT; + } + + if ((gridCols > 1) || (gridRows > 1)) { + avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with grid images"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if (!avifScalingModeIsNoScaling(&encoder->scalingMode)) { + avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with encoder->scalingMode"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if (encoder->sampleTransformRecipe != AVIF_SAMPLE_TRANSFORM_NONE) { + avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with sample transforms"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if (firstCell->gainMap && firstCell->gainMap->image) { + avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with gain maps"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if ((encoder->data->items.count > 0) && (encoder->extraLayerCount == 0)) { + avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported for image sequences"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if ((encoder->width < firstCell->width) || (encoder->height < firstCell->height)) { + avifDiagnosticsPrintf(&encoder->diag, + "rendered-size override %ux%u must be at least the coded image size %ux%u", + encoder->width, + encoder->height, + firstCell->width, + firstCell->height); + return AVIF_RESULT_INCOMPATIBLE_IMAGE; + } + + if (firstCell->transformFlags & AVIF_TRANSFORM_CLAP) { + avifCropRect cropRect; + if (!avifCropRectFromCleanApertureBox(&cropRect, &firstCell->clap, encoder->width, encoder->height, &encoder->diag)) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + } + + if ((encoder->data->items.count > 0) && (encoder->extraLayerCount > 0) && + !avifImageHasEquivalentTransformProperties(firstCell, encoder->data->imageMetadata)) { + avifDiagnosticsPrintf(&encoder->diag, "rendered-size override requires 'pasp', 'clap', 'irot' and 'imir' to match across layers"); + return AVIF_RESULT_INCOMPATIBLE_IMAGE; + } + + return AVIF_RESULT_OK; +} + // This function is called after every color frame is encoded. It returns AVIF_TRUE if a keyframe needs to be forced for the next // alpha frame to be encoded, AVIF_FALSE otherwise. static avifBool avifEncoderDataShouldForceKeyframeForAlpha(const avifEncoderData * data, @@ -1736,6 +1847,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return AVIF_RESULT_NO_CONTENT; } + AVIF_CHECKRES(avifEncoderValidateRenderedSizeOverride(encoder, gridCols, gridRows, firstCell)); + AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_FALSE, &encoder->diag)); const avifBool hasGainMap = (firstCell->gainMap && firstCell->gainMap->image != NULL); @@ -1866,6 +1979,10 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, if (encoder->data->items.count == 0) { // Make a copy of the first image's metadata (sans pixels) for future writing/validation AVIF_CHECKRES(avifImageCopy(encoder->data->imageMetadata, firstCell, 0)); + if (avifEncoderUsesRenderedSizeOverride(encoder)) { + encoder->data->imageMetadata->width = encoder->width; + encoder->data->imageMetadata->height = encoder->height; + } const uint32_t gridWidth = avifGridWidth(gridCols, firstCell, bottomRightCell); const uint32_t gridHeight = avifGridHeight(gridRows, firstCell, bottomRightCell); @@ -2154,6 +2271,10 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { return AVIF_RESULT_INVALID_IMAGE_GRID; } + if (avifEncoderUsesRenderedSizeOverride(encoder)) { + avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with avifEncoderAddImageGrid()"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } if (encoder->extraLayerCount == 0) { addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // image grids cannot be image sequences } @@ -3214,6 +3335,9 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) #endif // AVIF_ENABLE_EXPERIMENTAL_MINI const avifImage * imageMetadata = encoder->data->imageMetadata; + const uint32_t imageWidth = encoder->width ? encoder->width : imageMetadata->width; + const uint32_t imageHeight = encoder->height ? encoder->height : imageMetadata->height; + uint64_t now = (uint64_t)time(NULL); uint64_t modificationTime = (encoder->modificationTime != 0) ? encoder->modificationTime : now; uint64_t creationTime = (encoder->creationTime != 0) ? encoder->creationTime : modificationTime; @@ -3628,8 +3752,8 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // template int(16) volume = {if track_is_audio 0x0100 else 0}; AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // const unsigned int(16) reserved = 0; AVIF_CHECKRES(avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix))); // template int(32)[9] matrix= // { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; - AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->width << 16)); // unsigned int(32) width; - AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->height << 16)); // unsigned int(32) height; + AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageWidth << 16)); // unsigned int(32) width; + AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageHeight << 16)); // unsigned int(32) height; AVIF_CHECKRES(avifRWStreamFinishBox(&s, tkhd)); if (item->irefToID != 0) { @@ -3705,16 +3829,16 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); // unsigned int(32) entry_count; avifBoxMarker imageItem; AVIF_CHECKRES(avifRWStreamWriteBox(&s, encoder->data->imageItemType, AVIF_BOX_SIZE_TBD, &imageItem)); - AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6)); // const unsigned int(8)[6] reserved = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // unsigned int(16) data_reference_index; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // unsigned int(16) pre_defined = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // const unsigned int(16) reserved = 0; - AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3)); // unsigned int(32)[3] pre_defined = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width)); // unsigned int(16) width; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height)); // unsigned int(16) height; - AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) horizresolution - AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) vertresolution - AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); // const unsigned int(32) reserved = 0; + AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6)); // const unsigned int(8)[6] reserved = 0; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // unsigned int(16) data_reference_index; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // unsigned int(16) pre_defined = 0; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // const unsigned int(16) reserved = 0; + AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3)); // unsigned int(32)[3] pre_defined = 0; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageWidth)); // unsigned int(16) width; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageHeight)); // unsigned int(16) height; + AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) horizresolution + AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) vertresolution + AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); // const unsigned int(32) reserved = 0; AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // template unsigned int(16) frame_count = 1; AVIF_CHECKRES(avifRWStreamWriteChars(&s, "\012AOM Coding", 11)); // string[32] compressorname; AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 32 - 11)); // diff --git a/tests/data/kodim03_yuv420_8bpc_384x256.y4m b/tests/data/kodim03_yuv420_8bpc_384x256.y4m new file mode 100644 index 0000000000..3301162637 --- /dev/null +++ b/tests/data/kodim03_yuv420_8bpc_384x256.y4m @@ -0,0 +1,3 @@ +YUV4MPEG2 W384 H256 F25:1 Ip A0:0 C420jpeg XYSCSS=420JPEG XCOLORRANGE=FULL +FRAME +ccca`a_`eeccdddeeeeeeeeeeedddddddddddddddddddddddddddddddddddddddddddeddddddeddddddeeeedeededdddddddddddddddddddddcdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddcccccdcdcccdccdccccccccccccccehgghebfhhghghgdbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccbbbaaa``__^__``abbccccbaabbbbaa`__^]\\[[Z[[[[[[[[\\\\\\\\]]]]]]]]]]]]]]]]^^^^^^^^^^^^^^^^^]\bdcci{{{sOWdb]WVTTTSTSTTTUUVVVXWWWWWWWXWYXYYYXXZZXXXWXWWWWVVVWWWWVWWXXYYXYYXXXYYXXYYZZZZZ[[Z[[\[\\\[\\\\\\]^]]^^^^^^^^^^^^^^^_^_______^^__^__^`___``____``_________`___`_````a```aaaaabbbbabcacbabbacbabcbcbcccbbdW;<@>MhT>>A??<;Tgfeffggffffggfggfffffffffeefeefeeffeeeffeeggfeeeeefgghghhhhhghghjkmpstx{|zsnkhhgntupmosw{n`cb[T_ZOJHFEEFFFGGGHIIKNOMMMNNMOONRRQQPPQQPPOMMMLMNMLMMMNNONNNOPPPPQOPOPQPQQQQQQQQRSSTTUUUTUUUUUUUWWXXYZXYYYZYYZYZYYYYZZZ[[[Z[ZZZZZ[\[[\[[]\\\\]]\]\\\]]\\[\]\[]]]]^^]]^^^__^__`aaa``a`a``aaabbababaaabcbafLKR(" =dihghihghiiiiiihhhhghggghfggghhgggghhhhhhihihhhhhghhijjjklllkjiklnqv|upnlknywuxzw]c_|wYSWMIHHHGHIIIIJIJLMOMOPQPQQQRTTSSSRSSSSQPOOOOOOPPOPPPQPQPQQQQRSQRRRRRRSSSSSTSTTTUUUVVVVVVVWWWXXXXYZYZYZYXZZZ[Z[ZZZ[[[[[[[Z[[\[Z[\\\\\]]]]^\\]]]\\]]]]]]]]]]]]^]^]]]]___^_^__```a`aa``a`abba`abbccbccbeY)%*8(#&&#!Fkigfgghghhfhhhihhghgghhgghghhhihhghhiijiiihijhgghgghhiiijijjjihijknty}rjijmrv{~ztrqsyv]cbkeMKIFHGGGHHHHHHIKKKLNNPOQQQRSSSSSSSSSSRPOOPPNOQQQPOPPPPRQQQQQQQRQRQRRRSRSTTSSSUTUTUVVVVVWWWWWXXYXXYXYYZZYY[[ZZZZ[Z[[Z[[ZZZ[Z[\[[[\]\\]\\]]]\\\\\\\]]\\\\]]\\]]^]]^^]^^^^___`````````a_`aa`aababccbccccM-$($#$$$ Ijgfghggffhhggghghhggggghhihgghhiihghijjijjjihhhhhffggghihjhiijihilmpyvmijnu{~}~{uqnpyv]ccmpzOBHHGGGGGHHHIIKKLLLMNOOPQRSTTUUUUSSSSRQQPQPOPPPPQQQQQRRQRQQQRRQTSRRRRTTSSTUSSUUUVVVVVVWVWYWWWXXXWXZYYYYYZZ[ZZZZZZZ[Z[[[Z[[\[[[\\\\\]]\\\]\\]]]]]]]]]]\\]]\^^^^^^^^^____`_`a``_``````aaaaabbacbbdbccdeX7!#&&%!%Ligfeffhgghgfiggghhhhgggghhigghhiikjihijjjjjjiiihghghhggghijijjikjlnnr{wqllrv{}zxtolpxw]cauk~kp]?BJHHHHHIIIJKKKLLMNOOPRQSTTVVVUTTSSSRQPPPPQQQRRRRRRRQQQQRRRQRRRRTSSSSTTTTVUUUVUVVVVWWVWXXXWXWWXWXYXXXYZZ[ZZYZZ[[Z[ZZ[Z[[Z[[[\\\]]\\]]\[]]][\]]]]\[]\]]]^_^^^^____^^_`__```````a`a``aaababbbbcbccdcdf_>!##.Tjgehgeffffhggghhgghghihghhhiihjiikpnjijkkkjkjkhhhgghghghhiijjihjkmoposy|~wqnkp|~|{xvsqolnwx\c`zyzt{L:HJHHHIIIJJKKLLLMNOPRRRSSUVVWWUTTTSSSQQRSSSRQRRRRRSRQRQRRRRRRSURRSSSTUUUVUUVVVVVVWVWWWWWXWWWWXYXXXYZYY[ZYZZZZ[[Z[ZZ[[[Z[Z[[\[\\\\\\][\]\]\]][\[]]\]]]]^^]^^_^^_`__````_`_``_aa```aabbababbbbbccddcfcF24F_ieeegfefffggghggggghihhihhiijjijjikqsmkkkkkkjijhihhhighhhijjjkkklmpqqoruvwzsomjq~}xvtqnonlmux\cbqx{w{osPfyA?JIIJIJJKKKLLMMMNOPRSTTUVWWWWWVUSSSRQRSSSSRRRSRRRRRRRRRRRRSSRSSSSUVUVTTUUUVVVWWVVWWWWXWWXXXXXXXXXYYZYYZYYZZZ[Z[[ZZZ[[[Z[[[[[[\[[[[[[\\\]]]]\\]]]]^]]]^^^^^_`^_````a```a`_```aaaaabbbaabbbcccdcdedefacggddeffeefefhgfhggghiiiiihhhijiikjjknstomklkkkjjjihhhigghiiijjkkkmnpqspnoprsy~urqoipy{yvtqmkkkkkrz|wy\c`itzvzya=EKIJKKLKLLLLMNMNPQRUUVVWVWXWXWUSSSSSQSSSSTSSSTSSTSSSSSRSRTTSTUTUVUUTUUUVVVVVWVWVVWWWXWVXXWXYYXXYXXYZYYZZZZZZZZZ[ZZZZZ[[[Z[[[\[\\\\\\][\\]]]]]]]]^]^^^^^]^__^^____`a``a```aaaa`bbabaccbbbdccdddeddfeeeeefeeeefefgghgggghhihjihiiiiijkkjntxupnllkkkjiihihhihhijjijkkknoqqponmmoppry~wqnponrvwtsqpmjijkkpvvry\d`{spoR?LKJKLLMLLMMNNOOPRSUVVWWWWXXWVVUSSRRSSSRTSSSSSTSSSSSTTSTTTTTTUTUUUUVVVVUVVWVVWVXXWXXWXWWXWXXXXYYYYXYYZYZZZZZZZZZZZ[ZZ[[Z[[[\[[[[]\\\\]\\\]]]]]]]]^^]]]^^^^______``a`a`aaabaaabaaabbbbbcccccddcdcddeeeeeeffefffggghghiihhiihihiiijikkls~}wsonmjjjjiiijlliilmllkkklmprronnmmnooopu{{qmlnoqttsqsonnkjijjmpqr¿y\cb|vs}~b?ILJKLMMLMMNOOPPQSSUUWYXXXXXWXUSSSSTTTSSSTSSSSUSTTSTTTUUUUUUUTVUUVVVVVVVVVVWWVVWWWWWXXXWWWXYXWYYXYXYYZZZZZZZZZZZZZ[[[[Z[[[[\\\\]]\[\\\]\]\]\]]]]]]]]^^^^^^^_``_``aaa`aabbbaaaaaaabccbccccdccddddeeeeeeeeffeeffghhhihiiiijjiiihjjjknv~xspmkjjjjhjovtpuzyqmlnonprqpnmlmmmmmmr}~rmkkknorqqppomkjjiijllox\c`~~|zAFMLLLMMMMNOOPQQRRSTWYXXYYXXXXVUTTUUSSSRSSTTSSSSSTSTTUUUUUUUUUVVVVVUWVVVVVVWVVWWWWWWWWXXXWXXXYWYXYYXYZYZZZZZZ[ZZ[ZZ[Z[[[[\\]\\\\\\\]]\]]]\]]]]]^]^]]]^^^^_^____````aaaaabaaaaabaabbbcbcbbcdccddeddedeeeeeffeffffhhiiiihjjijjiijjktytqmkijjhikp{tlnrsrqponmklmkmkjlx{pljijlnnqpoqomkjjjiikipx\caM@HNLMNONMNPQQRRSSUVWXXYYYZYXYWWVUTTTSSSTTTTTSTSTTUUTTUUVUUVVVVVUWVUVVVVVWVYXWXWWWWWWWXYWXWXXXYYYYYYYZZZZZZZZ[ZZZZZ[Z[[[[[[][\\\]\\]\]]]]]]^]\^]]]^]^^]^_^_`___a``aa`aaaabaaaaaccccbccbccdcceddfddddedeegfeggggghiihihhjijjiijjlv~vqoliijihiio}|rmpuurponnnnnmlkkjq~ynkiijkmmnnpspmkjjjjkkjk¿w\cbs~Q@QPOQQONOOOQRRSTUVWYXYZZZYYYZYWVUUTTSTUUTTUTUUTTUUUVUTVVVVVVVWVVVVVVVVVVWVVWWWWWWVWWWXXXXXXWXXXYYYYYYZZZZZZZZZZ[Z[[\\\[\]\]]]]]]]]]\]]]^]^]]]^^]]]]^^^___```a`_a``aaaaaaabbbbbbbbcccdcdcceedddedeedfffffgghgihhihhhihjijjiijnwyrnmkjiiiihip}{wqqstsrponmnopomllsxmkiijjkklmqurmkjkloqoljl~ÿv]cb~r}z|n6JRQPNMMONPPPRSUVVWXWYXYYYYZZZXVVUTTTTTTUTUVUUUVVVVVVVVVWVVVWWWVVWWVVVWVVVVWWVWWWXWWXXWXYWWYYYYXYZYYZYYY[Z[ZZZZZZ[]\[[[\\\]]]]]\]]]^]]]]]^]]]^^]^^^^^^___aaa````aaaaaaabbcaabcbbccccbcddddedddddedeeeeffghgiihhihhhhjijihhjp{{urpliihihihjqz~yvtuutrpnonnnoonpsqs|rjjijikkjlprpmllpsutqkii{þu]d`~|PAMQNNONOPOQQSSUUWVVXXXYYZZZ[ZXWVTUTTTTUUVUUUWXWUWWWVWVVVWWWWWVWWVWVVVVVWVWWWVWWWWXXWWWXXXWWYYYYYXYZYZZZZZZZZ[[[[[[\[[\\]]]\]]]]\]^]^^]^]^]]^^^___`__`_``````a```aaaaabaaabbcbcbcccccddedddddedddeedeeffghghhhihihhijijhjlt|sopoljhihijjmoqvwxwvttrqommnnoonntyxz}rkkjjjkjkmmnnmmmrtuqnjjo¿w]cb|y~ǯtPBPOOOPRSTTTTUVVVVVWVXYYYYZ[ZYWUVUTTUTUUVVVVWWWWXWWWXXXXXXWWWVVVVWVVWWWVWWWWWVXXXXXXXYXXXXXYYYYXZYYZYZZZZZ[\[[[[[[\[[\\\]]\]]]]]^_^^]^^^^^^_`_^^`____``_`a`aaa`aaabbbbabbbbcccdbddcdddddcddecdeddeefefgghihihhihhhhhjikoz{pmmmljjihjikmnmpuxxuqoonnommpqpoov{{{vmjikjkiilmmllkknrrnkkkim}s^c`}urDHNQSUWUVVVVVVWWVVWXXYYYZZ[ZYWUVUUUTUVVVWWWWWWWXWXXXXXXYWWYWWWWWVWWWVVWWWWWVWXWXWXWXWXXYXXXXYXXYYYYZZZZZZZ[\\Z[\[\\\[\]]]]]]^]^^^^^__^^_^_```____````a```a``aaabbbbbabbbdcbcccdddddcddddddeeeedfgffggghihhiihhhhjjjmx~unnnllkjhhijlkkmqttqnmmmnoonopnoquyzzunkjijjjjkkmlkjjlopnkjiiigpɿs^cbv~|~TEJUYXWVWWVVVWXWVWXXYYYZ[]\[ZXVVWVVVVVUUVVVVVVVVWWXYXZXXXYXYXYYXWWXXWXXWWWXXXXWWXXXYXXYXXYXYYYYYYZZYZZ[[[[[\\[\\\]]\]\]]^^^^]^_^__`__`````a``a``_a``````aaaaaabbbbcbbcabccbccddcdeddddedddeeeeeffhfhiihiiijiiinxunuyqqolljjhiiijjjknopommmmmnnoommnorvwy{|ywvtnljiijjkllllkjjkmnmkjiihggiǾr^c`||^MTZYVVWVUUVUVVWWXXXYZ[]^]\ZYWWWWVTUTUUUUTUVVVWWXYXYYXYYYZZZXYXYYYYXXWYYYYWYXXYYXXYYYYYWYYZYYYZYZY[Z[Z[Z[[[[[\]\]\\]]]]]]]]]^^^^__`a``a```a`````a`_`__aaababcbbabccccccbccdddcddedddeeeeeefeffgfghiiiihihjijwsxttokjjihhjjiikjmnnlllllmlmnlllmnorvwwuqommliihhjkkklllkkkklmkiiihihhiyöq^ca{}`QPXVVUUTUTTUVWWWVXYY[\\\[ZXWWVVVUUUUTUUTUVUUVVVWXXXYYZYZZZZZZZZZYXYYZZYYXZYZYYYYZYYYYYYYYYYYZZ[[[[[[[\[\\\]\\\[]]\]]\]]^^^_^^^^^_``__`a``_aa`````_``aabaaabccbaccbcbbccdedddddeeedeefdeeefhhfgiihghhhhikky|}~vjijjjjikjjjkkllkjklkklmmklkllnqtvusomkkkjjjiikkkklmlkkkljjjiiiihhhvp^cacjHTUTTTSRTTTUVUVVWXYZ[\[ZYWVVVVVVVUUUUUUVUVUVVWWWYYYYZZZZZZ[[[ZZZZZZZZ[Y[Z[ZZYYZYZYZYYYZZZYZZZ[[Z[\\\\\^]]]\]]]]]^^]]^]_^^^^_^^__^^__^^`__``__```aa`bbabbabbabbbbbbcbcddceddcdeeeeeeeeffffggghihhhhhihjmv~pijjijjjjjijlllljjkkkklllklkklopssoljjjiiiiikjkklnnlkmmljiiihhhghhzp_ca}||yzIHRTSRRSSSUUUUVWWXXZZZZZYWVUVVVUUUUUUUUVUVVVVVVWXXYZYZZZ[[[[[[[[[ZZZZ[ZZZ[Z[ZYZYYZYZYZZZZZZZ[[Z[[[\\]]]^^]\]]^]^^]]]^_^_^^^^_^_]_^]^^____`_````a`babbbabbbaabbbcbbccdddeddddeeeeeeeeeffghghhhghhggghinrz}sijijiijjjkkmmjjkjklmnnlmlkkllmpoljjjiihijjkkjkopmlmnmliiighhhhgmq_d`rGFQSRRRTRSUTTUVVWYYZYZZZXWVVVVWVUVVVWVVVVVVVVVWWXYYYYZZ[[[\\Z[[ZZY[[ZZZ[[ZYYYYYZZYYYZZ[Z^_Z[Z[[[\]][]^^]^]^^^^^]]]]^^^^^^^^]_]^]]^_^__^^_````aaabbcbaababbbbbbbcdccddcddeedeeeeefeeeffghgghggggghhhmpswzkikjiijjjklmpljkjjmqrqopolkjkjkkjiihiihhjjkjkowwrnnnlkiihhhhiiitp_cb~wPLSSRRRSRSSTTUVVWXYYZ[ZZXWWVVWVWWWWWWVVVVVVWWWWWXYYYY[[[[\\[][[[[ZZ[ZZ[[YYZZZZYZZZ[ZZYagZ\\\[\\]\[]^]^^]]^^^^]^^]]^]^^]^^]]^]^^^_^^^___````aabaaabaaaaabccccdbcedddededdeeeffeffeffggggghgghfghfgilrzwklnljijjjjkmoomjhjmrtrurokjkiiiijihhhhihijikmu|}tnmkjjiihijihkpv||¾p_c`~Ƥ]CLSQPQQRSSSSTTUVVXYZZZ[YXXWZWWXXWVWVVWWWVWXWXXYXXZYZ[[[[]\\\\[Z\[[Z\[ZZ[ZZZZZZZ[[\[[ZZ\\]]\\\[\]\\]]]^\]]]]\^]]]]]^]]]^]\]]^]]_^__^__``````aaaaaaababbacbbdccddddeeeeeddeeedffeffffgghffffggfggir~|soqssokkjjjknrsokikmopprqokjiiiihiihihghhihikouzytnnpojihjikkkrutx~xuyq^c`yTBORPQQRRRSSSSUUUWXY[[[ZXYXYYWXXXXXYYYYYYXWWWXYYZXZZ[[\\[[[\\\\\\\\\[Z[[[\\[ZZ[\[\[\\\[[[]]\]]]]\]]\\]]]]]]]]]]]]]]]\\]^]]_^^^^_^^_^___``aaaaaaaaabbbbccccddedddddddeddeeeeeeeefeffgffffffgffglyyxvw{}xnjjkmotxvpkjklmmmmnmljjjihhiiijihhiijilquyyvty{umhhgiouwvtpow||vqw~żo_ca}~wHHONSQRQSSSSSTTTVWXYZ[ZZYYYYZYYZYYYYXYZXXWXXYYXXYZ[\\Z[\[]]]\\][][]\\]\\\\[[[Z\[\[[\\[[\\\]]]\^]]]]]]^]]]]]]^]]]\\]\]]]]^^^]^_^^`^___a``a``aa`aaababbcddddddddeddddddddddeeffffefffggefgffggmz}~~}nlorrtxzumkkkkjkklmlkkjiiihiijjjigijjikv}{{z{wkhhfit~~wpkkqutrps}þo_c_⻜yygSSEPSPQRSSSSSSTUUWYZ[[[ZZZYYYYZZYYYZZYXXXYWXXXYZZZZ\]\]]\]]]^^^]^]^]]\\\\[\[\]]]]\\\\[]]]]]]]]]]]]^]]]]]\]\\]]\]]^]]\]]^]^^____a`_`_`__`a``aaaabbcdcdedddcdcddddccccddeddeeeeeefgfefegggggjt|~}|~pr}}vtutpmllkjjkkkkkkjjihjjjkkkiijjkjjr~{zz{}rhgghhpz{riijloppnqzÿo_cagW}uJIRQRRRRRQRSSTVVXYZ[[ZZYYYZZZYYZ[[ZZYXYXXYYYZ[[Z[\[\]\\]]^^^^^_^]]]]\\\\\^]\]]\]]\]^]]]]]\\\]]]]]]]\\]]]]]]^^]]_]]]]_]^_^^``a`````````aaabaaccccddddeeccccccccacdccdddedeeeeefffefgfghgjntwvy{{z~}ywxz}v|ztsponkkjkkikkkkkiijikjlmmljjmmlknv||yx~wmhghhhjopljhhlnoomp|o_c`sQoygENQSPQQRRSTTTVWYZZZ[[Z[Z[ZZZZZ[[\[ZYZZYYYYZ[ZZ[[\\\\\\\]]]____^^^]]]]]]\\\]]\]]]]^^^]^^]]\]]]]]]]]]]\]]\\]]]]]]]]]^^___```__``````a`aaaacbcdbdedddccbcbbbbbbcbcbbdddddddeefefffgffgggillnortrty|}~zvtutuwy~zutqljjjkkjkjjjjhiijklnnljkorrsuvxz|qhggghiiijjighjlmnms¿o_cag~sh~mVJHQPQQRRRSSTUWXYZYZ[Z\[[[[Z[[\]\\ZZYYYZYZZZZZ[Z[[ZZ[[\\]]]]]]\^^^]]]]]]]^\]]]]\^^]^^]^^]^]^^]^]]]\]]]]^]]^]]^]]^_^^`__a`````````aaabbbbbcdeddccccbbbaabbaccbcbcbcdcddeeefgfgfeffggghklmmmnmpsuyzwrpopqtvz~ysljkjkkkjjijkjjijkmnnmjkqwy{yvz{jggghghhhiihhhikllns¿n_cbxnaozeGOQQQRRRRTUUUVWXYYZZ[[\\[\[]]^_\[[YYZZZZZYZZZ[[[ZZZZZ[[[[[\[]_]]^]]^]]]]]]^^]^^^]^_^]]]^]\^]]\]]]]^^^^^^^^^^^^__``__````````a``baaabbccdddbbbbbaaaaaabbbbcbbcbccddddeffgfffefffghjjkkkllmmoswwrmlkmnrywpkkjikjkjijkkkkkklnomljlqw{{xxqhghggggggghihghjknquo_ca|xjZPPRPQQQRSSSSTVVXXXZZZZ[\[[]__`^\[Z[[ZZZZZ[ZZ[ZZYYYYZZZZZ[Z[\]\\]^^^^]^^]]^^^_^^^^^^^^]^^^]]^\^^^]^^_^^^__`^^_```__`_`__```a`aa`aaaccccccaaaaaaaaaaaaaaabcbabbcddcdeeefefffeefghgijjkjjjklotvrnlkkklrwljkjjkjijijllklmmmnomlkmptuutx~{mgfggfhhggghghgimpqu|ýo_c`zE|`BQSOQRQRSSTTUVWXWYYZZ[[[\^_^__^]^\[\\\\[[[[ZZZZXYY[YXY[ZZZ\\[[\]]]]^^^^^_^]__^]__^]^^__^^^]^^__^^___^`__`^^_``^_``^_``aa_`a`aaabcbbbabbaaa``aa`aaaaa`bbbbbbbcddeeeefeeffeeeefghhijjiijjknssolkkjjny{umijjkjiijiklkkkllnoolkkkmnpou|zsw~vmihfffggfggffhilpstxĻn_c`Jr|EFRSQQRRRTUWXZZYXYZZZ\\]]\]^^]^]^^^^^]][\ZZYYYXYXZYYYYZY[\[[Z[[][]]]]]^]^]_^^^__^]^_^^^^]]^^^__^__`^__^^_`^_`_^____`_````_``aabbaaaa`a``aaaaaaa`aaaaaaabbabcdedeeefeeeeefegfgfhihhijiikmnomkkjiluz|}ytqolkklkkihhjklkkllmnnmlkjjjlmpz{sw{wpllkgfgfggeefghloptv~Ŀn_cbuyUγjCCMSQRUTQMIFIRVYZYZ[[\[ZZ[[\\[]\]^^^]\[ZYZYYYYXYYZZYZZZZZ[Z\[[[]]\]]]]^^^]_____^^^]^^^^]_^^^__^^_^__^^^^_^_^^__^^``_````````aaa`aa_``a`aaaa`_`aaa``aabbccddededdeeeddfffefgfgghhhijjjkkmljkmlp{vqstpkjikjjkkkjijllmklmmmnmljiihimrz{xzytnllihfgfgfefgghmqsv~n`cb}{~xUзTDKMFJWp\VKKZYYYXXYYXYZZZ[\]]]]\]]][ZYYZYZYYYYYYYYZZZZ[[[\[\\[\]]^^^^^^_``____]^^__^_^^^__^]__^^^]^^^^^_^^^__^___``_``aaa`_`````_``aa````````aaabdccddeeeeeeedeefgffffggghghhijjjkjjjmstuzonoljijkjkkklklmnmmmmmmnmlljhhhiozz|zurnjhggfgggffgghjmtx~n`c`w}t[anyīYX|ERXUUWWWWXWYYZZ[\]]]]]]]\\ZZZZYYYYYYXYYYZ[ZZ\[\\\[\\\]__^^_a```_`_^^_________^^]^^^^]]^__]^^_]_`^^^`___`````aa```_````_````````aaaaaccabcdeedddeddffeefgfggghgghihjiijkiinvy}{nlljiijjjkjkmmnoppmmmmmmlkkhgggis~{vrlhggffgfgghghjkoxn_c`~~^h$UzUIVTUVVVVXYYZZZ[\\]]\^^^\\ZZZYYXYZYYYZZZZYY[[\\\\]\]]]^^^___``__^^`__`___`__^^^^_^^^^_^^^_^^_____``__``_a```a````a`````a```a`aaa`bbbbdcddcdddceeeefdefeegffgggiiihihijjjnw{~uljjjiiiijjklmnopnnmmmmllkjihhgfht{vrkgggffgihhhhihjrzþn_cajYqazFYTTUUUXYYYZZ[[\\]^^__^^][ZZZZYYYYZYZZZY[[[\]\\][]\\^^^^^^__^^____________^]]]]^^^]_^^^__^_]___^]____``a``a``a```a`````````aabbbdbbccdcccccddcddfdefdeeffggghiihhgihhipvyz{{|~}umkjihhhhikkkmlmnmmlmmmonmkiiihggipy}volighhfgilkiiijlryn_c_{|}fXwƽt:TVWXWVWXXYZZZ[\\^__`__^]]\\[ZZZ[ZZZ[Z[\\\[][\]\\\]\]^_^^^^______^^____^]^^^^]__^^__`_`__^]^]_^^^^___```aaaa`_```````a`aa`aaaaacccccdcccbbbccddedeeeeeffggihihhhhihijqvwuuurtwvqmliihggijjklllllkkkllnppokjihfhghkpt{~rkihgigghjmkjjlpv~o_cbim{aSFEFMXXVVWYYYZ[[\]_```__]^^^]]\[\[[[[\\\\[[\\[\\\\][]]\\\^_^_^_^]^]]]]]^^_^__]^^___^^_^^_^^_^_``__``a`aab``````````aaaaa`aaaccbcccbbbbcbccbedeeddecddeefgghhhhhhiijnsutrqnnnptrpljhigghjijkkkkkijjkmnqqnjihgggggikpv~rkihhfgghijkjkoyn_c_{bf㽐jCNVUVVWXYYZ[\^_```__`b``_]\]]\Z[[[[Z[Z\[\\\[[[\\]\[[]]]]^]^^^]]]^]^^____^_^^^^^^^^_^^^___`a```a``aaaa```__```aaaaaaaaabacbaaabbbbabbcbcddddccccdeeffghhhgghhhkqvvronnnnpqqmjgghggijiijjjiijijlmnooljighfegfgjms|qljihgfghhikjlrÿo_c`iyПTkvݩhGRZWVVWYZ[Z\]^^_^``aaba``__^]\\[[[[[]\\][[[\^\\]\]\\]]]]^]]^_^_^^_^^__^_^^^^^]_^^^__```__`_``a`a`a`__````aabaaaaaaaaaababab`abbbcbcdddccbabccdcdegghhggghgilrwvokkkmnnmjiggfhgfhijjjhihhjjlnnnnmljhgfffeghilw}{smjjihhgghikkmntn_cbuu[wKiֳOEOXYXXYXXZZ]^^^^_`bccccbbb_^^]^]\]]]^]\\]^_^^]^_]]]^^\^^]^^_____`^_]^^^^_^^^_^___`_a``_````aaa```_`_```aaaaabaaaaaaaab`aaaaacacddccccbbbbcddccdfgggggghhhjotqlihijlmkhhggggghhiiijhhhijmorpnomlihgffffilmt|}umjjhhgfghjijmos{o_c`|zS`yڪޠdGFOVY[\]\^^^]]^_`ccbbbbccccba`^^___^^_^^```___^]^__^^^__^^_``_`^__^__^_____^__````__^_``a`a``_``````aaaaa``a`aaa`a`aa`abaacbcccccbabaaabbcccdeegfffffghhjmkihgghhihgggggfghhiiiihijjknrssqonmkiggfehlrx}|rjggggfgjpqojmqxp_c`vFI˒dRKGIKOQXZ\^_acda`_``abbccb`_``_`____abb_```^]]]__^^__`___`````````__`_`_`````_`______``````_`___```a````_````a``aaaa`abbacbaaabbbaaabbbcfdeeeeeeefefhhihggfffffeeffffgggiiijihhikknpsvusqoligeehlrw|}|vnifggghhoy~vmmt}¾p_cawzȬc3Nw㼪ܴį˫ztc^WRPPQX^a^^^^^^``bbbbba``_`acdccb``_^]^^__^^____^_```_```a``````a```_`````__^____``__`____``````^____a`a```aabaaabababaa`aaaabbccddeeedddddeeggfffeeedddeeeeegggijjjihhiikmquxxvsokihfhkotxwsolihgfghhhsxnnwľÿp_c`us~vuz|wz+AOϨ´ЯʺѡǿwVMX_]\[]]^`bcccba`_`acddcca``_^^^^__^^]_^^^_^^_``````a``a`__________^^^^^^^^____a_^_`_^__^_`````a``_`aaaaa`aabaa```aaabbbdddcddddddddeededdddcceddeeefeehjkiihhhijlnrvxxvpmjiggjmpqqmjihhfgfghijxvopq^d_}yd)@R­Ƹ̼ݶۤbIV^ZZ]_abaa`aaa`aaabca`a_^^\\^]]\]\[\]^]]]]^]^___````_`^_^^^]^^]]]\]]]^]]^^_^]^^^^__^___```_a`__`_``_``_`aaaaa`aaaababddcccdcccdcdddddcbdcbccdcdeeffhjkjighhiikkosuwvtnkihfhlqqmihigfgffgghhn{}vrsû¿q^c`v}@,<]ʵѶ^IY\\]]^\[\]^___^___^\\\\[[[\\[[[Z[Z[[\[[[[\]]]_``_^__]]\\\\\][[[\[]\[]\]]]]]^^^^^^^_```____^_^_^^_```_`_aaba``aabbcaccccbcbbbcbccccbbbcccccbcddefgijjjiihiijklmosuuqlhgffhnqokhgfefgfffghhkt{}{wv¿q^c`9)@Z|κ޹NN^]\[YYYZZ[]\\]\[[[[\[[\[[[[[ZZ[ZZZYYZ[Z[[\^_``_\\]\[[[\\\\[\[[\\\\]]]]][\]]]\^]]^^^]_^]^]]]_^]____``caaaa``aaababcccccabbcbbbccbcbbbbabbbcddefgijihiihiijkkkmoqsrligffhlmjhgfeeeffgggghjls|}ÿq^c`}w5*CWµϼʽuH\\YXXYWYZZYZ[ZZZZZZ[[\\\Z[ZZZZYZZZZZZ[[[]^___]\[[[ZZZZ\\\[[\\][\^]]^]]^\\\\\]^]\]\]^\]]\\]]^]^^___`aaaa``aaaaaaacbabbacaaaabbbbbbaaaaaaabbcdeikjhhgghiiikkkmnpsrokjgfgihggfeeeeffhhghhijp}þq^c`6.D\̾ҪKW\WWXXXXXXYYZZZYZZ[[[[ZZZZZZZZZYZZZZ[[Z\]]^][[ZZZ[Z[[[[[\]]\\]]^_^]^]^]]]\^]]\]]]]]\\]]]^]^^^^^__^_`aa`_`aaa`abbbaa`aa`aaaaaaaaaabaabcbbbcdgkkhffggghhiijjkmortqnlihhhgggggeeeffggghhiko{q^c_~z41qαTP\VWWWWWWWWXXYYYZZZZZZYZZZZZZZZZZZ[[ZZZZZ[[[ZZZ[[[[[[[\]\]]]]__a``_^^^]\]\]]]\]]]\\]]]^]]^]^]^__a````a````aaabaaaaaa`a`aaaaaaaaaaababbabdikighhhhfefgggjkmnpssrpnmmkihfefffdegghiijjkovp_c`zrrr7.Av~ѽʞYK[WVWVWXWXXXXXYYYZYZYZYZZYZZZZZZ[ZZZZZZZ[[[Z[ZZZ[[[[[\\[\\]]]\]____^\^\\]]\\]\\]]\^\\^]]^]]__^``a``a`````aaaaa````aaa`aaa`aa``abbbaabbdfjjhhhghgfdfgghjklmorsttsromkjihghgffghijjijmqu~o_cbobhm;2CpjEXYWVWWXXWXXXYYZYYYYYYZZYZZZZZZZYZZZZZZ[[ZZZZZZ[[[[\\\Z[ZZZZ[\]]^^]]^][]]]\]\\]]]]\]]][]]\]^^^_``^``ab`_`a````a```aa`aaa`aaaaaaaabbbcfikiggfeffeeeghjikmnorsuwwurqnlkikkjhgihjkjkmptv{m_ccjp|~p46FpǵIOZVVWYXWXYZYYYXYYYYZYZZYYYZZZZZZZZZ[Z[Z[ZZ[ZZZ[Z[[\[ZZZZZZ[[\^[\\\\\\\\]]\\]\]]]]]^]\]\]]]^^^__^^_a`_````a__````````_`a`aa`a`aaaaceikiheeeedffggghjkkloopruxxwutsonmmmkijijklmoruwx|m`cbvrs~r64GsΪcGYWWWWWWXYYYYYYYXYYYYYYYYYZZZZZYZZZZ[Z[ZYZZZZZ[[[Z[Z[ZZZZZ\[\Z[[[[[[[[\]]]][\]]]]]\]\]^\]^^^^^^_____`__```_`_____`````a_`a`aab`bchjjihfeeeefgggggijklmmoqtwwwvutrrqopolklmnnpswzyy{l`c_tuwj34Do«ͳIXVVWWWWXZXXYYYYYYYYYYYYYYYYZYZZZYZZZZYXYZYZYZZZZZZZ[Z[ZZ[[[Z[\[[[\\[\\\\\[\]\\\\\\\\]]\]]]^^_^^_^_____`_^`_`^_``````_``a`babbcdijihggfeedffggghijjjjlnqtvxvvvuussqpponpoprsvyyyxy|~jac`}~n15DkʲɴȵɽE[VWVWXXXXXYXXYYYYYZZYZXYYYYYYYZZZZZZZZZYYZZZ[[ZZZZ[ZZ[[[[\[[[[[\\[Z[\\[\]\\[\\\\\][\]\]]]^^^^^^^]^`____`_``_``__```_``_aaaacdhkjhffggfedeffggiikjjlmnruvvwwxwxvttrrqqrsssuvxyyyz|}had`|w{^14GaβݼǵuJZUVWWWWWXXXWXXXYYYYYXYXYYYYYYZZZZZ[ZZZZYZZZZZZZ[[Z[[[\Z[\\\\[\[\\[[\\\\\\\\\\]\]]]\[]]]]]]^^^]^^`_```_```_____```_`a`aaabaehkkhggfffeeeeefghhjjjjknpruuvwyzzyxvvutttsttuvvxyxy|~}||~haca{{nzQjg72Mbrټҿ乥Ũ]OXUVUVXWWWXXXXXYYWXYXYXYYYXYYZYZYZ[ZZZZZZZZZZZ[Z[Z[[\Z\[[[[[\]\[]]]\[\[\]]]\]\]]\\]]_\]^]]\]^^^^____`_`__``__``____```aabcfjkigffeddeeeeffghhijijkmqtsuwyz{|{zywxxvvuvvvvxyzwx{~}}~~~~{zz{gac`zagyv>Ihj;3JM[ƬЮNSWVVVXWWVWXWXXXYXWXWXYXYXXYXYZYYYYYZZZZYZZZZZZZ[[[Z[[[[Z[[ZZZ[[[\\\\\\\\]]^]\]]]]^]]]^^_^^^_^^^_^__a`___``____^_^^__```begijgeeecceeddffghhiijijlmpruwxzzz|~}}|z|{yzyxyz||{zy~~|{{z{|~~~{zzzgac`taIJ@A<5I?I­Ⱦ޺JWVWWXWVWXXWWXWXWXXWXXWYYYYXXYZYZZYZZZYZZZZYY[Z[Z[[ZZZZZZY[[[[[[\\\\[\\\]]]]]^]]]^]\^]^^^^_^^__^`````__`__`__^__^^_`__`bdhhhfdedcddedfffhihhiiijlmqtvxyz{{|}||{~~|zzzzz}}zzz||zzgac`yhHKF>>0GDCu߬ɳϸߝEZUVWVWXWWXWXXXWXXXWWXXXXXXXXYYYYXYYYZZZZYZZZZZ[Z[ZZZZZZZZ\[\[[[\[[[[\\\]]]^]]^^]]]^]^]]^^^^_^_^___`a`^__^^^`^^___`__`beghfdcdcdcdcdefgiihijkhjmqtxzz{|}}}}~~}zz{{{}|yyz{|zzgac_|tvkw}SKJD=;5KDCa˭νĮjNXVVVWXWWXWWWWXWWWWWWWWWXXXXXXXXYYYYZYYZZZZZYZZZ[ZZ[[[Z[[Z\[[[\[[[[[\\\\\]]]]]\^^]^^^^]^_^^___^^_`^______]^^^^_^_^__aefggedbbbccdccddghijkklkmpuxy{|}~|}{yxxyzyzgad_z~{JKHE<;2OBFPؿƵKWVUWWWWWWWWXWWVWWWWWWXWWXWXXXYYYXYYYYYZYYZZZZZZ[[Z[Z[[[Z[[[][[[[[[\[[[\\\\[]]^]]]]]^^^]^`_^__^_```^_^__`_^^___`^_``dfghfecbbbacddedefgjkkklnqvxy{|}zyyyzyz{gaca~pGNJA;:4I>G@nݶ½»}KXVWVVWVWXWWWWWWWWVWVXWWWXXWXXXXYXXYYZYZYYYZZZZZ[[[ZZ[[[[[[[Z[Z[[[[[\\\\\[[]\[\]\\\\]]]^__`__^__^_`_`__a`_`__`a__`bfgfddccababceeeefggikjlnrvyzz{}~|zzzzz{{gac`|{]JNH>?87RAKLWƲHXVVVWWWVWWVWWVWWVVWVVWWVWWWXXXYYXYXXYYYYYZZZZZZZ[ZZZ[[Z[[Z[[[\[[[[[[[[[[[\\[[\\\\\]]]]^^____^_^^^```__`_``____``aefeeddbbabbbceeeffgijkklpvyz{|}~|zyyzz{}gad_`GMFA?87QDBFL~ϷԹeQWVUWXWWWWWWWWWWVVVWWVVWWWWXWXWXXXYYXXYYYYZZYZZZ[ZZZZZZZ[Z[[\[[[Z[[[[[[\\\\\\[[[]\\\]]]^]]_^^^^^_```__`_____^^`bcffdeddbbacbcdefgfgiklmnpsux{|}~}{zyz|}}~haca}YJNG@B:9O=GJHl縜GZVVVWWVVVVXWVVVWVVVVVVVWWWWWXXWWXYYXYYZYYYZYZZZZZZZZZZZ[[[ZZZ[[\[[[Z[[[[[[[[[[[\]][]^]]^]]^^^___``_`a___^^^^_acddfedccbbbbcdeffhhhjnnooqtwx{|}}~~}~~}{{{|~hac`~~{zVLLFA@:6M?HKIV涐SUVVWVWWWVWWVVVVVVVVVVWWXWWWWWXXXWXYXXYYYYYZYZZZZZYZZZZZZ[[Z[[[[[[[[[[[[\\\[[\\\\[\\\]]\]^^]_`aaa_``aaa````^^`accddddcccbccddfghhjkmqopoprvz||z{}||~~|}}}~hacbw{uHGLF@@68NAIJEEx׮JYVVVWXVWWVVVVVWVVWVVWWWWXWWWWWXXWXXYXXXYYXYZZYZYZ[ZZZZZZ[Z[Z[[[[[[[[[\\[[[[[[[\\\\\\]]]]]]_a`abaaabbbbbca_^`bbbccccbcbcddeefhijjknnonooquy|{xy~}|||{~~}}~hac`~sylC3IF?B88P_ͮɹEYWVVVWVWVVVVVVVVVWWVWVWWVWXXWWWWXWWXXYXXYXXYYYYZZZZYZZZZZZ[[Z[[Z[[[[[[[[[Z[Z[[[\[\\[]\]]]^^`bcdefeeffdbca_``abbbbabbabcdefgghiklmoponmmpuy{yy|}}~~~iac`oyugaH?HE?B58Q?G?CAJ|ͪȷ÷zIQWXVVVUVVVVVVWVWVVVVVWWWWXXWWWXXWXXYWWXYYYZZYYYZYYZZZZZZZYZZZ[\[[[[[\Z[[ZZ[\[\[[[\[\\[\]^_`efhjihhhgea```aabaababaaccbeghjhjjklnpqoommrvxyz{~iad`~~lh`KLHF>>69PBHG<8[v—سŃfLPUUVVVVVVVVVVVVVVWWVWWWXXWWWXXXXXXWXXXXYYYYYYYZZZZZZZZYZYZZZ[ZZ[[[[[[[[Z[[[Z[\\\[[[]]]_`dhiijhghfecba`a`baaaaaaaabccdgijjjkllprspooptwxy{}iac`xtwiPLJHF=<9=49N8Nz~vvrn~i`ܼ˹}sTKQWWUVVVVVWWWWVWVVWVWWWXWXXXXWXXXXYXXXYYXYYYZZYYZZZYYYZ[ZZZ[[Z[ZZ[[ZZ[[\\]^_aefhjkjiijhfededccdcddccaabaabacddfhijnqrrqrssuvz{{yy{|}|{|~~iacapntt\NMIFD:<0:KU~|xxushs~j5bNsorҴı_LOUWVVUVVVUVVWXWWXWWWWWXXXYXYXYYYYYYYZYXYZYYXZZZYZYZZ[ZZZ[[Z[[Z[[Z[Z[\]^acdeeghhhgfgfdcccceeffedddcbbabdefhiijllsuwvuvxyzyyyxy{{|~}}}{yy{|~~~~~iaca}yv}^PLHIF<=0,d{yxxvtkm{Ki#:QvǠͱʷbRKTXVUWVVVVVWWWWWWXXYYXWYZYYYYXYYYYYYYYYYYYZYYYYYZZZZZZZZZZ[ZZ[[Z\\]`cbddcefgeeeddcccbccegfgffeeeeedfghjlmnprsvyyyy{{{{z{yy{{||}~~}|{{zzxxy{||{||}}~~~haca{xWPMJIG=?&h{yyxwvtpgy~lm׊uzxttvuuqqnt]Hq_j}{}}}}sw{{v~cMTZZZXYYXYZYYYYZZ[Z[\^__````_`aadefghffeefijjjmmmopqrqomjhgiiijkklopoqrtuuuwwwwwvwxwyy|}~}}{yzzzyyzz{zy{z{z|}||{z{~~~~~~|}}~~~~||}||}~~~~~}|zzwttqonmpebca}ps~WOPMJK֊tyuuvvussqon|lFj<}zzzz{vquwvhamsimxzXMNSYZYZYYYYYZZY[[\]^__^_``_abbdffggggeefgijijjkjlmpoonkgddeeeghijmmpqststuuutttuvxx{}}}zyzxvwxxyyxyyyz{z{z|}{zzy{~||}}}{|{{||~}}}~|||}~~}~~~}}}{xvtrqnnmmndbd`rv}{y}~\QPQBщtwtvvvtrusqlt1M?W~~uosvf^\^ga\isgbtw{bNPYZYYXYZZZ[[[]]^_^^__^`acabdeffffeedefghfggghijlmnlgecbbddfghjlmoppsrrqpqqrsvwy{zyyxvwvuuvwxxxzy{zyyzzz{zzy{}~||z{{zyyxyyz|}|}~}}zy{|~}~~}zzxvussqnlmllnebcayzwx]QSKY͆tuvvvvtuttqop|m2vsmsynd|f^]`eha^ekccvsrno{iNWYXXZZZZ[Z[\]]]^^^]^`ababbcecccbcccddeedddfggijijjjgdccdffhjjklmnoommnpqstvuvxvvtstrsuuvwwxxyy{zyyyyxz{y{~}zxyzzyyyxxxxyz{|}|}|{yxz}~{{yxxurqqoomklkloebc`}y|QOPEsvwuutuuusqqou}Ndplt{c]_bnd`]]fib_omegegkm||UKYmtx{_drRV[XZZZZ[[[]^^\]\]]]^aabcbbbbbbbcbceddcbbdefgghghkkkgdccdcfhiijjjllklnprsssrrrqrqpqruvwvvvwxxvxzzyyxwx{}~}{yvvwyxwxzxwwxyyy{|}}||{z{|~|zxwutsponmmlkjklnebcatp}l||STPVֿxtvvvttuuvstrppy}~)KsVlluvdV[hjhb``fgc^bli`a`aehkxmIMXVZn}x}VQ[ZY[[[\]^^^]^]\\]^_`bdcba`abdcbbdcbaccdedefgggiklifedccdeggiiillmlmonomooppoprqpruwuvvvvvvuwy{{xxxy}~~{yxwtsvxxxyxwwuvxyz{|}}~~~~~{xurrqonmlkkjkkklldbcazxyyPTG|صqtvussuuvttttrmt|}QPg9{jnus~{x~|tomd_`fkh^\jZJYYQUfzfMW][\\\]]]]]]]\]\]]_`bcaaaaccccba`aaccccbddedeghiigeeddccffgiilllllkllmloprrrssstvxwyyvvvwvx{||{yz{~~~|zywwwutuwxxxwvvuvuwyz{}~~~~~~}~}~{yvsqoonnmkjjjjjjkoebc`luRTIڴovvurtuvusvusprqx}~~k#W}xjrqu{xqlhid^^dle_cusHNYWTP\wZQ]]]\\]]]\\]]\\]^^^_aa`abcbba``__acbab`abbcdfhhiihgeedbdefghhiijkjkmnopqssstuwvwyz|{yxwwwy|~~||}|zxtsssuvwwxxwwvttuwvwyzz|}~}}}|||{zyyyvwsqpopnmlkjjjiijknebd`y~QPUحpwwsstttsuvtrssrq{|}"5hkoqnQ%>xtjsn}|yvpstlc\^hpv~eIOY[VVcqrQW^][\\\\\]\\]]]^^^__``aaa_`____aba``a``aabcefgjiihffdefefghiiikklmopqrsttvvvvxyz||yxxxwy}}zyyxuusqpqtvwxyxwvutttuvz{|zz|~}}|{{{zzzxvvvvtsqpomnnllkjihhijjldbccomwy{qv|~rNMhޢpwvssturtuutstrros}}~}R<"9bopsl}v|mjvsnhdit~[HOX[ZZq^Q\\[[\]\\]^]^]^_^^^__`__^]^^^_`````a`````acdfhhijhffeefefghijklmnppprrtuvuuuvwxwwwwwvwy{|||{yvvussrqqqqrtwwvvwvustuvx}}||~~}{|zzz{zyywwuttsrrpopommlmlkiiiiiijldbca}v|}xvYRJlۙowtsutrrttusussqqqw~})$I~|nvks~XGRYXUctwurNY][[[[[\^]]^^_^^^_^^__^^___``__`__`_^^^_acegffggfffffeeeehjmopqqpoqqrtutrrtttttuuttuxxwvvuttsrpoomnooprttttuutsuxyz~}}}}~}||{yzyxzyxwuuttqqqonnoonnmmlljiiiikjkldbc`}~gSJqՒovssttrrtussutrqtrry~}mOQYG!vtrshyrkder|{xNJWUOKf}fxWS]\Z[[\]]^_^^]]^^__]]^```````_^___]^^^^_`bcceedeefffgghiikmpqrppppoqqqqqpqqqqrrqqstutssrrrpnnnlllllmmmprsqtusuwyz|~}~~~}{{{{zyzzxxxwxvutsqpoonnnnmllmmmkjijjijjklebc`ym~VRHp҅quqrsrqsttqtutqrrrqp{~L!6vrpnunsyy`Y]\]]]_alrx}iS[dfJSTSHercvhO[]\\\\^]]]]]^]^^^]^__`_`__`^]__^]^]^^^`aabdddcdeefiiikkjnoqoonnmlmmmmnlmnnoppqoqppppqqonmlljjkjkkkjklnpprtuvwyy}}~~~~}{z{{{zxxxxwvwvuvtspnnnnnnlllkmmlkjjjjjiiijdbd^w~un[RNizqtqrsrqssrrstsqtsrprs}~W3>;*Kvosi{muhWZ]^_^^^___baelt~vkab`hwzMQSSJ^]\w~QY][\]]]^\\]]]]]^^___^^^_^^^^^^]]\]]^__`a`bbbbdfgghhijjjmonljjihihhijiijklmnnnnnmmnonoljiijigijihhiijknnprruuvz{}}~~||z{zyyxxwvuutuutusrqnnnnmmmlklmmkjjjjjijiikdbcauy|u{wWQP\usrorrpqrsqrttrrtspqrqs~}~xrA1]zHR~pul|sja\SW[\\]]]]^_`_^_]`am{}n]Ybj[T^duITSQPgcZvYW^\[]]^\]]\\\]]]]]^^^^^]^]]]]]\]]\^]^___^_`abceffffhijkjiheeffffefgghijijllkllkkllmmjjjhhgfggggeghijlmnnorstwxz{||{{zywwvuttrqqqrrqrsqqooonnmmlkkkkkkkijiiiijjdbcbelm|xymQONNtsrprrqqrsppsrqtsqqsrrqt}}|r)9<(#_xmy{T^]]]]]]]^]]]]_^__`___^```_^]^]]]\]adeddeecddbbaaaabcbcb`aceegfffedcccccedfggghiiiiihihggffeeffghhhhijlnnooonomooqrrqqqppqqpppqppppoonmnmlllkjjjigfffhiijldbc`olIONMEߪlrqpoqqnoqmoqpoqqpqpqqpoqqswywvz}}~LAwu#806jO9WM,P|~msokxqq{aOPQSUWXYZZ[[[\\]\][[XYVPPMPQIGJKIKMUYZZXWTRPQPLISo|`?=::dkU_\]^]]]^^^]^__`_``a``abba``_^^_`bcdededccbaa`aaabccccbcefgghgecccccacccddeeffghiihiiiiggghgfhhiiijllmnnmmnnnnoqrtsrqqqrqqpppopnnnlllmlmmmlkjjiihhfhjkklldbc`xwv~sdLOOLGWޥnpqqnppoopooqpprqopqqppoporsvxwvwz|}~t66NQ(;AL] V~}~umtiozqr{x}QSNORUVWYYZYZ[\\\\][XXRNRMLMMLIKMONLIQ[YWWVSROORQNW~GA?znsqnppmpolnpnnqooponpppooonnoortwwtstuxz}~~}~~}|olpim}}~wqst{TTUPOPQTUVUXYYXXZZXSMMMJMLKJNQSRNMOMMKJHDMYVUVRPQPONPPLNbyfTXB=DMMMORRQQQgaccced][[ZZ\emusgb_\]bghjijkjigdbcbdbbddcabbbeffedeghjjfedcccacbbcddcddcddcdbbbceddeceddceefgijklllllnmnnnopprqppqqqqqqpppnmmnmlmnmlkklnonlkjjiiijjjjijdbcatdjcx{ztMLNNIGB;Dmsrortnnpmloomnpmopnmpoononnnmnppsxxwutvy|~~}~}~~~}}}|z{{zklnhv~xxuqpqxVVYSMOQTUUVXXXYXZYRLLNJLJMLMQRPMMONLLKKJIFLYVTUROOPPOPQOQltmyyK>BGOMMOQQPN]fceg`b~ſe`ikkjkjhedccdddeffcbbcegffeceghiifdbcabbbbbccdcdbddcdccdefgddeddfffffhikkkklmmmnnnpoopqqqpqqpqqpqqomnmmmmmnnmnnopponmljjjjjkjjjjjdbccd}zosqz|ONPNIFCA:]ʣjsqqorqnnonopnnppmnpmmnmmnnmmmmnomorwywvvv{|{z|~{y{~}|}~}}~~~}~~}}}zyyxywwvzuhkhi|xsnnoptKY[[LNORSTXVTVWXVOMNMNIILLLOQPNMOPMLLKKMIFDKXUUSPPQPPOJLNOYvprC<7*BoqqpppnqnmoolnnmlnnjmonjlnmllllllljkkigmuuppoqsvtsutuwwxwxxwwyxvxvuvtsutstqpqrppqpgehiplefhhGKH[aIMMNNOOPLHKJJLHKNORSQQQPQQOPNMLLKLMKLLHFBGTUQPPQOIUexpowvnYEEV[JLMNMKJMKJJJNPRPQNPRQQPNOPMLKLLKLKKKJJKGECAQTQOU[VOROrz|niluxbTdQUx|v`gilmllmt^epүpqqponmmknqtwxxuromjhgfeefedeeeeeffgghiijjhhihijkjjklmnnpponpqponmllkkjjjjjknmmoppqqpnonnmlllllnnqttrrqpmlllkkjjihiikjjkdbccevys^ILIIHE@;<716/_{nqqqrpmpnlmpnmlolknmkkkmjijllljkjjjhhhhgfhmuxrpqqppoonprstvursrppqqoopommmmlkid`adccd^sb:@IE`KJKIJJLJIHKMPPPPMOQPPPOOPMLJKKLJKKJMLMLKHDCBPTPOOQRQOZ{mfixtpeKUOPjeefhlollniccgcc{ǸZebzrqspoqsttvwzzurnljihgggfefgggfghhijijkklkjjjjklmmlmnppqrqppqqqmmkiijjjijjkmmmnoopqrqpnooonmmllmoqrsronllkkkjjihffgijjjkdbca~}~{t~ukZGFGHGC@@:4.41#]zmrqpqompoknonlmnljmnljjmlikkkkjjjjihhhhhhghjoqrsrpoonmpopqoonnnnopnnlijjiigfd`a\^^aieu>CDBDIBLIJHGGIJKJKNMMNOONONMLLLJJHFKJIHKLLLLJIGDBBPTOPQPNPW[k|zhdjvek]@OQTfbcejknnnnpnklkn\ecorpstwy{{zwwvsqooljjijkihhhhhhiiklllmnlmlkkjjkmnonoqrrtttrqqoomkihiiiiijjlmlmnnopqqponqrqpnmllmpopponlijjijihggfffgiiiidccaw~vg_GIMKHEE==73/52 Yznpqpqokoqlnopllnmjlmmhjllihjklkjijlhhhhfhhgghjmnppppqolkhfihggfghffededccba_^[XafineID>??K=GHEEGGJJKKJJJLNOMNNOMJIHIFEDFKIIKLKLKJJIGDBBNROOT\YROPelqa\lx^nICRW```agknppppoollhfuYigntx}~~xxvutuutssqnmkjikkkkklmnnnnonmmlmmlmoqrrsttsttropolkkihhhhjjjklmlkmnnooqqqpqrsrqommmmoonllkiihhggggfefffghjhfccc`~uuVNMLEEEB>>98243" Sznoqrppnmpnlnonkmokjlmlhjllhjkkljjkhiiigggggfffghhloooqpjjiigfeeedddbcaa`]\Z[_eijkniw`<>=AJ@:FFEGIJLKLLKLKLMMNKJJIGHGFECDEKKJJKKKKKIHFECBMQPQYb[[^TLXus\gumZi9JX][^_diknpqpoonmlkjppWiit{}{zz|~}||{xsonllmooonmoprqooqoonnnnprssssrrqqqonmkkihhhhhijjjkmmmlmnonoqqrsssssqonmmmmnlkiiigfgfggffffffggffeccca|{|~sphMPLIFGE@@<:7.61#Hzpooopqmloolmpnjlmmiilljhjlkgijkjijjjiihhfgggfgggfhhknnnljhhfedb_]\]\[]\\]cefijjllrz@E6W\Y\]^dgkooopoookiiopyaYfkp~~{wsqqqssrsrqrsstrrsqppppqqrqqppommlmmlllkiiijjijlllmmoommmmooqssttrqppooommmlkjhijgghgggffffgffefedccd]~zsl_HJEHIJHBA>:6.31('"!>vtnppppmlpomlmolknnkjjlkjiklighikjjjjjiihhhhhhhgghhhihhgeeedbbb`]````decfhhglllogCI<7M@A:?FFGIJKLLLMMNNNLLFFJGDE4134?GIKJKJKJIJKHHIDCBIPOPNNOFENNSgbVdsNoXK^XZZ\`ejmnoopnmmkjixaZhnz~~}|ywvuwwxwvwuuvuuvtsqpqrqqppmlllkjkjlmlmlkkkllkklnnooopnnnopqstusqqpooppomkkjiihhihfhgggggfggfgffeedcd_~wuxMIOLJJFB??>94)2.&'&&$5pumpppqokmpmkmnnklmmiijkjjhjkkghjkjiiijjihjhhhhiiihhkhhegfedffddefdegfdhigkllmnhL?D;K@A@:@EFHIKKKLLLLMKJLIGACI23FA:31BNKKJIHJJIHHIGEBCGMONNNDINONV[LRg]SaH\UYYZ_bhklnonlmnlilszť]Vdlt~~~~|zyzz}{{{zxwwxwutqqqrrqqnnlkllmlllmnmoonnmmmmmnnooopooooppruuusqqponommkjihhgggffffffggfffhhgfeedcccbn{w{|z~}wd_|jGLJFHGEDBA@93*4.##""!",ewnoppqpmlnplkonljkmliilmjhikkjhhjkjjijjjiiijhjiiiiiighhheegfeffffhhhiiihmnmmlo`;?:KB@@>:AEFHHKKLLKKLKKJM7G=,@FF:;.:MJKHIHHIIGIIGFDCEKMLKEJNOOPQQMVYOTKWSWWZ]`gllnmlmnljkkltyXP\cs}|{||||||}}}{{|{{zz{zwuqrsrrrqnlmmooopooooqqrpoppqppppppqqoooqrqsuuusppqoonlkiggffgffefffgffffggghgfeeccccbglyqtWKIIIIFACB@>63(6." #Tuoopppqnknpnklnlkjlljgjlkihijkigiijkjjjjjjjiijjjhjhgkjhhihhfhhfghhgjkilonnogq:B7KB@?@=;BDEGHJIHJJJLKJP8,FDH;+.17JL4>NHJGGIJIJIHHGECEEHJICFMNNOQPMOWPRSURWXZ]_bglnnnmnmlllkhlzpYW]bn~|{yy{|}~}~}{z{|}}}|zxutsttturpppprsrrrrsstuuuuuutsssrsrqqqqrsssrstsrqoonlkjihgfffgfgffefffggfghfffeecbcccbzz|{pHIHKKFGEDD?<51'20%# ?otmpppqpmmpnklnokjjlmhhjkkhhiikkgigikjkjjjjjijijihlkjhijifiiihiiiilljnnnnoh>A8JC@@?><=BDFHHHJJIJJJKKN/;KKKE:3/"(1DMFFGHHIIHGGHGFFEDFF@DMONNPOMNNONJUSXZ[]^adfjlnmmnljjijihrz{VTUJMhLKr~}|||}||||{zz|}~~|ywvvvxwwwvuuuuwxvvuwwxyyzz|{zyyvuuttrrrstssrqrrqoonnmiiihggffggggfdeeeeffffffdddcb`bccb}rwyq{{|SLLKJEBEFE@@>83$30(&!-`wqppppqnkmpnklnnjikmljhjjljiiikkiigijkjjkjjkkjikkljikkhhkkhijjhllkkooooluKC6GE@@@??<>BCDGGHIIHGJJGIK,%%%=<@BCCFGGHHHGHKHIM>2/*8K>.;?+0FHIIIJHFFFFFFFFEEBACFGHIIIIHFGGHLRRVWZ_^`dfgghghihiiifkrq}cjzgKAB?=XS*?}~}{xwx{|}}{||{{|||||||~~}zyyxyvwwvvvtrssqpnmlkkjiiiggfeeecbcdcdeeeeedcdcaa_^^]accax~z||z}s}rJMJGFIGHGB<=<8+"42##!$$%$%$&$6etpopoopljnonkikmkjiklkihiklkihgjlkjihhhijlmnmlkmnnmmmkkmmmjmmllppnpomo99AI?A@@>>=;;J?A@???>==AABDDDEFFFHHHIHL-&+ 9PJLIFGHHFEFFGEEGGEDDCCDDEDCDDCEFDFDHMLUVX[^_bcfjjjiihhheiihhaSMO\N59:\_@IOlE}|~~||zz{||{yzxvuttsqpponlkjjhggfedccbbaaa_aacccb``^^__^^\\\acca}~||r~zuaLMPLEEIGDFB@@65/(72&%"$%'())*'$9aspooponnkjnnmiilmliikkmkghillljhggijjjikiknmnmjlmlkllmkmnomppopqkF>7K@@@@???=:=ABBCDCBDEEFGHHIK3&('57FGFJKGEGFGFDEFEEDCCCCDEDFEFEDFEEEGNGNSVY\]`ddfhjkijgddgfgh]MLROJA?FNKzˣWXsKw~|||{|~}|{yywvvttrqppnmkjihfeedcdbbcaa`_`_aabb``^^^^]][[[ZacdauytzzuvyqGOOFDIJIEE@9<61/'81 $  #$$'('(((*%+KktponnonnjjnolkimmmjijkmkjhhkkkkkkijkkkmnnmklnnnlmnmlknomoqqpqpkQ:5IB??@??>><;?A@CCCCDBCDFGFGGLK=,20HHFFFGFEDDFFFDCDDCCDDDEEEEEDECEEMFGMRWX\^`bbegjljfdeffheUHLPLB<>LW\Ycyv{}}|}}{{yyxwuvrpponljihhffedddccccbaa``_``a``_]][[Z[ZZZZaccb|p}v{|jrvwmiNHNJDDEGGBA?=;:60'92&$$"$%&'()*)**(%6Ypsnnoooomjknnlijlonkikkmmjigijkmmlmmmlllklonnlmomnlnpnnrrqpsih84FD?@@@?>>=<;>@@BABBBCCCEDFGFG@118&+EBCFHGEEDDCAABCBCDCDEEEEDEEEDELKDHKQVY\\^abehigeeeffi`NHKKG=?HV``[Z[n|{}{yzz{|}~|||{zzyyxvvurpnmlkjhfgffeddcaba```^______^^]\ZZZYYYYYZbcc`w{kovLMIEGFFHDB?B><<61*70#%'" !#&&&()))***()?arqoonooonjknmljiklnlhjiklmlkiijhjjikllnnonkmnnplmppnqqqqqrj:5AI?@@???>><:?AABBBACCDEFFHK&06DIGC>=BDBCCCCCBBCCCEEEFDEFEDDHURDHKPUWX[]`cegeeihffeYJHHD>@??@??==<:==<;9;>?>?@@@BBBDDEDDEFD'$#7MMGDEDDDCCCCDDCEEEEEFBDLNST^JDIMQVXYZ[`cdghgj_MEGD=9?CJPMHJJR[`Zf{xvwsiiigbbemswts~vqrsvuutsrtttvuvvz{||{ywvvxwvtssqomkjiiigffdca`_^]]]]]]]]^]]^^^\[Z[ZZZYWVWWWWYZaccbw{}zxzu~xyyVGFFIIIIGFBA:<<961'36,+'$')+,-..-*(&&'())(*+,-+(6Tlqpnnnooonjjkmmnkjimnnnnlkkkjjjlklnooomlnppomqqrrrrra`44EE?@???>>=<;;::>>>?@AAADCDCCDCDDEG-!8<8GDBCCDCCCDCCEFDCEF@To]HTVZQBGLPRVWZ[_bdghgWGBD>;:?GKNLILIFEIW^Xsqqijcaceabedbbdhjgkuzqmmpqqpqoqpoqtwxwvuutrttrrrpolhgfffffededb_^]]]]^_^_^^^^^]^^^\\[ZZZYXWVWVWYZbcca}qwz}ouyuSGHKIIHFHF?A@><833*54-+'$&'*))*,-,.---)***)*)*,,*);VmqonnnnnnnkikmnnllkljlnonpnmonooppmkmoqqpmprqrrrsqI{|10:K?A@??>?==<;;9:==>>>ABBCCCDBCDEEC)'##-+)GHGEBBCCCCDEDDGEC]lEPTYUBEJMOTVV[_abibOEEA;9=EJMMKILKJKHABPXklvdnsrc\ZUTTV]]ZUURXdfefmxukefilkkjjnqsssqrrppopppomkhfeefffdcdeda___``bbba``abbabba`^][ZZYXXWVVWYYYaccbo{z~zuw~nyLIIHJKEFGCBC@>:=4/%351/%##&+.,+,+,+*+-+,-.+,,+**))&):Ujrponnnnoomjjknonnlkjlkmmmmllllllopppomqrrrrrqr[Ae@3)JA@@???>>=<;;;:9<===?AAACCABCCCACGD!%&%)(:AGFEEEEECFHDD]PGOT\E>EKMPRVWZ_bZIDC<:;BHLMMKJLJJJFIGA@IVzvunT}ue]_\UW]]ZYYXUVYagkf\^iv}~o_ahfgilmnmnooomlmnpomjggfeeefedbcecbaacefffdcddfegffecb`_][ZZYYYXXYYXYbccavvzy~xcLLEEGCFHFCAA==:=2*%,83.&%(&').//--+*++)''*-.+*,.,-,-**8QgqqnnnnnoonklkmmnnpoonmllknooopqpomnqqqqqrqrbHDOJ:,?G?@???>>=<<;<;:9;=>=?AAA@ABA?@@AC@C:).$* ,2D?CB;EIBH`@EL\M9BHKNPQTZXNDA?:9=DLMLLJKMJIKHHIJIC?FNj~x~YVl_a`^_ZUT\c`]YVU\cggc^]]dp~~e\dfgijjikllkkjkmomjfefgffeddbbdcabccefhhgfffhiggffdcba`_\[ZZ[[ZYZYZ]ccd_~wv}tJKLIHHJFDB@@@><:2+%+50/'%**,,*+/00/-+,--+'(*-././../0/,+5J`mrpnnnmnopomlkjlmnppoppqqpommmnprsrqrqqshJHFO]232K???@?>>=<;;;;:98:==>?@A@@>?@@???@ACE=<502 %0&#+GJ?QsACDUY8><;;;<;:979<==>>?>>>@@>>??A@@CB@C<9)&#"-DJ@a{@>@CXD3DJLKIIJKKIKJHJIIJIHGFFAAFScqytmXyjoW]dcmXTURMPTQQX]agf^WZ\[ZX`jjg_hw]cddeefggfgkmmihhffhhgedb`_`__aadddeefggggfecddeedca```^]^^[Z[]_cgdcca{posujAIKEDGFIFAAB@=9740)*2,,'&*,+-./010.***+,,,*++)'&*,,-..-,,01/09K]kpqpnmnnooopnoonmmnnopqqrqqpopopoaIEGFHD::./K?????>=<<<;<;::979;;<=>>>?>>>>?>??@@ABBDGHCBBIIEwU6=>DO6:@CA@@=649?EJIIJJJKJJLJHHIHHHGHGHGGB@@HXbXUa~xljbZ[YnhfefZRPRUTW]^^\XSZaafiggcYZipl`ccbabeggjllkiiiihgfdc``^]^^__`aadddefgfedcccbbbbba___^\\\[]^abehdbca|gsysnq||w|mrhIMHFHIIE>BD@9::83,')//,%&#%*/1110111/,*+,-.,-.,,,+**))*,+,,.0004?NblqrppnnponppoppqrpqpqrqrqprslXGFFGII@H56'?>=>?<=;;:::989::;;=>>>=>>?>>>>?@@BBBAABEC@an7875?84>>A>836;>CIHFHKJJLLLJIJIIIGGHHIHGGFB?>CQX^yqarl\Mnyllg``][ZTPPPR\abb__ciinkdNFLYuo^bbcdgijmmjjjjiigfec`_]\\]]^_``abddeeedcabba`_^____^\\\]^_bcdfefdcca}nsr{mj|xcYfrxoJGFEDHIBCEA=;;=84/&(120+%%$$&+0233210110/.---..-------//000.,)-1226AR`ioqqponooonpopqppqqqqrsn`L=>CIHGH@zZ58/)FB@??>>>=<<<;;::9989::9::;>==?>?>>>>>?@AAA@@@>;\|A89518-598448BJV^WGSvbVXtspkjif\WQMNNRTZagc`acgkecSC>FJQ_adgjkmmnkjkjigfedcb^]\[\[\]_`abcdccbbba``a`^]]]]]]][Z]_`bedcbcddcd`upyzx{zKEFGFJHDDD@;=?:860)(42/.)'$$$&,.0144422210/...--------///.//0,)),14436>IVainpqqsrqqqpqrstrpiXJ@AFA9=CJL@gs5:3*0I?A@?>>=<<;;;:::997899899:;<=>>>=?>>>??@@??:7:DR484,82*0169<>BBBDDHLLLMOLHFGJKIIIGGHGGIGIHDB@=?GPUHG[YRdnheekmf_\UPROPQMMR]efehhgd^aL@B59fphiinmljhiigeddcb`^[ZZ[[[]\]_abcc`_`__^_a`__^^\\Z[\^^`abbba^^`bcccaxzrquvqz|{}NCGKBAAB?BB>;;=94-'%/0-+'$##%#%*-,..2654211010./-,--.-.0-*-..0/,+)+3774226>IPZ`fhjlkkkjfaWLD==?ABGHC<=GIP::41&;G?@??>>=<<;;;::99887899989::<==>>>?>>>=<:526;;_|{c686+19,,778<<>==<<:;:::99997789999:::;==<=<;98522568@@{=76..:3+.57:;=?>?BDILMJIFBBB@=31238DHGGGEEEEEEB??BDBJLPLKJMNNNSW\cfc^XTQOMNMTZ`bdgbLNilaWxd_^\[^addb_\ZZZ[]^_`]]]]^^]]^__ab```_^\[Z]aefeba`][YZ\^`ccc`{xk[i~}|~ux|qHCFGDEHFGDA?<=<92-$+4-10-'#$&(&&*..//.,-56553122000/..-./0.---.,.11/-.37898::9;967;==<:;<<=FHH=d]7<40*+FA??>>>==<;;::::9898765778::98:9988754456778@=a|R7:5*962)0579<>?@AACGIIHFD80>+(85.10>IHGEFEDECCCA@A@FGGIJLMMNNNLLOQOPRPLMQRNFCGGKK@?Dlusht|zug]]^`a_\[ZZ[^_aa_^_^^^^__abba_^]\ZZ[]`bcb`]\[ZYZZ\^`cccazww{yz{E@KHADCABD@??:782-#*3-+.2/($%(+,,.0/-0/.-36675321100/0..--.00111000121/0257799:;;989=@?=<>>@>>=?CEGFA=?EDSl6<61.'.F@??>>===<;<:::9:98887654578776555545667788=BAyT654+694/*157;@@ABBBAABCEFFDDB:<83.-%2F???>>==<<;;;::9:9988876555555655566777789>>?@?BGHJF-/86-!",6'#AEEEEDEHGGHECKC=BFHIJLMMMNNPOONMLLJA<>@?:=GLLKJJknkr~xtma\[[[^_____``caa_][ZZZ[^`bb_^\[[ZYYYXXXZ[^accda}~{}}x|tjwcAGFDEFCD<@??C>982*#-1+-'$(,.23/-.04554445106754443332111101000////.1222244320-/017;=>=:6658:99=ACDEDDCDAD>sH;;61-+$4E@?>?>=>=<;;:::::::988776766677667777889:<@@BQ9<6/;742+*257;<<=>>??CFFB4(!$9,&(&9BBDDEFGFGGFBPWD=@DGIJKMMNNNNMMMKHC<=?=99>CHMNJHQnfbeuqkh][[]^`bb`_^]\ZZ[]^`bb`^\ZYYYXYYZYZ[]^bcccbrqr|yrxyzmncHGFFGDADBEECA<875- &-/.$ #$'+04642367987552135664455451/012210010110112233466534/368;>=<;;:;=>===;84/-)#4D@@>??=<<;;<;::9::99988787777777777889:;;@D=ce9:/&6:641++2669<====>@GJJJI4#,,!2?BCDCBDEFFGJHERUSN>@CGHIJLMNNMLNNHC?=??;9<<882+!(4/.)!" "#).561/.279887630024676.,*(()*+/0003212211201234552456679:;<=>??ABBBBBCAABCCBHn6@=82-,)#2C@??>><=;<<<;;;;::999888778788887888:;;;>DDGj5.0).;7530++2469:;<==>?CFDD?+%-0?FFHCBGIHJIERTOPSD=BEGHIKLMMNNJB>>?>;;;60.*)#/BA??>>>==<<;;;;;::99998888888899:9::;;:=BG>`N9;7,987520++1347::<====?BCDFH3 *;944BIHKEBpjLPNSK=@CFGHJMONIC?>>=<<=ADDCCBCBB=>=<>>>>?@ADFJBqF=@>851,))#,@B??>>>=<<<<;;;;:::9999899999:99:;;;;;=ACBIb9:7.3;8652.+,024699<==>=?ABACE8! 1=IKKDOwNPOPM>?BCEHJJHB>>?=:<@BEEDDDCDCBAB?:AMITaR^bOCU\^cdfimt~}~~wslf_[XVVVV[^^]^_agjjdbcce_htyqn|~}l||pklvnklkVFHGADFDBAA@>>=70-"27*,$ "!#$%).11234783579964689997765676654544442000/.--.0123566557758;<<=<>:W~NIQQRC>ACFFC@>>==;=@CEEEDDDDCCCCB@A?8@JTQGNTVDMZ\]]^^][^cis~|{x|~vuokjfca^]^afildbc`ztmbLADE>AHCCFBA@=<740,2+-$##"$$&(,1132232/3654656677998964677722476667666544465563342368879:974;==??ACDEFFFFCDf9A?=;82.,)'$%6AA??=<=<<<<;;:;;;;::;;99::9:;;;;<<;<@@@=]H:=:+5:866420)).12379:;<<<<>@ACCB7'Eog[ENNTI=@A@><<<<;=ADEDDCDDECCCCCCBABB@:?JIJJKWY_ic^\]WWWVXWZ`jv~{vx}~}~||ytolljimdbc`wp`ck|MBFD?CGBAC=>@;884. +5--+(&%$$&*/3444455679:;9666665533/.,0332/.064434676776368989<;<<>>>>>??AAAABAAA?=>ACCHC~:???<;60/+)'$"/?A?>>==<<<<<;<;;<;:;;:;;9;:;<;;<<<<@ACAGW;=<0+:8766420+(.00359::::;==@@A@BC:947^yurCIKPM<<<::;:;>BDEDCCCCDDDDCCCBABBC@@@:?HJFT[[SSVVWXZZYZ[[afa]ky~~{|~~~}~}~~j`c_}qrlp\lrbgjk}JCIDDG@??@<<:83251',5-/+((&#&&%'*05677676567:;<;:9876667777777666766543432412565688:7697899:<=;>=?ABDDCA@@C=hF?A@?<:61/+('&");B?>>=<==;<<<<;;;;;;;;;;;;<<<:;<<;?ABG>yr=>=8&6:8864421+),013469::<<<=>?ABCEGXssHBKMPE56979<@BBDDDEECCCBCDCCCBBCAABAA?;?EFGRSLJUX_bd`\__UWZSTZ]Zt{|~}{{{}|~}|zyyyzxwk`ccft|ptvtsyuxu_DEDDEGB@D@>>887663$+5140)()($%%&$')+/3698898766888:::87754568889787665767778999:9:8678;:=?@@?>?@@ABAAABDEEGBSW;A>?>=:60-*((&##3@A?>=>=<<<<=<<;<;;;;;;<<<<<<<<=<>CBD9lG:>;,+;88765310-(*001357:;;;<=>>ACE:Z}~U9EMNN<169:>@@ABEEDDDBBBBBCDDDCBAABB@@>=@C@OJGGB5@QW\`_Z[WRVPKRGDo~~|{}}~}z{|{z{vrwxzgacbvwzmr~|[IECHEDAED:89;>7$+4/2-()*++,*****+,.1567889886532477789767534554445466565566534789;=;;<<=;58>>?;>CCEj;C@>?=<;61-*(&&$!+;@?>><===<<<<;<;<<;<;;<<<<<=<=<>?@FAcywY9<;4#6:99754320-').0023679:;<=?A><6?|{f9??@@@A@B@BD:v>==;62.,*'&%!$4?A???==<=<<<<<<<<<<<<<<<<<<===ACE@Lwk>>;:')<8:88653100*'+/1213579:=<856:6d{x@8>DMJ<5899;>?AACDDDCBA>?15615>DAA@@?@A?AIOYXQOQE2.5@IPPVZYRJA775;`}|}zyx{{yvvvuw}}}jacbovpv|xral}uqhIED@CFGF@B>@>;;;8AB3#'4/-&!&)'&(*-/13456666655665677686669:97877433145765342498:8779;<<<==>?@@@AA@ABCDEDEEEFGG@kF?>@B@><<:74/,*'&&# )9A@??==<<==<<=<;=<=<<==<<==<=@@CBK|}sG>@<3 3<:997653100-((,133346742146:7J|~P59=DMB86998<>?@ACCCCD0-=41.14,9BA@@@A?B>*,38=A:)!%5@?:BS[ZUKED?637X|~zxz{zxxzwwxz}iac`}vhe{i`xv~mLIEDDGECAB@@>9<;7@B- 28.-*%""%'*,--/2443232334544576667866549;9:989:::89:9889:98977577778:<;:9::;>>=????@@>?AA=WS=C@@@?@><9830-+'&&%"#/======<;<=<<<=>======>ADEBA~Z<><:'"<::998654210/,(*-0243/-1447:;@@ACCCD613-)+,2*3BAAB@>@;$"&&%%$##(8A?88AGIRXNA:2,*'-Eh}|{{}}z{{|~jacby{~pv~qXCCCDDEFCAA?>>===ACDDEAFe:CA?@A>A@<:843/+)'&&$ %2>A?=>>===<>=<====<>=====>A@?A>xh@?<91,>::99665433210/-+,../14467:<7[}}wu?644;D?;88;78<=>?@AACC;/#2238;DCA?>?K_R<*##$""#*64,5@JJGKPK4&"!#%$#:G~{{yxzzyzk`cbn~umow{cADFHFDDC=BB>C@>>87A;).6+)(&'$ !"#"%+0677799;:;:::9876369887:852122689;<;94676887888899:;9:;;<<=>@A@?>@BBBCBDEFG@|w{;BAAA@>>?A?:874/,)('&%#!'5>@?====<=====><<<===>?>@AAAC?2@H@=?FMU\\adYB0*..)++*,+:@:GJ?6($" #$'.u~~yxyz{~j`ca}x~wxyvzsw~}[HMFCHDBBBFA?C<:;7:;2(49-+'&)(()(((&')-330++/123669;==;:98969844898:;>:298779;;<;;::;;<=;:;;;;;<;9:67:;<;<76/.+((&&$"!)4=@?>>==>===>=<>====@><5:<;::986665443333334556789;<8i}~xc861/29=<97:<98<=>>?@A@BD<+%-5KWXMD1(/;Qa`WSJB@C>301+:E94/**% 'q}|~}}}}jacazjymKIF?EFADDDB@A?;:<<743*66+1-),*+//13465420/((/33344345558;;;;996387347744;=8188:::9889;<<=>>>=;;;;;<?>>??@ADD?TN:D@A@A@?@>?@=<86/0,))'&&%!!&2<@@>=>>>>>=>=>?>=>A=@BEALg>@=<9-'=<;;:9976665545556678788::=9M||}|yI53..17=;:7:;98<===?AA@AD=0 IfaUE;84&!(4DTYWXYUND4/@IA92(%,) 'Z|}~k`c`vqs|ympyz{lv`EDCFDBCHED?BC>@<@=168)02+($&&%')+,-/131.-1224678887776420.1589877:7468798:917997653/0677::;;:9;;<;<=;<>?@BCCABBB?D~a8@DB?AA??@>>A=;;733-+))'&%"!!$.8>@???>=====>>?@>?CCDDCzH@@><4#->;;<<887777666677778789;;==;97:;88;=>>?AABA?BMYTa^ZVLGC9% #0CQTVNFJOTQH?2*# #$  "!#'6y~l`cavsqwicjdm{zjjvOAAHF@EGE?>ADB:=>97574(,4&$!#"$&),./00134687887878999:9:91-**.02458:;:7794335:<<==>=<;;89:::;:9;<<==;:>>?=AC?@??@>=>@?<;:653/+*)(%%%$!#(/8=?A@@>>>?@@;:>?@BF?pU=A@A<02><=<:9:899877888888889;;=:97:=:7;=>>>@@BMZnpHSWJ>4,*)%###!$/?JGPYYKBB/#!"$!"#$$'(Zk`caw|ytyx~}lpzYAEGHFFED?AHHA:;@>8:534)+3)**(&#$%)../2679:;:;:;<::9999:::;<:731.,-0/26766877;:8457579:;:=>==>=;89999:;;?D@??@@>>>?><:;9762/-))'&&&%$#$',26:==>=:58>>?BDE@bk?A@@<4(3>=<;;;::99989::9:::9:;;<>;G|z~e66,**)6?<:988<<:9;==>=OipttL=C8:1"%&$$$"!)>LLA>A?/!!&%%$$$'%;j`ca{~|w|{yxlnx[FJFEGDBBCMI@=:>B@98255(/6,+('%%&(*,-./1232468768;;985468:<<6;>;::;;98:;<=<::;;88;;:<;:98999799=>?@?@><<;=<:<:;?ACEAPzxpxR:BD@?AAA?@@A?>??<<<:99520.+*(('''&%$#%()--028;>?@AAD@UyFACA@?7)4>?<;;::::9:9:;::;;:;;;==>;:8869;:::9<;Aevuy^/'"0<7( "####%')+,//-)!!'&'$!$&$(ak`cbvr~rsy~xyvnsMEDBDFDBCGF>:<>AB=98:70'37)+%$'(*,/02466432233341220,**./026568642471/4897899984456899:;?@A@?A@??>>;<:9876521/-****)**++++.39;<=?=?ACCIT>CAB@;0"3?>=<;;;;:;;;;;;<<::<<=>@9Z}|uY85.)&&0?=;:997899:;:<:Svxyi3$14) ""#$##""'(#  "%&(%"#&%#B~~~~l`cayxr{po\@BCFGGGDBCCDB@CA@>97875&04)'$#$)))-.012342234715::9864420.+153334/.685359;:;:9884119;;:857889;=>>=:8557835<>@?ABCDDC9Zv:A@@@A@==?@@@A@@>>>=<;;:9878533200/-../1237:==<=>@ABC@i@CBA@@:/ 1>?=<<<<<;;<<<<<;<<<<>=>=:766;7(+3*'##$#$'+.121./10/2500666786689:889;;;=;<;;<;7789:;;<<;:;<;=>>=<<<<<;:;;<>??=863:=;:767=??>?@??@?><=?=;;::::78766644456798<=<=><>?BC?lyGCDBC@=:, ,=@===<=;<<=<<<<=<<;==>?=v|~|}c86,&'$)=:8:886++4*(&)&%(*,010133454369887765512359;;=<9::996247547998875559>?>?>??@?@@<79<;89=@@>>@A?>?@?????@>><=?><<;<<;;;9:;:79:;;;;<=??=;?@BD=[T?DDCB?>8*&:@>>>><==>=>===<=>==>?:d}|G72((%"0@=;;::::;;:;<;;_rp`6)&+$"""#""! "&&$$%%&(#?~k`cazsp|{tWEGCFIFADDHDAAAAA?=;;@;53&05()&#&&#%(+.0234236446579;<<<<9875677500566547::;;:;:;:99669:;;9::;<99;9329@;89:<99<;:>ACB>>:w|hhv|}{{{}f9A@AA@==>?@AA???>>@?<>>?>=<<<<;;;<<:9;<<><=?==?BA@Rc@CCCCB@>9,  3?@>>==>==>>=====>>=@:[~d98.'$$#5@=;;;<;;;;:;<8Hpul>-%&""$# ! !&&$%%&&($/}l`cbrv|urqm}uHEFEGFGDCEF???BC@><;;>842(/6(-+$'""$%'+.1233455676569:<<====99899:9:<;;;<<;;:;<===>=<;;;:97687541574117>==;98:<=<::<>AF;W}{tzq;B@@@B@??@?>A@@@?=@???==??=<><=<=><>97?===?:=>@CAE|vFBDDCBA@>8.#*:@@????=>>>>>??>??@9YxF:5*&$"&:?<;<<<<;;;;;:9`wsO-$ #"!!%%#%&&&''&m{x~||l`cavvwz}ur~cEGEFFEHG@GF=>@BB@>;>?:643++5++&$&"%&(+**./1013456687554699789:;:;;::<=>=::;;;;<<=>=<>>=<;<=;>?>=>==;;;8977:<;===?>@@>=@@>@>>>>>=<===<>;8:A?@>>=><@==BBCCx~Q?EDCCCA@=90% 0=B@?@?>?>>=>=>???:\_;;2(&$!*=>;<<<<;;;;;;7Hrwd1%  %$$$&&&&(%e{z|}l`d_qmxv~SGIIJJFC?>EEA@CC@<;:@:7543-.7,(% !$&*+,.//00213445567853465433358:::;;56435776678:=?;89:>?=;::=?>;89:;=<<<=<>==9:<<<=?@C@??@?==@><=?>=><<>>>>=<>>AAF;ec?DCDDCBBB>:2(!#3>A@>>??>>>????@9i~zG:6.'%#!-??<;<=<<;;;;;9aytE"##%&%&&((j|~~k`cbw~~toHKIIIHCAACDEB?=@B?>==8:775)+4+,)%!!%(()+/123332223345665678::986676564278796544458:;<:747;<>?=>>>>=;::<;;<<<;?@>=?@@@>=@;g~`A@=>>AA>@?>@B<=A?>?@>?>=>@?@>=@????>=>=?C?BD>9b}~}sECDCDEBCA??;4+$ $4>@A??>>?>>>?@:qwx{~b<=5+&$" /?>===<<<<;;<7Jrvb,"$&&#%'&.y}|}|}~}|k`ccg]]hbPczzy}|ySCFDDB?>CFCCEC==AB@<<:::665**2(*+*(()+,+...1334454434556788789;;::99:::977999::98779;:99::;:;;<<>=:<=;979=<<<;<<;:<<9;?AA:bncoxqrwwr{z;@AAB><@B>>A=B@=?@@??@A?A@=>?@@A@=?@><==>?@@@@B?H|N@EFDECDDC?><6/*%!&1;@@@???>??>;^zz}{~zH><2)%$""1?@====<<:<<99avuK$&%#%)%H~}|~~k`c_rtl`v~uvoSFHD@ABGGEBDEC>ACA>=>;884622")5*-0-**),/00034465656554444448888678999:;:89766899:;:::;:;<<<<=>=;87537:::;9;==>=>?@=;=<>?AC7:EB?=B@>A>A>>A@;?>>;?>ACAA@@??@>@A@?>>>>?>=>@@ABAC_BCEFEDCBDBA?=:4-*%##,5:=>??><=?N}~^=?90)%#"!/??=<=<<<<;;7Mprd*$&$&'-v||k`cbxo}~xbEFGDFIGJHDCDDB@AB>>?<;67763- (5-12+(*)*)*+,.158888777676666889:7688898899:;966;<;<;<=<<<=<<=<<==>=;;88;==>=??A@>;=7.16:<>BC>6:CB9AE>?@==>@?@?=A>>@?@C?>@?>AD?<@=@B=??>>>?;94.)&$! !&*0348;@CDxyvE>=8/(%$"!,<@>><=<<<<:AlvrJ#$'$R~|}}k`ca~vmkrzyz[FGDCGGGFD@@B?ADBBBB>=<79;81, )5+-+('&&())++++,/57:;8679:98:::;;=>=<<<=====>=:5422459;<=>>???>?>=>>===;<<===<@;2520220/01141`tn_?ECB<@@?>>>BBB@f}OBGDCBCDDDCA@@>=;750-*(&'()+/37<=<<<<;=>=9878.- )5&%'''*,+-///...--.148989;<==>=<:6577652121123100.,+,-0242/0158;?@@@??@?@>;;<>??4.8>>??<<>=::\cl}zm8>CDCA9C>A@?@?=?B?A@?>A?A?ABAB@AA?C>>B>>BCF@Uyy^@BECDDCBCEBA@A@><:97420/013379>BCBU~z|{vw|}qD??>91*'%#!%3>?=<<<;<:^unpJ+-  a~~}~{jacbp||svyyqIGIHDDDDDFECDFDBBA@=???::87.-!(2)*-)$)-,-/21442332/--,,,,/2332340**.10.-0111-/2420/-,,--365679889;;>@@BA@:547794+6=<=>?@@BAM}tmlmuo~:2ADAB;=C>D=@?<@D=C@>DC@;@??@A>>@?BBAA@???@ABEC>>@E@8BsoxsnvvFDEDCFDABBDCB??@?===:8978999;>=82+(&%"!,:?>=;<;;c{rof*"#366+!:~}~{j`cblwgZ^eZHHHECEGFFHDDDBACBAB?@>;:764.,)3-+*'(('(,02134457666420/./2330232128;;;:;<;;;9:;:99976878<=><<=<<:77779730121//38=?>=>>?@C>Yncbzwv{yV2;ADAB;@=AB>A>>E=>D;CBB:AA>??A>@@?B@?AA@@>9A@=DC7@wrfqxPDGEBCDDBBBBCB??@A?==<=<<===>@@ABCAk~~~{|vH??>=92-)'&$"&1:>>>=??=?;6754/,(3,*,(('%(*012233356889997687789:::9;:;;:;9:<;<<;;<;;<=====>==<====>@>=<9543389433117>@A@@@B>njh~rz{~p66AC@A:B;B?>==?EB>CE;:ryq{rM>GHHCBECBCACBBAA@A?=;:==?>???BBCDAS~}`?B@>=950-+('%$*18:<8Ozyuqh*+.59:*" ! E~~z~~}j`c`~{|utPHHBDKJEDGCDDBC@@A?D?<>:78852. (4*(*)&%%#'+0357656789:::99::967889;;;<<<<<<<;;::<<<=<<<<>>:>==>?>>??????>>??>=<?@??;6^uoryc[x}r{pB>C=@??@>><@??><<>@@@A@@BDEDHx}~{|xI?BB@=;940-,*++,/269?o~toqR$'!(<783& %Dl}~}yw}~}}k`cawx}ocq|jlwaHE@@IHDCBDBDDEFBBDAD<:>977772/")3*''%## !%*/368976569:;;;=<=<;<;<::<=>?>>===;;;;:<==><>><:<>???@??@??>=?>>>=<>=>?@?>>>?@ADtxxefokvBAA;8B=?@CCEB;AUw~yM9AHFED>BBBCA@A?A??@@??@>@@?@BCACDAg~|{y~b?CA@?==:8620/0347:=:[|upmj/,"-4?DGBBO\v{}}|z|}}}~k`cautxxvs}|ZFECHLGFCAFEDEC@@DC>@==>:7356-/")3'$#""!  !#%)-/1.++-.-033333431.-145445788::8:<<;:;<<<<>@>=?????@@??@?=???>>>=???????>?@=Jj_qR;C@8=D?8B?B@B:;A>><=>=@@@@ACBBCE=A>=DCDEE=6BGY@=@FGE@AFABDB@AA@@@@@@A@>AA@ABBCEAU~|||{|~}}zuJBA@A@>>=;866678:<>=>?>>???>>@?>?>==?>?>?>?=>>>@??>@=Soaq{o?BB?5>B?;@@C;?B;=D=5>C<:=?@>@><;=>?@@@?A@;A@>?@AACCDDCGw~{}||}y}~_@DB@A@?<::9989;;=?==e}vsoh+ (*!@p~~~~|z|z{k`caz{rmv~|`J[`OHEBECAEFCBCDB?@FC>>?=;;86:8414$&4,((&%#$$%'*,--/0101033433546678:<>=<=<=>?>=;:8632232135878:>>???>>?@A@>>?>@@?@>>><=???>?@<_fzx}ppE><58AAC@<<>@<@?<=BA>>?@=;<>@?<>;??C?7:<=9A>?DEB7D@?Arwc|~whk`qq`K>EB?BEFACEB@@BA@ABAA??@AA?@@@AADDAh|{||}x}zGBD>?B@?;;;;889<>??;MyxvuqO-$!:w}}}}yy{z~}k`cbs|yltlLLHDCCDGHFCCDDA@AFD>>@AA?;::;2104(%4+'&)'&(*,-//001112334456556789:95432232369<<<=<<:6336666530/36:==<<=?@@??<88=??>>>>????@A6?=??9A@<5;<==;??<;@@;>AB@?@>ABD@?F==;::<<=?;=:772423'&2)**'%#"#&)/4556688777898::99<873-+06777753345468::;:<<<<;;;996466665689:;;8324:=??>?@@ABB?rjl|tnj59CA@@>?9A@8B==@9=C;2;<:=:B>;>==><=>>:W{xposN &!"!O~}|}zz{xwz{}jaccjeez}}l^lwZHHFFEEACFED@?CBDDCAA?<;;;:66815(&4,*))'#$$&'+,-//.//-/14666456314667689;:<>=;;97444578:;<8/5898:<=>??>>=:887551.04689;;:=ACD~~}}v|w~A9=A?AA8:BA;>=:>>:>C87>BC@=?B>5>@?>>>??CA?=;=;=?>?A?;Es|vorj- ! !#!,q}~{yy|xx{xz}~kacbp~}|~QHJEDEDBFDA?@CBCHC??<<=<;;7446-1((5*(%%%"$#!#(+01000111355454555888;;>=:7446668;==<;998876638<<<:8:;;<;;77:<<>>=<==;976334469@CA@=:>B>7B<;<>7>C?<:=@A@@A;=D9@A=@?>AEDDD<7D>E?=P{w|xR?DDEDCCBBAA@>?@AABA@ABB@@@>?AABBB>Q||z|~yxz}}||~~rEACABAAAA?=;<;>@ABA=`}utpqP!"#"% O~~{zy|yy}zx|{|j`ca~}{yz~hHFGDEFEFD?ACDCCED=:Et}{uoife`]owsw{~}rp{wu{V9D:>CB@@;>A9?>6@<@:;BA?<9A@?@DBq}yzz||gCCEGFFDA@AACABAAAA@ABBBA>>A>?BBBBCDq}y}||w{~}}Z?BACAAAA@>>=<=?>@B<>96655.3+&2-,,'## "#%+-/121/./21112224567446349;<<>><<;:77655467777899989889:9:78;;<<==;:::::<===@=Txq}xi}n9?@?<=@AA89A9?;<=:@85;@DB<;;:ADD@??@@>@?===?C@>G??F:{nvn}yxxuxznGBCDEDB@BAACA@CCCCAAA@ACA@CCDCBB@C>`{x{~z{z}}|}~~tEABCA@@AA@@>>>??@A@Aj~uqnmO" !!""$##%!K~~}~~{yz~}z}yz}j`da|{qHJIGEBCEDCBCCB?@CA::?@?<::96342/+%3..,&$$#####&&(+-/035767875666777:;<<<8556544887645798899888:::;:;<;:::<===<;;:989<==?=;=8Q{{}zzr|qo{@=>?A<4>B<5@>8C<<=;>95:?ADD?;?>ADCA???>??@ABABE@9DB@amq}U@EB?ACCDCACB@BBABABAAA@C@?BB@@A@CBM~zxzyz~{{wvz}Z=ABBABAA@@@????@@A>Xwwvqtj4!#"#""#$%%"-r~}}}}w{|}|y{zu{j`cbx~|rVBGGECDEFEEEDCABE@?=@BA?:::456410)&5/0.*&$%&%)*+-/./00/1222222235666686444689;<==<<:999:9;;989;;;<<:;;<<=<;;:99::::;:988<>?C?h~~zuzyyN:@?=@>:=D@7A>9A?:9;>=;;;>ABCCBA@@@ABB@?A??ABB=>EFD>^|xvyyk~dCDCABDBACCBCECBBBA@@A@@AAA?A@>?AADCfvutvz{z{}{wxz}|rE@B@@ABAA@?>??@@AA?Di}|vrtT !$$##$$%%%!K|}{zzx||wyzy{|jacb{|z}qis{yrkM@EDBACEDFDA?BAC?@C@?AC?<;87892-0'$4.+,*)&&()*-/0012334424435667888:9979;;<=><9:<<:9;;;;9868899;<;<=<<====>>?>>>=::;<=>==;<<:dxpy}|ytuunfbgwpnrq[=>=@;;A@9>>9=@=:8<@@A@@@>>?@AA?>@@A@@BBAACFFFCD@Ov{x}{uKBEFD@BBBCCBAAAAA@AAABA@@AAAB@>>>A?V~w|~vxzyz{y}z|[>BAA@@AA@?>>=??@AA@exxvrk=!%$%$%&%&#-t|}~|}}}zuz|~~}xyziacayyy~{qlgi~xJCHDCHIEEHE@ADBABBA>@C??<78686501*"4-,-+,&#&"#&)+.002356676777446787545899<=:89<=::;;<<=99;::88:===<<=<=>=>>=>=>=<<<<<>?@x||~}}xqgenztx~b:AAA?==>>9=?9:=<7<@?;;=<;>AA@@A@@@@@A@A@@@A@BAABCCBGxor|UAEDC@CECDECBBAAAAA?@@ABB@??>?>@@?=Gnutx{xw|y{}{}sF@CA@AAA@@@>==?@@@>Nz{xwtr]-!&%%%%&%&!N{~}|z{xx{z{|zx}yt{k`c`}uo~wyuIEFCCFCBHIED@@@DB@BA@=?=:86:85601,#3*)++(&&#""#%*.2322234423443467888889:<;;:;>>;:;;<;:;;;==:78::<;==><;=>>????>>><;><<<;=?=Cy}~zx|zvv|zx~}stqA:;?B?>>===;>>8;=::;@@?>AB@<>?@@@@@@A@?A@@@A@?@BBE>D~{u~{{sjx`BBDFFABDBDDAA@AABBBABA@@A@AA@A??@A=b~xov~{wzzww{z\>CAABA@A@@>=>?>@A@@e{wxwrpK)  &&&%'''#2{||||~wz{{{}zvvwuvz}}k`ca~}{ut~mEDGECFCBIFB?>@FGBB>>?>?;;86854320+$2*+-)%""$#""$'*,-/1111454445779878889;<<<<;<:8<<>>===<=><56<>=<>>?@??==>=>?@>>>??==>>;;<;Kx~zz{ypvX<=6=B@?@>=?<;<====;;==>>;;<=?>??@AA?@?@@A?@?@BBA>DAqz~n}akyJA@BAABCC?BBABB@@?>BABAAAA@B@@@?>B?Rwu}~xt{xu{y~tD@BB@BBBA@@????AAA>S{zxvuui<)!&'%&(&&$Z~}~}{{{|}{zyyywuzx{{xz}j`cbw|~pqRCEEBDFAAFD@>@CGDA?>@@A>>>:986570//!3++-))&$%%&()*+,-010/1234424554456788::::<<;====<:;==<;;:8<@>::<>?@@@@@>>???>?>>@?>=>@@@;M}~~|z{u|oZ=C@;6@>=<:<==;<<==@>:;=>>=>@?A@?@@>@B?A@@B@E>nvm}f~|uhlRBFEDDCBDBBDBBABCA;?AAAB@@@@AA@@@A@Es~zzuwz}hjwxxw|z}[>BB@@BC@@A>=>??@A@Bn}zwtrtU2-#%(('&(#?~~~}yx~}~}~|z{y{{twvy|jaca|szwmkSCDBA@@BFGGCBDEC@=>@@@@??>;:7466,.."3(-0+(&%%')*+-/121345543553344455778899;<<==><<==>>>====>>=;:<<<=?>>>=@BABBCA@AAA@??@?>?;\{}euzsc6ACB>99?A@?>><==;;<<=<>=;;<=;<=@@?>?A@AB?=ACBBCB@C>donmjZ{rr\AA@ADD==EDCCEDA?AB?@B@?@@@A@@??BAA>euy{tov|y}xz|vx~tF?BAA@A?<=>=<>??@B?XzwxwtvnA20'#(('(%1{~}~}~{z|z{vtzyz}~iacarufEGC@BFGCEEABCC@@CC@;>@=>;963365-.-"3-..,*('),,+.133224545557687888878778:;97<===?>==<;======>>>?@A<9<=<=>=>>?A@@@@@A@@A@AAB=e}x}xu{rF6@BCB><;>;====<;;;<>=<:;=<<<>>===?ABA@A?@AABD@PkotvatIEFCBBABEEB?AABA??B@A@A@@@@@???@AB?Q~xuqxzyyvxz{x{{|X@BBAABA@>=<<>@@ACBJu}wwuuuV660+$%((&+o~~~|yz~||zyxxyyyvvyz|iacbiux~[ADDCGHABH@BECA@CE@<@BB=97766:74/0.!4/-/-))')**+/0122331345667988876789:;;;::??>==>>=>?===>??>>==<;49@AAA@>==<:;<=>??@@>?@AA?pvuxx[<:>>=;;<;<<<;<@>;>>====>?=><<@?=@ADDEBLxs|dbSAFEDEFCADECCB@A?;?ABA@?@?>@@@A@AA@Brxoxtv{zymr~~zuk`A?C@=ABB?@>:<>>ACCAc}zxtvvjA662-(# "&)(8|~|~|||z{}}|xuwwuut{~x{jaca|vtxhNBCDDBBAIJCGFA@CCA?AC?@=8;:8::50.2-"3/-.-+*)((*,.14344224432345556789:899::;<9998;=<=>===>>>><:::;;<@@?@BA<;;=?>>?@@??>=>>@@Buzyxy|Z=B;4;CBBBCA=4:>=>;::;=??===;<=;=>==>>????@?==>@ABAAy~}vg{sdd??<;AEBA@AAABACDC?;=@@@@@A??@A?@BB>^}xwqrxuq{{ps||vxV;BB><@B@@><=<=>BB@LuyuuzyuU79752-(''),3;Eo{}|{}|zz|zzxsvywyzzziaca}}zw{rOCCD@?@GJEADDBDB?>@@>@@;=;8:8431002!3//10.+++-.0//01123123434576557865689;<<;;===>?>>===>>>=<>???=<<<;<:<<;:8:<>?@ABB?@A@>>DCBCB@;<>>?><;9:<=??>>@??>===>>>>;<=@AA><>@>ryspi}awqIB>9@EEEEDDA><;<>>85>?><==>?B?=??B@Mzxostw{vsx~zurwz~rE>ACBABC?@@;<>@ABCA`ssssqunC899751.-/3:BEQ}~~~|x{}y{|zxuvzytwz{}zk`caodytL@EC@CFKHBBDDB@;=::96686//02/-/.,,+,-///0452233312222566767755889;;;<=<;:79;=>>>?@@???AABBBA=<>??@=J|~vxV<@?C?89;<=?@AA===>AA@>?@@?CB>@A@?>>>=<>?><=?ADD@<6Qjcbu~^s~RAF@=FHDE?=<=>?>>>?m~xusqqx{wuwz{vvxyW?A@AAAC@?>;=>@?BEASuuzo]buW;:9:;84469=?@>>>;978<=====<=>>?><;<===;=???>@ABCBBBCCB@@??CE@`}bTe~bBDF@?JFC??@:=@A@@@?@=;?A@@@;@A=Ry{|xttsvvuuy{||xylD@BAAABA@@A>>A??ABGq{uwtpm[>=<<=<989;=<:56<60/0.3-'-+&()+../1211445643322444465578779::9::9:98:96:=;=>=<;;=====<<<;:9;:<=<;:;;<==??@DFFBc|~}}y~v@>?>78?A=<<=>><4>A@@A?><;<=>=>>>?@??>??@@>@?>A>Oyz{vw||xzywyzzywxX?B@?AA@@B?>89>A@B?Y~zuwqrQ9@>???<:;>BEGEf~~|}|zx{}{yxyxvywxjaca{jFDGEEDDECABA?A@BBA@A<>??>=:68;41/.,3-%*)&&$&*+,--11222200/.0001123456536766677677:;>??>?>=<>>@??@AA?;:;:9:9>>?A>;:7:;;:bz}y~~oM:@A?@@?@A@??@??939@AAA@?A=9?><==????=;:=?>?@=;@B?I}~|ykeenoopg_`TTarP@C?8;HCDE?9;???;<=?CDBGpsv|uorh>>AA@A>;;==??<98787230*,2.#()'&'&$$')*+-../013565577777756567:;<<=A??@@?@@83479;98:;<;9:937<;::99;<<;<<>@CCDDA?>f}vtx~}{l::>C??@??@?@@@?A@=::<<;;;78;=<=<:;=?>=<7=EFADD?98=CA?=;;@@?@@AAB@==><@=Zzwwzn`_qzrywwvyyluS:@@@>ABA@@==@=ABC@b{jqyxqoN;?@ABB>;<>CDGD\|~}zzxz|zx{vw~|xx{vv{yytxhacaqtxx\a~pqkJBGICAA@BBCEAADAA@=>>===;9:9651/-/0/,*,-../.****/1012221120/013223333136789999::<==<768<<;<<>>>=;;<;;<=>@@?>??>=;988:;;>BBE}{y~~~}xE>:?C@;:?A>>=<=?@?@?>==<>?A??=9;<>>>;27<;;==>AA@A>s}}z{z{h=;;ACHNADA<<:>C@<;>A@@?@BA?>>A<>?>=FquzwwvqlpywytzwstzjA???>>A@>@A;==?A@>>>>=:63110.120-++----,+))+,+,.--..//0/.--,033555556789;:98987:@?>?===<<=>==>?@?>>?>?????==:9:;Bxwqtwv{NAB:=DB;;@BABA@@?>==><<==@=9:<::8:?@><87;>??>==@D=k|~z{|x{xrqvywqbIBE<=?G@C@>BCDB>?A@BAAA?;;825:@BA?=>o|xoiqutztvuyuyxuuwW;>??>=??B=:<>;@CDBW]YVk{xmK>A@AACA??=@DGEU}~~{|}~}z}zxy{|xwxpu{urux{x}hacay~zxhECEDDCBA@ACCCA??;<>>>@?<>;963442-/21/'())*)**)+..-+,./,+0573,,,0413667777779;<<;<;;<>==>==>@A@?>>@??????????@???@?<==????;Hvxmxu}d5AC;=BA><>>>@AAAA@@?<><8639?@BDD@9:===:8::>??<:=7Kinx~}~{}|yzz|\>=;8;>AA=?B<6:?A@=@>Wvvpegrvb?ABAABC@>@?BCFEj{~~}}}xxxx|{|}|vtyxvy|zyxiacas{yzwkYECCED@@A@BA@A<>><>>>A?;:::8565340/40/).011/,+,.00/01144//12242)-02324344456689:;9:;;==??=758;;;=>>>=>>>>>=>?>>>??@>?>===;3Fnkutz}}n~~}}sB;CB@@>=>>>=<<<<==<<=??><<=>:45;=;<99<<>??<=AABCCCBB=78:=<:?>?@??>98>@>=@@=65=@>>=BlxvulmokK>CBBA@?>?>@?CAR{}|~~}z~}w|{xy~|vzzzuwtrvtruxiacbpy|iYau_GDDGDABB@B?=AAA>?=;@A;=<9<7862582/3//+0/.,*+)+,*))*+*,.,+,.+-0)+..12..0//133578;;;;;;<==9876554;@??>>>>><:<>=>@@??>=>@>>?8P{xouwp~yu|P>@;EA>@AAAA@@@>?@>>:5787::;<;:=>??@@@><;;;<@?@A>Fmkmpstw{}|ytpoy{wwtN=@AA?PI@BB@;<><<@BA@>?=:;<;9;>>;:;8Vliu~xrx}moxVmyuwd?A>><>??=>A?=?;<>9554361.3 ..())*++*+,+++)(**())(+--/11,,0////1/01368668977899934:<<=?>>><=>>>?><<=?=>=>>>>>@@?>?9Tunquqrijy~z}r~tk9E;;EDA>>?@BBB@??>>><>?>?@@AAAAAA>?;6>=::;:;==<:A}w}}ywqv{zxuqmxunU>@@?=GM;;:;::===>?=>?==;<;;8Rsknpwyvzslt~revwvwvK<@>?<>?>?=<<>=>?=>>ABKwz|{{|~zwxy|{vspurrwxxxz{iaca{yu|bD@A@???C@<@CABCBA=??><;;>;5265/73.1+,*-.--,*+*+,-,--,**+,,),015225322--.2458<=<<<<;<<<<:;<=<<=<==<=<=<:76889:>?@A=cvkypkos{yp}yB9E?;DEB??;8:?CCAAB?@@@>>>>@?B?8=<966;<55;?<;=@>>ABBBWDB?=@==<<>==><;==<=?>><<8Huustnrvqpwqo|snqtyg<>;;::;?@?>anmskqnn_=@C@?A@ADA>?>CA`|{z{}}{{z{yv{wrxwtxuwvwruvzhaccleobCBBBBABCA@CCABEB==@=??<:><7473083/3+/-0000/0/-.--,--../1010,/223333343133/02479<=>==<;<;:96556678788652112467688=?@?=<;857`w{|wtro}{n|tZ;AB@>@DDB@<99<<:<@AAAB@@@A@?97@AAA7irw{zpxx|wxzy|qLA=MN?=><=A?<>>=?>>>?@?@@?>>>>ctlrrrsvtwsotlx|ulsxM:?>=>>>?=:98=ABAA>?BA?@C@?A?@Iu|uy{zz{{{yzztvusxvutvtsuu}iacbowtnr{~v[EBBCBCDCA@CB@BA?@@>:>>:=<94542451/2.0)+,++**+++**()+,-.,*,-,.//0.01-06465111///135;<=<;994/468:;:9:;741/0369:<;4178955:87>;8@DA@CA:9:9;B??@A;557:99;=;>C>cznjonjn~y||uQDDCCAET<>?:>>=@?<<<>>===??@??>@>===:79=???JqtssikomZ?A@@@@@@??>AA=<]~x}|y||y{uvzxryvtyzsqrqtvyyiac`vhkkpkSEAAB@A@??BCB?@?A?=;:=;<=87774205222-/&()('('()''&&'''**)*-00//121131/27764001422411568:;:519<<<<;<<<=<:::<<=>??>;88958AA>Cp|kiijs~~|~~vvxtsx|yrgvNBE@<@CC><@BBB@@?>57CA@ABB=6;?@@@?:45<=79;=?9389T|snnv}z{wcrxy^?==>@>aR>A=>?:;=>?><=<=<=>>>@>?=9:;=<;9=f|vbeosuoI>A@@???AAAACA9Ev~}|{vwxtuyxyvtzrrwxqwzxx{~haca}phqztufEABA?@?@A@?A=;>><<>@@><<=9795017212,,%(+,*))*+*))(('),-.0124431465331/67785578877764118<;935<;999::;=?>??>???<===>?><<>>:Eede\j~~y{x}[?EDC@>?BC@<:=@AB@?:;9?r~pmyqrwstyuz{z}rK=?AA>=>>:>>@?>?=<>>>>?mxuytuutsstsrtrqmlqvd;=>>==>>>;9;<>@<3MoggtqtkgU>A@@????@@@@@B>^wtvxy{xuyzwwxomuvtuyzz}hacay~njt}fC@BB@BBB?@A><>@?@>@@:;:=<46843450.1,.&)-.-+++,,,,))+,,+11026324324651035778767878880/5:<9937:72277769>><=>?>>=;;:F`ftvz{wr||}{z{y}|z??EDBB?>@@@@AB<5@?A@<>?<8:?ABB><<;56<:<@AA87>A@I||uv|hu}W@BABADfG=:ZvotvqnppnsstsssspnqsK9>?====<<::9;>@ACB@???;8<::658644420-0 +.'*../-+*,+*,++--./10000-,154566442104876677422369;;8729:56::::::65<>??@??>>??>@?>><;Qvky~ygqw|~yqkz~xsxQ=?@ABBCBA@??@>:B@<>@@@AACDDA:;>A><9<<898>>3:A@A~yqoyz||~~`:=??@>dX==Mvmuxrktroorontuttppsa;<==>>=><9889<=@@e|qx}eivnY=?AA@@A@@AABCB>\{|{yvzuuxwxrqqqsvsquv~hacbgjwyim{iGABACBA@?CB?A?>>>?=9;=<:888566530-0!+.'(*+-,,--*,-+,-+++()('*.-.0344466544564345663368::9965:78<;:89:<=429<>>>=;<<;;<><:>9Bamyofnqw~~vyyk>B@@?@BCACBA?<@>?@>>??>??>=9=AC?69<>???;29?:7>?v|lutgho^aux|vwepoG<>@B?Rc<<>>7:A?9<>;=A@?>ACABA><=>?CpyvuxrptrlooffoottsqpH8=;<>=?=;:87;<=:Qxyqfatnll@;@@?@??@@?ACB@Fp{}yyzxy}{stvpqrqrtvuqtu|~gacbgkq~uulL@@BCDC?>A@@??A?<;;<>?>;89878630-+1!+.&(((&&&''%&''()()*(*,-/1/0120333234776455555567666984697878;86:;<=57;==<<<:::;;<>=82Iuxkzvx~~~|xw}{qvuot~C@AA@>@?>??AB;??=>=>?@@<><=@@?>;@BCB?=><@BDB@?BBAC@A>dxyuuppssmqojkijmmrub:<;<=<=<<976:<=A??A@>???@AAC?X|||{vzvvwx~zqsurromlotxx{hacbr||k\^hptfE>ABCB?=?@BA@@A=;;678:640/0+0!*-%'')*(''''%%(&'),../0/04632445775441236778876447938;7489752558=;::96<>>>=?>>=>;;;:26a~ywx~}z}~}zt|~}wquy||yx{}~~V>@BB@AAA@>?8;B>>@@@>?=?@>?A@??@A@?@=867=CA?9<@:9>===@CCBCABBB@?=B@>czuuokgmtcH?;<=>?>===?AC@Et}xw}wu|{vwurrurtpiklpxsvhacbp{}wroqswhF?AACCBA@AA?>@>;:??>=:?;78:844011/0!(.%&&()**))+*)**)*+)(+...00//00/246654555333577866767:55:<8879999:;:88;<==;8668;?=58;=gpx~z{zxy}~~~z}}}~}|k=A?AA?A@@??<->>9?==A?==>?>>>=>?A?=>>@@??>?BC@=fujv{z{}~|wswxeA9C>;;RyI>A?@@>89;?==>BB@BB@AA>=?@=E|~tzutrssqrtolnolspgf\;;;;;<;;9997:<>=RvoptvwuunXA=889<<<>@@ACB@[}|w{u{|qowvstwqqstmkvtu~hacbnnlo|xq]F@BCCCA@A@??>?==>?=?=;=9::8535332,/"%-$"#"$#%'&''(((***,,-//00/13446444554333332335776679633557879:499;:9::89:98246:<:8=99hv}|z~}}xy{~|yxvy~|}}G9??AABB@@=>;=@<@<;<<>@AB@@?<:==<=@@ABA?E=;BC<_}|vvsy{U=>?@=En^9B??>;;;=?;>=???>B@d{{}zwsvrrqvqkinqnromnJ5<<<<<<=;889<==Enqorllswt\@>=>?>>=<<;=@ABJk~|yyrptx{urqtomqtoptrs|hacboonr|unqi`H@@ABA???=>>>=?B?>=@>;:::5854434/+0#%+!"$%$ !##&&(*''--,,-,./-/001233343133334444565666677743365878898987<<:7;;:8::7:<:<;Dwvuu~xzwv|~yzy|~|{}}~z~_;?A@A?@@A@?A?@AA@?BBAA?@ABAABB@@B?<>8_|{zuojnttrtswdC@>>>=g{C;=<=>=@?;>>=@@>>A>?=AA??@;Rxttvvswxumnrolfkosqlo`;:;<;=;;<;749>?>dxtrjdgkkaI>@@@?>?A@AC\{zutvvrxztrutomprupovwjacaozxvqfguxiI=>BB@><>A@???B@>>=><:;;56;52373,/1#%+"#%""$&&)+**+*+-.//-0320/.110110357766556656766676766865666589:9:;;:79977<<;;7155:9Nlyr~xz}|{}z}|{||}}|{}zw~x~y}~}x{xwuG@CCB@@A@:@?>=@A@AB?@?A>?AAA@????@?@AAAC?AAB=Y}~y}|zxzzuoopqfC=?>>;V}X9A>=>>@==>>@>>@@@A@A?AB>@;GvzrtuvusnkkppoklkqpnnqM7<<:;;:<;969<@=UtkipppnprnI<9=>>AB@?AACB>Koyz~|wppsstttyqusssssx|gbcaztksqkknqaGAEE?=>@BB@????==@><;==85;942470../!$-&$'&#%(())(''**)*,,,..,-+.43331/.1333433567887798787787844654578:97765644:<9:<74792P{}s}~s{}|~||~~|z}{z~{{}||in|{[;A@???@?@?@@@@?@A@AA@AD;X}}zzttsvzxsqlNDmq?>=>>>><=@@>?=>??@>@@>@@@@?jxrrtwxtuvojqutqomxslp\8:;:9;::<99:<;=IopmmnmfidgU5A@:9;>ABA?>>>>@??><<<;98<<725551/-.!$+#%&&%)***))(((**)'&'*,,-+/2444331./0../....2578999978979889367799;87757:7;>75:=9942W{vwl~~z~~}|zvvww{{{||~{xt|}xw|~|yvqlxvyq?=>??==6>:>;?@@A@?>?@?CB@?@?@@?@?@>=@A@?>?B======>??@@>??>A>_tphpxqorwsjkkprnoonkmjF8:::9:9::88:;<@fpilmllmib`C<>>A@96A@Oiywqqwywrsrotvtrtrqtqxhac`~{xzrkhN>?>@CA???>?@ACB@<>==;:;98226620/-/"&,##&()***)'((&&'))),,,./0102423330-2420100./110244667:99789878988799668755:=<7<87777Y{xyuwu~{{ttwzz{{xx}}yy{x{rq{xw}}{{T;@>?<=><=86==<=@AC=QrrD<>>?>===<>=???>??>>??>>@=Tvvttttusssrmpnkrrmnpnma;9;;88::897:<=:WplmkknmmjjY;@?>?;::@:9=A@A`qtwxvy{vpppoknxusvuy}gaca~}xwtvW@?@CA>>??A@=BA>99>=;:8::7688541///!%0'#&''''&$%%&$$%%%'***+-.-,./0110.02444434326533544555668888999989879889789;<6<:7;:@iox|ux{y}z}{xy||swzzyxuz|z||~~}}|}}}||~~l>>@?@>?=><=48939:<;==:8;<>@?@A@A@?@>>?A>@C?N|{~{|xyyywvsswxyzqP>???>Cj|]9==>?==>>>=>>=>??>>?>?>@=FvxtmlrssstsrrpknkpqkpmmK6:98889;:98;=<>@@>?::A@>=Qku}zsqtpspnustztolqvygacbspsv{|]@?DB?=?A@A>>CB<9;>=::9;:7894321/-/!#/%#%$#%&&&''&##%###&'*+)*+,.*.00/-/01100112256566667656568787668888798888989:8:;:<9Adspy{{s{{}{~zx|wvxrvzzy}~|~{{~{xz{uvupxyP<@?>@?=>><:;<2797;;;8:==;<=>?@??@A?><=M}{zzzxvx|yy|yy^@??>>?`zvG:=??@?=<;;<====?=<>>><=>>gzqoupqssvqpqqngjlnommmb;6777788889:=;ChkkmlikifbfU9>=<<>?=9<>6=ADbsvuyvtxrpprtooosyy}hacbmqqz}zmU?AC??=?B>=AAA@<=?;8;<:989941341/-/"","#'(&'(('())'')*((&',-,+.2440+,03225554543334236545566755666767777877:978899;99:<9Bhywvzww{}yzv|}|~}~zx{~z{}z||~wywjno{vasj@>?A@>=<==<8;88619:7<;9>6:??<:>===<<<;<<>>O}~||{||}{{ywzz{wvvvx{|wdF>?@?>Onv`9=>>>=;<::===<;<><=?=<=?;Z{tvsrqprllutnqopjlnmnkkO477766788:::<=`plomkjkjfbaB:><<<<;<<6;A=6Lcsvvytststruuptqps|iacatwr}|vysXCBA=>;>>;?A;:<;=;8=?>@@?=<4;=756<>>>><:;A==;?>=?<=>=<==?Y}{zxwvuxxyuuwuvzxppvqpkL9==@=EdszJ7;<<<<==<;<>><<>>=<=<<=8Jqqssnrrllpqpqmmqoonoonrk>887876889;:<:Wrlnnmhjig^dT9;=<:;;;78@<;=?^lwzuutttqtnkimqrqx~hacbir~xke[JA?>>?@AA@>><;<=;>?>97885780-.20-/""+" %%%'((('&''&'''%%&&(()%&+++,+.//0000000//022222344544445342332233566577756698656Nsyxrt{qfus|pdxy||zwyyxt|~zz|{routy}{}i_spH=?>=>==9=;976::;:BAA>>?===>?<=A@>><<:9=>==>?>>>;Eh._phgejusloplnokkqrppnqX8;:788678::;:JnlkfhgjdffbdF9:;;:;:;=8;=?>Retyxwtsqlvvoqvpo}|hacatzxusx{qig[J===??@@@?@><=;;87656841036/+0"!,#!#''%%&''&((&''&&%''*)(./0/,,0/1322255344455554322344334355542235555221355534634Rwvxur|tvv~rp|{}~|jdw|uv~}rw}xu|x{zywebus{os}`<;=>>=:;<8<98;87:??@>;==?A?=>>=?@>>=Po}vovvvrwzvqsqruwttxsnpyzfB<>;=;NnprN7?=;<;>==>==>?>>=>>>=>;BT6~ywtrrtpqmosmkleciqqljkE9::9887777:;@gskeinjkiifgU8<;;:98899<9;;Bbguwtuwuwrmos{U/g{jac`~}nfgh_g~sK;=>=>@B?;;=BA;::97278544124.+1$!-%"#&(##&'(&&'('%&'&$&((#'---,.,//-./1112223356666764454553456432136678867789943636\skxz{wwvy~|{s{zwzuqwxtw~|yy|xvxxzzhuwwzz{ysv~~wunsslonrnM99:?=>952=:6>B@=B@B=>@??>=?;;??<:=Y{|{|xxxvyuvuxvnspppvwoooztO;==>>>===<;;:;<=9D7Kquruqomqoopjikggkmnrjra98:;:;;987:<<_qln^^hcgffbfF5<>=:98:<::=>:Ufpwstoqttss}Z/Sarhaa`mqojbaitrN<<<;=?>89=:49?@<777420110/../.*)+*! "%## #!!#!#$%%%''(+++-+,--/01/0/00/0///..//0010021011222.+11112010,.27Vkzyrgoyvkepwwuxsv{zzyne_ajq{{yuyrk}s_x~pj{xnkvqyzy{~oI=>=<:863887782267;529:==956;:::856:9>austrttwwsvqwuuusntwrpstqmsY;9998:YnjlbHNC688768987:;;8886886;'[tmoiojrupmpqlifghkggnjhjG148875765265SrkoH>lfibbdcZ7348:986789:88D[buwptyspzxH.c{wzf_kjtxsqopy|sn\LIHJMMJGIJHGMMKJHFDDBA@BB=>@>>:9:0'0;0.114440-010011/-+*,/34443488::9;<<<;;==>>@@@ABA@ABCAAAA?@>===>>@CCC@@?AAA??DECA>Eh|v{||{vwhmx~zy}{{{xxzvrr|thzk{|nhodLHJIKKLIFFGDHF@IDIJEJLLHECFBHIECHIITbry||y~{||sw{xz}yxz|~zwyxzjLFIHHI`vuvyhfcMEGFDGFGGFGJHFDBEEFC*%,.+('')*))&%#+Kn~yutsstsrpoppppqqppqqsttuuuuuuv}rmmnmnnmmmmmnmmmmllkkjipkedS9-('))+**(((((&)($!$$0DXekrx|{vtttstsqpooqqqqqppqrsttutuuutv~~ronnnmmmmmmmmnnnnoonmlmsl`C+,3300//54/,))&%%%  &$)&$%'.3;Jcz~yutuuuusqppqrrrqppqqrsstttuuutu~~qnonmnmlmmllmnmnnnoonkptiD-2423640499;70,*)'$!!&%#$9510)%09;:7211.((-! "$ "$"4j|{{{zxwvtrrsttttsrqppqrsstttuuutu~rmmmmlmmmmlmmmmnoqppppttP@=872-##.9:7::412,)+%#"" !$$$ .e}{{zyyxurqrssttuutrqqqrrsstuuvvuv~sonmmmmmmmlmmmnnnppppossP<<863*%'/7758:;200+)$""!""$##$#"*R|~}|zyyxvssssttuwwusqqrrssstuwwwxx~~qnpnnmmmllmmllnmnnoopostS=;861+,.674468;901,&## "##&%&&$&'%!9t}|||{zxurssssvwvtqqrstssttvyyy{{rmnonmmllmmmmmmmnoooqpstZ<;863228:843577;5//%! "'"''#&%"%%'?}~~~}|ywtstsrtwusrsttuuutuxz{||}~rnnnnnnmlllmmmmmnooptutsd@997559:;:635769:1/'!  %!""!%'))*)'%M~~~~}~~}{zxutrssttttuvuvvvvvx{}}}~~qmnmmmnmlmllllmmmonouwushI9:769;;<:85666695.+# "$(,+)((((((]}}}}~~}}}}}~~}{{yvtstsstutvwwwwwwxy{~~~~qmnmlllllllllllmnnnqvvuskU:99:;<=;:975665572-)"+ !%(()*(')(*'4w}}||}}}|{|{{|}|||{yxvuttttttvyzyxwxxyz|}~}~~rnnmmmllllllmmllmnnqvvurk^@:;;;<;;::86654455/)# +4/*"!%((*,+*(()&Y~~}}}||||{{{{{{{|{{zyyxwvuuttvy{{{zyxxy{}~}}}qmommmmmmlllklmllmnrwvvrjdJ79;;:::;;978523240)$# ),-.+,' !&'(**())&=~~~}}}}||{|||{|{{{|{{zzzzyxwwuvx{{|}}{zyy{}~|||~rnnnmmmmmlmllkmlmnnsvvvrjgZ:9;:9::;;99:633211.)# ),-.,./+'%$!"$%%''()*f~~~}}}|||||||}|{|{{{|||{{{{yxyz{||}}}{z{||}|||~rnnnmmllllkklllmnootvvurji`C;<889::::;>>44320121*" %'./0/.+(! !$&(,)G}~~}||||||||||||{{{||}~}{|||{zz{{||}||{{{||||{{~qmnmnmmmllmkkklmnopuwwurl]HC@=8899:;;=IVA23211244+%"&$',*!! #))0`r{}}}}|||}}}||||||||}|}}}||||||}||{||}||||||{|{|rnomnnmmlklklllmmnrvvwus\CBGD>:8689=RLRJI44310224610+'%&##$$!"#!!!&"  '*Rjiqx}}}}||}~~~}|||}|}}|||||||||}~~}~}}}}|}||||||{|rmnoonnmmllkklkkmnrvvvuZBBCGG@<8666FhIQXV@23100124689742.,*$%"#&$$$%%! &-Mliiimv~}}}}}~~~~~}~}|}}|}||||~~~~~}~}||||||||||{|qmnnnnnmmkllklmlmmrvuyS0CDDFHA=:765@ZHQdXF34210122567<=?@BA<92*%(+-.2<5..Lljjkjimv|yvnqw~~}~~~~~~~|||||||}}~~~||||||||}||}~~roonnnnmmlllkkllmnsuxd%.EDEEGD?;8655MglWZN043201234468990Ijkkjklljn{r_\[ZYY[^acis{~~~~~~~}|}}}~}~~~~~~~}||||}||}}}~ronnnmmlmmklklkmnnsvs92DDEDGF@<87553E[NS;2234213334567778999;=>?@@<:CD<1DbeijjjklkcYXYZZ[[ZZYZXY\bp~~~~~~}~~~~~~~~~~~~}|||||||}}}|}~rnnnnnmmlmmllkmmnprwU0FDDEFGB>98643;_lO4434443333345667778779:=?@?>GG:0Bacccfije]XYZ[[ZYZZ[\\\[ZYZbjw~}~~~~~~~~}{{{{{|}~~~~rnnnnnnnlmlkkkllmoto4 4GEFEEGE@<98543;\gI643344333356677788778:<===>IF51Cbdb_`^]ZXXYZZ[Z[\[[\]\\\\[ZYZl~~}~~~~~||||{|||~~~rnnmnnmmmllkkllmmnv\# 8GFFEEEGC?<97433B^fC3=C84334567768899789;===;86452Mhd`dY:455567787899;::9<=:<;BKB35>]dc`WXYXYXXYYYYZZ[]]\[\\\YZ[[[Y^n~~~}}~~~~~~~rnnonnmmmmllklmmmotB !>GFFFFFFFD?>976452Mkpf<555678888889;;;::<<;<<976655:ap^XH9468:989:;<<<=;=>=;?HC38@F\db[YXVWWXYYYZZZZZZY[[\^^]\]^][[[Z^q~~~ronmmmmmnmmmlmmmmnuM'EIHHGGFFFFEB><:97765=>?>=EF33>DL]ca^\ZXWWWWXZZZ[ZZ[]^]]\]][\__`\]^[[j~rooonnmmmlllllmmmot\"(GIHHHHGFGFEFB?>;997767A[blnkeWKBFCBC?=>?>>>CJ?1;BHK_cb_][YXWWWWXXZZ[]_`aab_]\\^__``de_[Yd}~qnonnonmommmlklmmorl2*HJIIIIHGHGFFEA@?<;988768>JRaetldj_\T==>><;?HI98@FFPcbaa^\[YXWWXXYXZ^^_ab_^^^___]Z[`fh`\\Zb~rnnnnnnnmmnmmmnmmpptQ+HJJJJIHHHHGGGDAAA?=;::9988<;JVbgnhnO<=====BJG9>DFG^dbbd^\[ZYXXYYY[]__`___^\]__``]ZZcfc^_a^mrnnnnnmmnmnmmmmmnqqrm1*IJKKKJJIHHHHHHEBBCA><;:::;:;:9=DFNK?=<<>>@HKF?CHGZedcbe`]\[ZYYYY\^_`^]]_`a_\^_```_\^cgb]^`c}y|sppoonommmnmmmmmnqqqvX0IJJJKKKJIJJIIHHGECBA@>=<<>=><=<;:9;=>?@ABDLLEFIJXfedddfb^][[ZYZ]___^^^^_`ab`\^```a`^\cgb^``r}g^^XRSX_p}~rnonononnmmnlmnnoqqrtsQGJJKLLKKJJKKKJIIIHHFCDB?@AA@????@ABBCEEEFHNJGMLXfeeedehd_^]\Z\^___]]^^^_``aca]_`a_`a\\bhdaaksYPNKLMJIJLWg{~rnonnnoononmmmnnorrrtvpQHJJLKKLKKKKKJJIIJJJHECDEEDBBBABCDEEDGHHHLONOQYdeedeceih`^^]]_`_^]]]^^^````acb____^]Z\\`a__\UZbaWLKMOPORRWusnnoonoonoonooonorrsuuvkOIJKLKLLKLKLLLKKKJKKJFEFFEEEECCDEEFFHHHKPRQPYdddeeedejid`_`__`^]^^^^_`````aadc^_`a_Z[][[__^`^Z[^ZYYSPRUZVXvsonnnpnnnnnponmnqrrstuuviPIKKLLLLLLLLLLLKKKKKLKIGFFFFFGIJIJKLMNPSSOL\edddddfchjhcabb`__^^^_``bbaa``abdc____^[\_]_ca`\YXZ\]^YTSSU[X[s~~rppomopoponoonnprsrsuuvttlSIKLLLLLLLMMMLLLKKKKLMLLJHGGIJLNNPQRRRQNKM_ededdeedflidecba`____aabcecaaaaabdc`___a`__]bb_`]YXYYZ]ZWUST\VTgz}}~}}~~~pmppooooonoooppqssssuvwurtoVIKLKLLLLLMMMLMLLLKKLLLLLKJKLMNOPPPONLKJScddeeefeefkjdfdcaa`_^_adeihhfaaaabbdda`abaab``bab_[YZZZ[[YWVRU\UPZp|||}~~~~~sonopppponooonosssstuvwutsuq\KJKLLLLLLMMMMMMLLLKLLKKKKLLLMMMNMNLKKKYddeeeeefffkkeefdbaa`__`mifhijhaababcddbbdabcbcdba`\ZYYYYZZYWTRW^YSWgy~~~~~~~~~~~tqqonopqpppnnoqsssstvvwvttuuudOJKKLLLLMMMMMMMMLLLLKKKKKKLLLMMLLLKKM_ddeeeffffejmeeffdbbaa`_ejkpollhaaabcccddfcbccddaa`]\ZZYZ\\[XVWV^eeehv~~~~~~~~~~~~~~~~}~~~tqqpppooppooppsutttuvvwvuuutsulVKJLLLLMMLMMMMMMMMMMLLKKKJKKLLLKJJJTcedeeefffffimfefgfdcbbb``dmnjkkfaabccccdefffffgea`^]\[[Z[\\[\_`^`gnggy~}}~~}~}|}~~~}}|}~}}|||||}~~~~~tpqqqpqqpqpooqtuuttvvwwvuuutrqrp`OIJLLMMMMMMMMMMMNMLLLLJKKKKKKKJIK[eeeeeeffgffgnhefggfdccbbb`dmqkdabbccccdeefgggggeb`^[[ZZZ\\[_cfii`Zf]Zm{}|||||{|||}|{||{||||||||}|}}}~~tqqpqqqqqqpppsuuuuuvwwwvuvvutrqqrjXLJKLLMMMMMMMMLMLMLLKKKKKKLJJIIPbdefdeeffggggmieggggfeddcbcbbkpjbbcccccdeeffffffeb`^[ZZ[\[\aeghc``ZVWYZgx~~|{{{{{{{|||{{||{||}|~~~rpqpppppppppptuuuuuvvwwvuuvuusrqqqodSKJLLLMMMMMMMMLLLKKKKKKKJIIIJYeeeeefefgfghgkleggggggfedcccbcmrmjgdddddeffffffefc`_]Z[[Z\chjic^\a_UTVWY^o|{{||}}}}}||{|}}|}~~~sopppppppopqquvvvvvvvwxvuuttsssrqppqm`QJJLLLMMLMLMLLLLKLKJKJJJIIPbdeeeffffffgffjnggggghhgfeddccbcluqgdcddeeeeffhifedb_][ZZ^eijhedec`^VRSTY][m~~~}~~}}~~~~tpqppqppqqqqrvvvvvvwwxxvuvuttsrrqqqppql_QKJKLLMMLLLLLLKKKJJKJIIJ`gefeefffggggggipifgggghhggedccdcdjqrnieddeeefijmhgfc`_[\agkhfeeefgfaWTTUX^`^s~|}~}|~~spqppopppqqqsvvvvvvwxxxvtvutstssrqqppoppl`TMJJKLLLLLLLKLKKKJIIIYmiefffffgggghgggnmfggghhhiggfeeeeedgknprmljfhijjnlhjfcaaejjgeeeeefehhc\XZ[\bbj}}{z}}soppqpoqrrqprvvvvvwwxwxvtuutttssrqrqqpooopmdYPLJJKLLLLLKKJJJIKXlnifffgfffggghhgglphggghhhiihgfffefeddefjmlghjijilpmliefjkiedeeeeefffgiga_`]adk~~|z{}~}}srrqsrqqpooprvvvvvwwxxwwuvvutttssssqqrqppoopoiaWPLJJJJJIIIIJP_mnnjffffgfgggggghfjqmgghhhiiiihhhgggfeeefdegiijjijkqrnljklifeeedeeeffffgiifaacdu~{xz~xvzsopqqrrrqqqprvvwvwwwxwxwuvvvuttssssrqrqqpoooopomic\VRONNOQV_jpooolffgfghggggggggippkghhhiiiiihiihhgffgghjknljjjjkorpnnljhgfddefggggffggijggfiz~~|zx{~{xw{}~sqrqqqqrrrrrsvwvwwwwxwwvtuvvvutsrrssrqqrqpppoooonnnmkigghknppooooohfghgghhhghghhhoqpjghhiiiiiiiiiiihhijklklpjjijjmrrmnmjihhedeikmnnjfgggijiiottvx{}}zxwwzzyz~vtux}tqrqqppqqrrrswwvwwwxxxwwutuvvutttrrsrrrrrrqpooooonnnnooooopoooopppjghghghhhhhhhhhnqqpighiiiiijjjkkjjjklkjklpljjkjkqrnmomkhhhfemoorrqlgggiijnqrsuvvwwwwusrsuwysttuuvy}tqqpqpprrrrrtwwwwwxxxxxwvvvuttssssrrssrqrqqqqqppooooooooopqqqpqqpqmghhhghhhhhhhhhlqqqpighiiiiihijjjkjjjjkkjppjjjjjpsomopljiiigfkqsqnjghhhiosqrsuuwvutsrsssuv|}vstsstttuvy{~urrqpqqqqrrqswwwwwwxxxxvvvwvutsrrssssssrrrrrrrqpppopppqqqqqqqqrrqqphgghhhhghhhhhhkrrrrpjgiiihhhjjiikkkkkkkjnsljkkjnsqmmpolkjiigfjotpjjhhinttqqstuusrqrstuttuwy~||xutttttuwy{~vrqqqrqqrqrsuwwwxxxyyxxwvwwvvuussssrrrsssrsrqrrqqqpqpqqqrrrrrrrrrqqkgghhhhhhghhhhiprrrrqkhiiihiijjjjjjjjjiilsojkkkmrsolnpomljiiighmuvqlklnstrqrrrqqqssttuuttuy{}yuuuuuutuvvwz|}roopqppoppqqswwwwwwxywxwvvvvvuttsssssrrrrrrrrqrrrrqrrqqrqrrrrrrrrqrlhihhhhhhhhihhhnsrrrrrmihiihiiiijjjjjiiikrqkjjklqsqmmnppnlkjjjigjprsommqtrqqppqrstttttttttuvwxuvvvuuuuuvuuuvwxz{|tpqrsrssrqporxxwwwwwxxxwvvvvvvutsssssrrsrssrssrrrrrrrqrrrrrrrrrrrrrnhhhhhhghhhhhhhmrrrrrrspjiihihiiijjjiijijqsljkkjossommoppomlkkjjiilmmmlpttrqqrrtttttttstttttvztuvuuuuuvvvvuuuuvv~tqqqqpqqprrrvwwwwxxxxwxwvuuuuuutttsssrrsrssssssrsrrrrrrsrsrrsrrrrrrqihhiihhhhhhhihkrrrrrrssrmiiihjjijiiijijjptnjkkjmstrmmmnpppnmllklnommllmrttqqrsstttsssttsuuq}vttuvuuuvvvvvvwvw~trqopqqqqrrtwwwwwwyyxwxvuuvvuttssttsssrsssrsrssssrssrrrrssssrsrrrrrqkhhhhhihhiiihhiqrrrrrsssspkiijjjjijjjjjjptokkkklrstommmoppqonnopponllllqstsqprrsttsttttsrsyvuuuvuuvvuvvwvvv~tpqppprrqqqtwwxxxxxxyxxvvvvwvvutttstsssssssrrsrssssssssssrrsssrrrrrrlghhhhihhihihhhosrrrrrssstrojiijijjjjjjjqtpjjjkkosttmmnnnoppqpqpponllmlosssrpqqrrsstttstyztuuuuuuuuvvvvuv}tqppqrqrrqrvxxxxxxxxxxxvvvvuuuutsttssssrsssssssssssssssssssrssrsrrrsohhhihhhhhhhhhimrrrsrrrsststroljijjjjjiksuskjjjkmtturmmmnnopppppponnlmmmssstrpqqqrsstu~|uuuuuvuuuuuuvuv~trrrsrqqrrtxxxxxxyxyxxxvvvvuuutstttttsssstsssssssssssstssssssssrrrrrqihhhhghhghhhihlrsrsrrrsssstttspmljijikptttmkkkklrttvqmmnnooopppononllllpusstsqprrrstsvuuvvvuuuvuuuuu~urrqrrrrqrvxxxxxxxxxxxxvvvwvvuttttssssssssssssstsssssssssssssssrrrrrskggghihihgiihgjrsrrrssssssstttutsqpoosutttnkkkkkputuvpmmnnnooonnnnommlmmsutttsqqqsvvqw|uvvvuuuvuuuuuu~uqqrqqrqqtwwxxxxxxxxxxyvvvvvuttstttssssssrsrstsstsrssttsssstsrrsrrrrsnhighiihihhhhhipssssssssssstttutuuuvuuttttpkkkkkntttuvpmmmnnnnnoonnnmmmlqutuuuustwyxrs~uuuuuuvvuvvvuv}trqrrpprsvxxxxxxxxxxyxxwvvvvutttttutttsssstssssstssssstsssssssssssrrrqiiihhihghiihihnssssssssssssssttttutttttttrlkllkmttttvvrmmmnnnnnnnnnmmmmotttvwvvwxxyvqyxuvuuvuuvvuvuu~urrqpqrqrwxyxxxxyxxxxxxvvvvvvuuttttsttstssttstttttttsssrsssrrrsssssssrjhiiihhiiiiiiiksssssssssssssttsstttttttuutmklkllquuuuuwtommnnnnnnnonmmmmqtttvwwxxxxxrs}zuuuuuuvuuuuuu}soqqqrrqswxxxyxyxxxxxxxvvwwvvvuuttttsssssstttsssstssstssttsssssssssssrjgiiihjiiiiijijrssssssssssststttttttttttutnllllkouuuuuuwwsnmnnnnnnommmnmotuttvxxxxxxvqx{tuuvuvvuvvvuu}trsrqrqrvxxxxxxyxxyyxxxvvvwvvvvttttttttttttsttttttsssssssssssssssrrrsrlhiiiiiiihhijijpssstssssstssssttttttttttutollllkmsuuuuuuwxvronnnnnpnnnnmnrvuttwxyyxxxrs~xuuuuuvuvvvuuv~~trqrrqqtwxxxxxxxxxyxxxxvvvvutttttttttttttttttttttttttttstsrstssssrrrrskhhiiiiijjjjjigntsssssssstssstttttututuuuvqklkmlmruuuuuuuuwwvtrqqsupnnnnnotuvuuwyyxxxtpy~uuuuuuuuuuuvuv~sqsqrqquxwxxxyyxxxxxxxxvvvuuutttsstttttsrttttttsssssststtttstssssstsrrjhhhhiiiiiiiijiksssssssssttsttstttuttuuuuusllllklpuuuuuvvuvvvwwwvvwrnnmnnnruuvvvwyyxxvpsxuuuuuuuuuuuvuv}tppqrtsvyxxxxxxxxxyxxxyvvvvvuuuttttssttssttttttttttttttttsssrrsrsssssqjiihghjiiiiiiiijqsstssssttstttttttttttuuuutmlklllmtvuuuvvvvvvvvvvvwsmnnnnmouuuvvvvwxxvqpz|uuuuuuuuuuuuuuu~tppqrrswxxxxxxxxyyyxxxxvvvvvvuttttttttttttttttttttttsstttttttttssstssoghjjjghjjjiihijiosstssssttttttttttttttuuuuunlmlllmsvuuuuuvvvvvvvvvwuonnnnnnsvuuvwvwwwvqor~~~~xvuuuuuuuvvvvuuv~ursssqswxxxxxxyxxxxyyxywvvvwwvutsttstttstttttttsttttssstttttttttttsssnghiijjghjiijhijiltsssstsststtttttttutttuutuplmllllpuuvvvvvvvvvvvvvvvqnonnnnpvwwwwvvvwwsopx||{xvuuvuvvuuuvvvvvuv~ursrrsuxxxxxxxxxxxxyyxxwwvwxwvvutuutttuttttttttsttttttttsttttttssstssmiiiiijjiiijihjjjjststssssttttsttttttttuuuuvrllllllntuuvvvvvvvvvvvvvvtnnnnnnnswwwwvvvvvuppq{vuuuvuuvvvuuuuuuuvvv~vsrsrsvwxyyxyxxxxxyyxxxwvvvwuvuttttttttttttttttsttttttssssssssrssstsrkiiiiiiiijjijjiiihqsttttttsttrtttttttuuuuuuvsmmmmnnnsvuvvvvvvvvvvvvvvupnoononovwvvvvvvvvropvwuuvvvuvvvuuuvvuvuvvv~vrrqrrvxxxxxyxxyxyyyyxxwvvwwvvttttutttttuttttttttttttttttttstsssssssqjihghhhggijijjiijiotststtttttststttttttuuuuutmlmmlmmquuvvvvvvvvvvvvvvvrnooonnnswvvvvvwvvtoosz{uvvuvuuvvuuuvwwwwvvv~tqrqprwxwxxxxxxyxyxyyxxvvvwwwwvuvuuttttttsttttttttttttttttsttttsstttqiiiijjjjjjjjjjiijiltstttstttttttttttuuutuuuuunlmlmmnouvuvvvvvvvvvwwwwwtoooononpvvvvvvwvvvqopuzwvvuvuuuuuuuvvvvwvuv~tprqqswxxxxxxxxxxxyyyxxwvvwwwvvvuuuttuttttttttttttttttttttttttstssstqhijiiijjiiiiiijjijkrttttsttttttttuutuuuuuuuuuplmmmnmmsuvvvvvvvvvvvvvwvupnonnnoosvvvvvvvvvsoorx{{uvvvvuuvuuvuuuvvwvv~urssrtxxxxxxxxyxyyyyyxxwvvvwvvvuuttttutttttttttttttttttttttttttsttttoiiiiiiijjiiiiiijjjiquttttttttsttutuutuuuuuuuvqlmmmnmmquuuvvuvvvvvvvvvvvrnonnnoopvvvvvvvvwuoooty{vvvvvvvvvvvvvvvvvvv~urrrquxxxxxyyxxyyyyyxxxwvvwwwwvuttttttttttttttttttttsttstttsttsttsttnijjiiiijjjjjjjjjijiotttttttttttttttuttuuuuuuvqlmmlmmmouvvvwuvvvvvvvvvvvuonnonnoosvvvvvvwwvroopvx{zuvuuvvvvvuvvvvvvvv~tqsrrvxxxxxxxxyyyyyyyxxwvvvwwvvvuuuttttttttttttttttttttsstssttttttttmiiihhiiijjjjjjkkijjlststtttttttttuttuuuuuuuuusmllmmlmntwvvvvvvvvvvvvvvvvpnnonnnnqvvvvvvwwwuoooswxzuvwuuvvvvuuvvvvvvv~urrpqvxxxyxxxxxxxyyyywxwvvvvvvuuuutttttttttttttttttttsssttssttstsstsliiiiiiiiiiijjijkkkjkrtttttttttttttuuuuuuuuuuvtmlmlmlmnqvvvuvvvvvvvvvvvvvrnnooooonsvvvvwwvxwqooquvwy|~uvwwuuvvvvvvvvvvuv~uqqqrvxxxyxxxxyxxyyyxxxwvvvvvuuuuuuttttutttttttttttsssssstsrstttsssrjiiiihihiijjiiiijjjjjotttttutttttttuuuuuuuuuvutnlmmnllmptuvvvvvvvvvvvvvvvuooooooonpvvwwvvvvwtooosvvwxyyxuuvuuuvvvvvvvvuvvv~tqrsswxxxxxxxxxxyyyxyxxwvvvvvuuuuuttttttttttttttttttstsssssssttssssqhghihhhhhhiiiijkjijjimttttsttttttuuttuuuuuuuuvuommnmlklnuvvvuvvvvvvvvvvvvvqnoooooootwvvvvvvvvqooqvvvwwwwwuvuvvwvvuvvvuvvvv~uqppswxxxxyxxxxxyyxxxwxwvvvvvvuuuuttuuutttstsstttttssttsssssstsssssphhiijihiiihiiiijiiijjkstttttuuttttutuuuuuuuuuuvqmlmllllnrvuvvvuuvvvwvvwvvwsnnonnooopvvvvvvvvwsooptwwxwwwwvvvuvvvvvvvvvvvuv~tqsrsxxxxxyxxxyyyxyyxwxwvvvvvvuuuuuuuuuuttttttttsssstsssssttttssrstojjjjiijjjiiiiiiiiiiijiqutttttuutuuuuuuuuuuuuvvvrllmmmmnnouvvvvuvvvwwvvvvvvuonnnoonnosvvvvwwvwvpnorvwxwwwxwvuuvvvvvvvvvvvvv~ursrrxxxxxxxxyyyxyyyxwxwvvvvvvuuuvuuvvuuttttttttttsstttttttttttttttngjijihgijjihhjiiiiijjimtttututttttttuutttuuvuvvsllmmmlmmmtwvuvvvvvwwvwvvvwvqnonoonnoqvvvvwvvvwroootxwwwwwwvvvvvvvwvvuvuvvw~urrstwxxxxxxyxyyyyxyxwwwvuvvuuuutuuuuutsssttttttttsssstttttttttttttmfgijiihghijhghjhjijjjjksttuttuuuuttuuuttuuuuuuutmlmmmmmmnrvvvvvvvvvvwwvvwvvsonnnnooootvvvvvvuwtooorwxwwxwwwvvvvvvvvvvvvwvv~ussrswxxyxyxxxxyyxyxyxwwvuvvuvvvtttututtssssstttttuttttttttsttttttskgghiiijiggiihhiiiiiiijiquttuttttuuuuuuuuuuuuuvvumlmmmmmmnouvvuvvvvvvvvvvvvvuonooonooopvvvvvvvwvpooouwvwxwwwvvvvvvvvvvwvvuu~vsrqtxxxxxxxxxxxyyxxyxxxvuuuuuuuttuuututttutsstttttsttttssrrssttstrjfggggggijijiiiiiiiijhjiltttuutttttuuutuvuuuuuvvvolllmlllmntvvtvvvvvwvvvvvwvvqooonnnoootvvvwwwwwrnporvvwwwwwvvvvvvvvvvvvvvu~trtstwxxxxxyxyxxxyyxyxxxvvvvvvututuuutuutttuttttsststttttsssttttstrjghiihhhhhiihhiiiiiiihijirttttttuuttuttvuuuvuuvuvpmmllllmmnsvvutvwvvwvvvvvwvwsnnnnnooooqvvvvvwwwtopppuwvwwwwwvvvvvvvvvvvwvv~urrqswxxxxxxxyxxxyxxyxxxvvvvvvvvvuuuuututttttttsrttttttttttttttttuqijjiiiiiiiihhiiiiijjiijijottuutttuutuuuuuuuuuvvuvrnmmmmlmmmpvvvtvwvvvvvwvvwwwuooooooooootvvvvvvwwqooorvwwwwwwvuvvvwvvvwwvvw~vrrrtwxxxxxyyyxyxxyyyxxxvwvvvvvvvuuuuutttttttttsstttttttttttttttttpiihghihhhhhiihhhiiiijiiiilstttuutuuuuuuuuuuuuuvuvsmlllmmlmmmtvvttvwwvvwwwvvxvvpnooooooooqvvvvvvvwsopopuwvvwwwwuvvvvvvvvwvvv~urssswxxxxxxxyyxyyyxyxxxwwvvvvvvvvvuttututttttttttttttttttttttttstpihhhhiihhhhhhhhiihiiijjjjkrttttutuuuuuuutuuuuuuvvslmmmmmlmmlqvutswwwvvvvwvwwvvsnoonooooootvvvvvvwuooppsvvvvwwwuuvvvvvvvvvvv~ursstwxxxyyyxyxyyyyxxxwxwxwwwvvvuuuuttttutttttttttttttttttttttttstohihhiihhhhhiiihhiiiiiiiiiinttttttttttuuuttuuuuuuvsmlllllllmmpvvurvvvvvvvvwvvvvtnnnoonooooqvvvvvvvvpooopuwvwxvwvtuvvvvvvvvvv~urtstwwxxyxyyxxxxyyxyxxxwwwwvvvvvuuuuuuuutttttttttttttttttssttttttmiiiiihiiiihiihhhhhiiiiijihjrttttttuttttuuuuuuuuuusllkllllmmmouvvrswvvvvvvvvvvvuonoonnnooootwvvvvvvroppprwwwxwwxuuvvvvvuuvvv~tqrrsvxxxyxyxxxxyxyyyxxxwwvvvvvvvvvuuuutuuttttttttttttttttttttstttliiiiiiiiiihhiihihhiiiiikkiioutttuttttttuuttutuuuusmlkllmllllmrvvtpuvvvvvvvvvvvvqmnnnnoonooqvvvvvwwtpopopuwwwwxwvtvuuvvvuuvv~uqrqqvxxxwxxxyxxyyxxxxxxvwvvvvvvvvvuuuuuuuttttttttttttttttttttsttrkiiiihiiihhhihiihhihiiiijjihjsttttuuuttttttttttuuutnkklkllllllpvvvprvvvvvvvvvvvvrnnnnnnoooootwvvvwvupppoprwwwwxxwtuvuvuuvvvu~urrqrvxxxxxxxyxxyyxxxxxxwvvvvvvvvvvvvuuuuuttttttttttttttttttttsstqjiiiihhiihihhiihiiiiiiijjjihgottttttuuttttttttttttsllkkklllkllntuvrouvvvvvvvvvvvuonnnnnnnoooqvvvvvvvqoooppuwwwwxxvtuuuuuvvuu}ursssvxwxxyxxxyyxyyxxxxywwwvvvvvvvvvvuuuuuuuuttuttttttuutttttttttqijjjjiiiiiihiiiiihiiiiiiiijjhkrutttuuuuutttttttttuqmlllllmlkkmmrvvtorwvvvvvwvvvvusonnnnnnooooswvvwwwsoppppswwwwxxwttuuuuuwvu~urtsqtwxxxxxxxyxxxxxxxwywwwwwvwwwwwvvuuuuuuttttttttutttttttttttstpihhiiiihhihihgghhggihghhhihhjimstttsttutttstttttutmjkkkkjkkkkllovvuonruvvvvvvvvvvsnmmmmmnnnnnotvvvvwtnppooptvwwwwwvrttttvwtt~|{{{{|}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||{{{{{{|||||{{{{{{{{{|zwwwwwxxwwwwxwwwwwwwwxwwwwwwwwwwz|{{{{{||{{{{||{|||yxxxxxxxxxxxxy|||zyz{|||||}||||{yyyxyyyyyzyy{|}}|}|yz{zzz{|}}}}}}z{{{{}{{{~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}~~~~~}}}}~~~~}~}~~~~~}~~~}}~~~~~}~~~~~~~~~~~~~}|||||||||||||||||{{{{{{{zzzzzzzzyzzzzyzyyyzyzzzyyyzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|yxxxxxyyyyyyxyyyyyyyyyyyxxyyyyyyyyzz{{{{{zyyzzzzzzzz{{|{|}||||}}|}}}}}}}}}}}}}}}||||||||||||||||||{{{{{zzzzzzzzzyzzyzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzxxxyxyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyzz{{{zyyzzzzzzzzzz{|{{||}||||}}}}}}}}}}}}}}}~~|||||||||||||||||||{{{zzzzzzzzzzzzyyzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}~xxxyyyyyyyyyyyxxyyyyyyyyyyxyyyyyyyyyyzz{zyyyzzyyyyzzyzz|{{|}|||||}}}}}}}}}}}}}}}}}||||||||||||||||||z{{zzzzzzzzzzzyyzzzyyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{{yyyyyyyxyyyyyyyyyyyyyyyyyyxxyyyyyyyyyyyyzzyyyyzyyyyyyy{z{{||||||}}}}}}}}}}}}}}}}}}|||||||||||{|||||{z{z{{zzzzzzzzzzzyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyzzyyyyyyyyyyyyyyyyyyyyzyyyyyyyyyyyyyz{{{||{|||}}}}}}}}}}}}}}}}}}}}|||||||||{{||||||{{{{{zzzzzzzzzzyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzyyyyyxz{zyyyyyyyyyyzzyyyyyyyyyyyyyzz{{|||||||}|}}}}}}}}}}|}}}}}~|{|||||||||||||||{{|zzzzz{zzzzzzzyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyzzyyyyyyxyyyyyyyyyyyyy{{zyyyyyyyyzyyxyzz{||{{||||||}}}}}}}}}}}}}}}}{|||{{|||{||||{{|{z{{{{{zzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyyyzzyyyxyxyyyyyyyyyyyyyyz{zyyyyyyyyyyyyyzz{||z|||||||}}}}}}}}}}}}}}}}}}}||||||{{||||{{z{{{z{{zzzzzz{zzzyzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyxyyyzz{zyyyxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxz{{{|{|{||||}}}}}}}}}}}}}}}~}}|{|||||||{{||{|{{zzzzzzzzzzzzz{{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyxyyyyyyyxyz{y{{{{zzyyyyyyyyxyyyyyyyyyyyyyyyyyyyyyyxxz{{z{{{z{||}}}}}}}}}}}}}}}}~}{||||{||||||||{zzzzzzzzzzzzzzzyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyyyxxyyzz{{|{{yyyyxyyyyyyyyyyyyxyyxyyyyyyyyyyyy{{{z{{{{||||||}}}}}}}}}}}}|}}||||{|||||||zz{zzzzzzzzzzzzzzzzyzzyyyyyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyyyyyyyzz{{{{{yyyyyyyyyyyyyyyyyxxyyyyzyyyyyyyyzzzzzz{{|||||||}|}}}}}}}}}}}|{{|||{|||||{{{{{{zzzzzzzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyyyyyyxxxyyyxxxyzzz{zzzzzzzyyyyyyyyyyyxxyyyyyyyzyyzyyyzyyzzyzz{{{{||||||||}}}}}}}}}}|~}||||{||||{|{{{z{zzzzzzzzzzz{zzzzzyyyyyyyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyxxxxyyyyxyxyyyyzzzzyzzzyyyyyyyyyyyyyyyyyyyzyyzzyyyzyyyyyzzzz||}||}}||}}}}}}}}}}}}}|{||||{{|||||{zzzzzzzyyzyzzzzzyzzzzyzyyyyyyyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyyyyxyyyyyyyyyyyyyzz{zyyyyyyyxyyyyyyyyzyz{yyyyyyyyyyz{{z{|}|||}|}}}}}}}}|}}~}~||{|{{z{|{{|z|{zzzzzzzzyzzzzzzzz{zyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyyyxxyyxxyyyyyyyzz{{{zyyyyyyyyyyyyyzyyzzyyyyyyyyyyyz{|{{|}||}}}|}}}}}}}}}|}}~|{{|zz{{{{|{{|{zzzzzzzzzzzzyzzzzyzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyyyyyy{zzzzzyyyyyyyyyyyyyyyyyzyyyyyyyyyyzz{||{|||||}||}}}}}}}}}|}}~~}|{{{|||{{|{{{{z{zzzzzzzzzzzzyyzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzyyyyyyyyyyyyyyyyyyzzzzyyyyyyyyyz{{{|{{|{{|}}||}}}}}}}}|}}}~~~|{{||zzz{zz{zzzzz{yzzyyyzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzz{zyyyyyyyyyyyyyyyyyzzzzyyyyyyyyyz{|{|||{{||}}|}}}}}}}}}}}}}}|~}{{|{zz{{{{{{zzzzzzzzzzzzzzzzyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyyyyyyyyzyzyyyyyyyyyyyyyyyyyyzzzyyyyyyyyyz{||||{||{{|||}}}}}}}}}}|}||~~|{{||||{{{{{z{{{zz{zzzzzzzzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyxyyyyyyyyyyyyyyyxxyyyyyyyyyyyyyyzyyyyyyyyzz{||||{||||{|}||}}}}}}}}|||}}~~~}|}}|{}{~||{{{{{{{|{{{z{zzzzzyzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyyzyyyyyyyyxyyyyyyyyyyyyyyzyyyyyyyy{{{{|||||||||}}||}}}}}|}}||||}~~~z|}}|||z}~}{{{{{{{{{{{{{zzzzzzzzzzzyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyyxyyyyyyyyyyyyyxyyyyyyyyyyyyyzzyyyyyyyyzz{{{|||||||||||||}}}}}||}||||}}|{z{{{|}}}~}}{{{{{{{{{{zzzzzzzyzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyxxyxyyyyxyyyyyxyyyyyyyyyyyyyzzyyyyyzyyz{{|||||{|}}}|||||}}}}}}||||||}}}}}|||}|||}~~~}~}}{{{{zz{{zzzzzzzyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyxyyxyxyxxxxyxxyyxxyxxyyyyyyyyyyyyyyyyyyyyyyyzyyzz{{{||{|{|}||||||}}}}}}|||}}||z|~~~~~~|}~}}|z{}|{{zzzzzzyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyxyxxxyxxyxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyzzz{||{{{z|||||||||}}}}}}}|}}}}|}{{}~~~~~}|~}~~|||{|}{zzzzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyxyyyxxxyxxxxxxxxxyyyyyyyyyyyyyzyyyyyyyyyyyyy{z{{{{|{{{||||||||}}}}}}}}}}}}}}}|~~~~~}~~~~~|~~zzzzzyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyxyxyyxxxxxyxxxxxxyyyyyyyyyyyyzyyyyyyyyyyyyzzzz{{{{|||}||||||}}}}}}}}}}}}}|}~~~~~~}{zzyyzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyxxyyxxxxxyxyxxxxxyyyyyyyyyyyyyzyyyyyyyyyyyyz{{{{{{|||}|}}||||}}}}}}}}}}}|||~~~~~~}zzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyzzzyyyyyyzzzz{{{{{{|||}}}}||||}}}}}}}}}}||||~{~}zzzzzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyxyyxxxxxxxxxyxxyyyyyyyyyyyyyyyzzzzzzzyzzzzz{{{{{{{||}}}}|||}}}}}}}}}}|||{|~~~{~~}~|zzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyxxxxxxxyyxxxyyyyyyyyyyyyyyyzyzzzzzzzzzzz{zzzzz{{{|||}}}||}}}}}}}}}~|||{|~~~}~}~{zyyyyyyyyyyyyyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxyxxxxxxxxxyyyyyyyyyyyyyyyzzyzzzzzzzzz{{{zzz{{{{{||}}}}}}}|||}||}}}|{{|~~~~~}zzzyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxyxxxxxxyyyyyyyyyyyyyyzzzzzzzzz{{{{z{{{zz{z{{{|||||}}}}}||||||}|}|{{{~~}~~{{|~|zyzyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyyxyyxyyyyyyyyyyyyyyzzzzzzzzzzz{{{{{{{{{{z{{{{||||}}|}|||||||}|}}{{{}~~~~~}}}~~zzyyyyyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxyxyyxyyyyyyyyyyyyyyyyzzyzzzzzzzz{{{{{{{{{{{{{{{{{{|||}|||{{{{||||||{{|~~{zyyyyyyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyxxyyyyyyyyyyyyyyyyyyyyzyzzzzzzzzz{{{z{{{{{{{{{{{{{{{{{||||{{{{{{||||{{|{~~~yzzyzyyzyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyyyyyyyyyyyyyyyyyyyzyyzzzz{zzzzzz{{{{{{{{{{{{{{{{{{{{||||{{|{{{|||{{|||~~{zzzzzzzyzzzzzyzyyz{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzyzzzz{zz{z{z{{z{{{{{{{{{{{{{{{{{{{{{|{{{{{{|{{{|{|~|zzzzzz{{{z{{{{zz{zyyyyyyyyyyyyyyyyyyyyyyyyyyzyyyyyyyyyyyyyyyyzzzzzzz{{{{{{{{z{zz{{{{{{{{{{{{{{{{{{{{{{{{{{{{|{{{|~~}{{{{{{{{{|{{|{{|{zzyyzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzyzzzzzzzzz{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|{{|{{|{|~|{||zyyy}zyyyz{{{zzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzzzz{zzz{zzzzzz{{{{z{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|~|||yyz{{{zyyyyyyyyyyyyyyyyyyyyyzyyzyzzzzzzzzzzzzzzzzz{{{{{{{{{{{{{{{{{{{{{{{{{{{|{{{{{{{{{{{|{|||}¾zy{|{zyyyyyyyyyyzyyyzyyzzzzzzyzzzzzzzzzzzzzzz{zz{zz{{zz{{{{{{{{{{{{{{{{{{{{{{{{|{{{{{{{{{{|{}¿Ĺ}zz{{zyyyyyyyyzzyzzyzzzzzzzzyzyyzzzzzzzzzzz{zz{{{{{{z{{{{{{{{{{{{{{{{{{{|{{{{{{{{||{{{{{|}|}ǹ~z|{zzzzyyyyzzyzyyyzzyyzzzyyyyyyzyyyzz{zzzzzzzzzz{{{{{{{{{{{{|{{{{{{{{{{{{{{{|{||{{{||{}}~ëxz{{{yyyyyzzzyyzyyyyyyyyzzyyyyyyzyzzzzzzzzzzzzz{{z{{{{{{z{|{{{{{{{{{{{{{{{{|||{{|{{{|~~}}ŷ|z|{{yyyyyyyyzyyyyyyyyyyzyyyyyyzzzzzzzzzzz{zzz{{{{{{{{z{{{{|{{{{{{{{{{{{{{||{{{|{|||}~}~¦y{|zz{zyyyzyyyyyyyyyyyyyyyyyyyyzzzzzzzzz{z{{{{z{{z{{z{{{{{{{{{{{{{{{{{{{{{{{||||||}~|ɸzz{zzzyyyyyyyyyyyyyyyyyyyyyzyyzzzzzzzzzzzzz{{zzzzzzz{{{{{{{{{{{{{{{{{{{{{{||{||{{|}|~y{zzzyyyyyyyyyyyyyyyyyyzyzzzzzzzyyzzzzzzzzzzzzzzzzzzz{{{{{{{{{{{{{{{{{||||{{|||}||ǰx{zyyyyyyyyyyyyyyyyyyyyyyzzyyyyyyyyzzzzzzzzzzzzzzzzzz{{{|{{{{{{{{{{{{||{{||{{|}|ʹy{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzyzzyzzzzzz{{{{{{{{{{{{{{|{{||{{{|||Źʼy{zyyyyyyyyyyyyyyyzyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzz{{{{{{{{{{{{{{{|||||{{|{|}|øͬzzyyyyzzzyyyyzzzyyyyyyyyyyyyzyyyyyyyyyzyyzzyzzzzzzz{z{{{{{{{{{||||{|{{||||{||Ĺƍw{{zyvsuxyzzzzzzyyyyyyyyyyzzzyyzzyyzzyzyyyyzzzzz{{z{{{{{{{{{{|||{{{{{{{|{{|½ҿͥyvhdeb``cgqx{zzzzyyzyyzzzzzzzyzzzzyzyzzzzzzzzzz{z{{{{{{{{{{{{|{||{{{||{{{{|ϿȲr^^_\^^\\\^clw{zzzzzzzzzz{zyyyyzzzyyzzyzzzzzz{z{{{{{{{{{{{{{{|||{{{{{||||{|Ϲigqod\\\_`_a_`s{yzzzzzzzzzzzyzzzzzzzzzzzzzzz{zz{{{{|{{{{{{{||||{{{{{|||{{{|~nnongfea``agb_s{zzzzz{{{zzzzzzzzzzzzzz{{{{{{z{||{|{{|{{{{{{{||{{{{{{||||{|mnnooonhbb`aieap{zz{z{{{{zzzz{{{{{{zz{{{{{{{{{{{{{{{|{{{{{{{||{{{|{{||{{||½oqnnnnnnkfb_bjc]ix{{{{{{{{{{{{{{{{{{{{{{{{{|{|{{{z{{||{{{{{{{|||||{{{||{{|¶žrtononnnmhcb_dkbY`r{{{{{{{{{{{{{{{{{{{|||{{|||{|{{{{{{{{{{{{{||{||{{{{{{{|ƤĿttpooonnnlgda_elf^akw{z{{{{{{{{{{{|{{{{{{{{{{{{{{{{{{{{{{{{{||{{{{||{{|{{|Ǻturppononnkebdaiqqppuzz{{|||{{{{{{{{{|{{{{{{||||{{{|{{{{{{{|||||||||||{||{ƽvusqqoooomllnmhjqvpsyzz{{xxyz||||{{|||{{{{{{||||||||{{{{|||{||||{{|||||{|{ƻxwurqqpooporuuuukfpggox|x||z|}}||}|||||||}|||||}|{||{{||||{{{{|{|{||||{ÿywurqqppqqtwxxromc`bebhxz{}}|}|}||||||||||}}||{||{{|{|{{{|{{{{zzz{~¿{zwuspqqqrvyyyrllog^_bb`cş}z{}}|}||||}}|}}||{|{|{{{|{{{{{{{{zzz{{{~~zwusqqqtyzzxtssqni^]_adef˳{z||{{|||{{|}}{{{|{|{z{{zzz{z{zzzzzz{{xvtrqvz{zwttttuun`^_achhgɰ~y|{|{{{|{}||{{|||{z{{{|{{{||{{zzzz~~yxvvx{{yuttttttuupfbeffmiԷz|||||||||{|||{{|{|{{||||{||{{{zz{z{|{zwtttttttttuvslklikmŎ{|||||||{||zzzz{||{{{|{{{{||{{{{|}}}zwutttttttttttvvrmmmlԾz|||{{||{zzzzzz{|{||{{|{|{{{||{|}|zxwvtttvvuuutttuvwttq~ƹ}}{|{{{|{zzzzzz{{zz{{{{z{|{||zz|}|zywwusuxyz{zwttttvwvvùƏ~|{{{{zzzzzzz{{{{{{{z{|{{{zz}{}|zxwwuty||~~}xtttvwx¿ٮ~}|zzzzz{{|{zz{|||{zz{zzz|}{yxwwutx~}zvtuvvyÿ‘~~}}|{|{zz{{{{{zzzz{zzz|}{ywwwutw||wwvvxȳ|{{}~||||zzzzz{{||}y|}}{zxwwvtuz}y|”ppoqsw{}||||{{{yz{}}{zxwwwvuw|~qqpppoory~}|yz{|}{{yxxwwvv|uxsooprqry~yzz{|}||zyyxz{}{vy{xtsrsvxy{|xzz{{}}||{|}}{~}}|ywsrvz|yyyyzzz{|}}}}}|{~w}~~}{xvw}{~xyzzzz||}}||{z|{{wttvz|yx|~~~xyzzzz{|||{zyru{|wtsuw{}}xyzzz{{{{zzy|~~{y{}~}}xyzzzzzzzzzz}}~yyzzzzzzzzy~~~{yyzzzzzzz}~~zyzzzzyy}~~~{yxxyx~~}~~~~~~ \ No newline at end of file diff --git a/tests/gtest/avifchangesettingtest.cc b/tests/gtest/avifchangesettingtest.cc index 485a87be2c..31c37dfb50 100644 --- a/tests/gtest/avifchangesettingtest.cc +++ b/tests/gtest/avifchangesettingtest.cc @@ -241,6 +241,36 @@ TEST(ChangeSettingTest, UnchangeableImageChromaSamplePosition) { AVIF_RESULT_INCOMPATIBLE_IMAGE); } +TEST(ChangeSettingTest, UnchangeableRenderedSizeOverride) { + if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) == + nullptr) { + GTEST_SKIP() << "Codec unavailable, skip test."; + } + + ImagePtr image = testutil::CreateImage(/*width=*/64, /*height=*/64, + /*depth=*/8, AVIF_PIXEL_FORMAT_YUV444, + AVIF_PLANES_YUV, AVIF_RANGE_FULL); + ASSERT_NE(image, nullptr); + testutil::FillImageGradient(image.get()); + + EncoderPtr encoder(avifEncoderCreate()); + ASSERT_NE(encoder, nullptr); + encoder->codecChoice = AVIF_CODEC_CHOICE_AOM; + encoder->speed = AVIF_SPEED_FASTEST; + encoder->extraLayerCount = 1; + encoder->width = 128; + encoder->height = 128; + + ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1, + AVIF_ADD_IMAGE_FLAG_NONE), + AVIF_RESULT_OK); + + encoder->width = 256; + ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1, + AVIF_ADD_IMAGE_FLAG_NONE), + AVIF_RESULT_CANNOT_CHANGE_SETTING); +} + void EncodeAnimation(const char* key, const char* value_before_first_frame, const char* value_after_first_frame, const char* value_before_second_frame, diff --git a/tests/gtest/avifprogressivetest.cc b/tests/gtest/avifprogressivetest.cc index 3071b5604e..9aec82e376 100644 --- a/tests/gtest/avifprogressivetest.cc +++ b/tests/gtest/avifprogressivetest.cc @@ -187,6 +187,65 @@ TEST_F(ProgressiveTest, DimensionChangeWithAlpha) { TestDecode(kImageSize, kImageSize, /*expect_alpha=*/true); } +TEST_F(ProgressiveTest, DimensionChangeExternal) { + encoder_->extraLayerCount = 1; + encoder_->width = kImageSize; + encoder_->height = kImageSize; + + const auto smallImage = testutil::CreateImage( + kImageSize / 2, kImageSize / 2, 8, AVIF_PIXEL_FORMAT_YUV444, + AVIF_PLANES_YUV, AVIF_RANGE_FULL); + ASSERT_NE(smallImage, nullptr); + + ASSERT_EQ(avifEncoderAddImage(encoder_.get(), smallImage.get(), 1, + AVIF_ADD_IMAGE_FLAG_NONE), + AVIF_RESULT_OK); + + ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image_.get(), 1, + AVIF_ADD_IMAGE_FLAG_NONE), + AVIF_RESULT_OK); + + ASSERT_EQ(avifEncoderFinish(encoder_.get(), &encoded_avif_), AVIF_RESULT_OK); + + TestDecode(256, 256); +} + +TEST_F(ProgressiveTest, DimensionChangeExternalWithAlpha) { + const auto image = + testutil::CreateImage(kImageSize, kImageSize, 8, AVIF_PIXEL_FORMAT_YUV444, + AVIF_PLANES_ALL, AVIF_RANGE_FULL); + ASSERT_NE(image, nullptr); + testutil::FillImageGradient(image.get()); + + encoder_->speed = 3; + encoder_->maxThreads = 1; + encoder_->extraLayerCount = 2; + encoder_->width = kImageSize; + encoder_->height = kImageSize; + + auto smallImage = testutil::CreateImage(kImageSize / 2, kImageSize / 2, 8, + AVIF_PIXEL_FORMAT_YUV444, + AVIF_PLANES_ALL, AVIF_RANGE_FULL); + ASSERT_NE(smallImage, nullptr); + testutil::FillImageGradient(smallImage.get(), /*offset=*/17); + + ASSERT_EQ(avifEncoderAddImage(encoder_.get(), smallImage.get(), 1, + AVIF_ADD_IMAGE_FLAG_NONE), + AVIF_RESULT_OK); + + ASSERT_EQ(avifEncoderAddImage(encoder_.get(), smallImage.get(), 1, + AVIF_ADD_IMAGE_FLAG_NONE), + AVIF_RESULT_OK); + + ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image.get(), 1, + AVIF_ADD_IMAGE_FLAG_NONE), + AVIF_RESULT_OK); + + ASSERT_EQ(avifEncoderFinish(encoder_.get(), &encoded_avif_), AVIF_RESULT_OK); + + TestDecode(kImageSize, kImageSize, /*expect_alpha=*/true); +} + TEST_F(ProgressiveTest, LayeredGrid) { encoder_->extraLayerCount = 1; encoder_->quality = 21; diff --git a/tests/test_cmd_progressive.sh b/tests/test_cmd_progressive.sh index 6492ee0d29..6cdd1c6730 100755 --- a/tests/test_cmd_progressive.sh +++ b/tests/test_cmd_progressive.sh @@ -22,14 +22,16 @@ source $(dirname "$0")/cmd_test_common.sh || exit # Input file paths. INPUT_Y4M="${TESTDATA_DIR}/kodim03_yuv420_8bpc.y4m" +INPUT_SMALL_Y4M="${TESTDATA_DIR}/kodim03_yuv420_8bpc_384x256.y4m" # Output file names. ENCODED_FILE="avif_test_cmd_encoded.avif" DECODED_FILE="avif_test_cmd_decoded.png" +INFO_FILE="avif_test_cmd_info.txt" # Cleanup cleanup() { pushd ${TMP_DIR} - rm -- "${ENCODED_FILE}" "${DECODED_FILE}" + rm -- "${ENCODED_FILE}" "${DECODED_FILE}" "${INFO_FILE}" popd } trap cleanup EXIT @@ -45,6 +47,18 @@ pushd ${TMP_DIR} "${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE}" "${AVIFDEC}" --progressive "${ENCODED_FILE}" "${DECODED_FILE}" + echo "Testing manual layered encoding with rendered-size override and mixed input sizes" + "${AVIFENC}" -s 8 --layered --render-size 768x512 \ + "${INPUT_SMALL_Y4M}" "${INPUT_Y4M}" -o "${ENCODED_FILE}" + "${AVIFDEC}" --info "${ENCODED_FILE}" > "${INFO_FILE}" + grep -F "[768x512]" "${INFO_FILE}" + "${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE}" + "${AVIFDEC}" --progressive "${ENCODED_FILE}" "${DECODED_FILE}" + + echo "Testing rendered-size override smaller than an input layer" + "${AVIFENC}" -s 8 --layered --render-size 384x256 \ + "${INPUT_SMALL_Y4M}" "${INPUT_Y4M}" -o "${ENCODED_FILE}" && exit 1 + # libavif relies on libyuv to do scaling echo "Testing layered encoding with frame scaling" if avifenc -V | grep -o "libyuv : available" --quiet; then From f3e8b2e3e4f0678003a18588957c668d4130e74b Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Tue, 24 Mar 2026 17:45:22 +0800 Subject: [PATCH 2/8] Add layered image fuzzer --- tests/CMakeLists.txt | 1 + tests/gtest/avif_fuzztest_enc_dec_layered.cc | 224 +++++++++++++++++++ tests/gtest/avif_fuzztest_helpers.cc | 92 ++++++++ tests/gtest/avif_fuzztest_helpers.h | 120 ++++++++++ 4 files changed, 437 insertions(+) create mode 100644 tests/gtest/avif_fuzztest_enc_dec_layered.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 96ca5d74d6..8a3dd797a9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -220,6 +220,7 @@ if(AVIF_FUZZTEST) add_avif_fuzztest(avif_fuzztest_dec_incr gtest/avifincrtest_helpers.cc) add_avif_fuzztest(avif_fuzztest_enc_dec) add_avif_fuzztest(avif_fuzztest_enc_dec_anim) + add_avif_fuzztest(avif_fuzztest_enc_dec_layered) add_avif_fuzztest(avif_fuzztest_enc_dec_incr gtest/avifincrtest_helpers.cc) add_avif_fuzztest(avif_fuzztest_properties) add_avif_fuzztest(avif_fuzztest_read_image) diff --git a/tests/gtest/avif_fuzztest_enc_dec_layered.cc b/tests/gtest/avif_fuzztest_enc_dec_layered.cc new file mode 100644 index 0000000000..ed2f3fd1c3 --- /dev/null +++ b/tests/gtest/avif_fuzztest_enc_dec_layered.cc @@ -0,0 +1,224 @@ +// Copyright 2026 Google LLC +// SPDX-License-Identifier: BSD-2-Clause + +#include +#include +#include +#include + +#include "avif/avif.h" +#include "avif_fuzztest_helpers.h" +#include "aviftest_helpers.h" +#include "fuzztest/fuzztest.h" +#include "gtest/gtest.h" + +namespace avif { +namespace testutil { +namespace { + +constexpr size_t kLayerCount = kMaxNumLayers; + +struct UniformScale { + int32_t n; + int32_t d; +}; + +constexpr UniformScale kNoScale = {1, 1}; +constexpr size_t kRandomScalingModeCount = kLayerCount - 1; +static_assert(kRandomScalingModeCount == 3); +constexpr std::array kSupportedScalingModes = { + UniformScale{1, 8}, UniformScale{1, 4}, UniformScale{1, 2}, + UniformScale{3, 5}, UniformScale{3, 4}, UniformScale{4, 5}}; +constexpr avifScalingMode kNoScalingMode = {{kNoScale.n, kNoScale.d}, + {kNoScale.n, kNoScale.d}}; +constexpr std::array kNoScalingModes = { + kNoScalingMode, kNoScalingMode, kNoScalingMode, kNoScalingMode}; + +constexpr avifScalingMode MakeScalingMode(UniformScale horizontal, + UniformScale vertical) { + return avifScalingMode{{horizontal.n, horizontal.d}, + {vertical.n, vertical.d}}; +} + +constexpr size_t BinomialCoefficient(size_t n, size_t k) { + if (k > n) { + return 0; + } + if (k > (n - k)) { + k = n - k; + } + size_t coefficient = 1; + for (size_t i = 1; i <= k; ++i) { + coefficient = (coefficient * (n - k + i)) / i; + } + return coefficient; +} + +constexpr size_t CombinationCountWithRepetition(size_t value_count, + size_t pick_count) { + return BinomialCoefficient(value_count + pick_count - 1, pick_count); +} + +constexpr size_t kScalingModeCombinationCount = CombinationCountWithRepetition( + kSupportedScalingModes.size(), kRandomScalingModeCount); + +std::array GetScalingModeCombination( + size_t combination_index) { + std::array combination = {}; + size_t remaining_index = combination_index; + size_t next_candidate = 0; + for (size_t slot = 0; slot < combination.size(); ++slot) { + const size_t remaining_slots = combination.size() - slot - 1; + for (size_t candidate = next_candidate; + candidate < kSupportedScalingModes.size(); ++candidate) { + const size_t combinations_with_candidate = CombinationCountWithRepetition( + kSupportedScalingModes.size() - candidate, remaining_slots); + if (remaining_index < combinations_with_candidate) { + combination[slot] = kSupportedScalingModes[candidate]; + next_candidate = candidate; + break; + } + remaining_index -= combinations_with_candidate; + } + } + return combination; +} + +std::array MakeRandomScalingModes( + int horizontal_combination_index, int vertical_combination_index) { + const auto horizontal_combination = + GetScalingModeCombination(horizontal_combination_index); + const auto vertical_combination = + GetScalingModeCombination(vertical_combination_index); + return std::array{ + MakeScalingMode(horizontal_combination[0], vertical_combination[0]), + MakeScalingMode(horizontal_combination[1], vertical_combination[1]), + MakeScalingMode(horizontal_combination[2], vertical_combination[2]), + kNoScalingMode}; +} + +inline auto ArbitraryScalingModes() { + constexpr int kMaxCombinationIndex = + static_cast(kScalingModeCombinationCount - 1); + return fuzztest::Map(MakeRandomScalingModes, + fuzztest::InRange(0, kMaxCombinationIndex), + fuzztest::InRange(0, kMaxCombinationIndex)); +} + +void EncodeDecodeLayered( + std::vector layers, + const std::array& scaling_modes, + uint32_t expected_width, uint32_t expected_height, + bool use_rendered_size_override, EncoderPtr encoder, DecoderPtr decoder) { + ASSERT_EQ(layers.size(), kLayerCount); + ASSERT_NE(encoder, nullptr); + ASSERT_NE(decoder, nullptr); + + if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) == + nullptr) { + return; + } + if (avifLibYUVVersion() == 0) { + return; + } + + const avifImage* const reference = layers.front().get(); + ASSERT_NE(reference, nullptr); + + encoder->codecChoice = AVIF_CODEC_CHOICE_AOM; + encoder->extraLayerCount = static_cast(layers.size() - 1); + if (use_rendered_size_override) { + encoder->width = expected_width; + encoder->height = expected_height; + } + + for (size_t i = 0; i < layers.size(); ++i) { + ASSERT_NE(layers[i].get(), nullptr); + encoder->scalingMode = scaling_modes[i]; + const avifResult result = avifEncoderAddImage( + encoder.get(), layers[i].get(), + /*durationInTimescales=*/1, AVIF_ADD_IMAGE_FLAG_NONE); + ASSERT_EQ(result, AVIF_RESULT_OK) + << " layer " << i << ": " << avifResultToString(result) << ": " + << encoder->diag.error; + } + + AvifRwData encoded_data; + avifResult result = avifEncoderFinish(encoder.get(), &encoded_data); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << ": " << encoder->diag.error; + + result = avifDecoderSetIOMemory(decoder.get(), encoded_data.data, + encoded_data.size); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << ": " << decoder->diag.error; + + result = avifDecoderParse(decoder.get()); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << ": " << decoder->diag.error; + + const int num_decodes = + decoder->allowProgressive ? static_cast(layers.size()) : 1; + if (decoder->allowProgressive) { + EXPECT_EQ(decoder->progressiveState, AVIF_PROGRESSIVE_STATE_ACTIVE); + ASSERT_EQ(decoder->imageCount, static_cast(layers.size())); + } else { + EXPECT_EQ(decoder->progressiveState, AVIF_PROGRESSIVE_STATE_AVAILABLE); + ASSERT_EQ(decoder->imageCount, 1); + } + + for (int i = 0; i < num_decodes; ++i) { + result = avifDecoderNextImage(decoder.get()); + ASSERT_EQ(result, AVIF_RESULT_OK) + << " layer " << i << ": " << avifResultToString(result) << ": " + << decoder->diag.error; + EXPECT_EQ(decoder->image->width, expected_width); + EXPECT_EQ(decoder->image->height, expected_height); + EXPECT_EQ(decoder->image->depth, reference->depth); + EXPECT_EQ(decoder->image->yuvFormat, reference->yuvFormat); + } + result = avifDecoderNextImage(decoder.get()); + ASSERT_EQ(result, AVIF_RESULT_NO_IMAGES_REMAINING) + << avifResultToString(result) << ": " << decoder->diag.error; +} + +void EncodeDecodeDimensionChange( + std::vector layers, + const std::array& scaling_modes, + EncoderPtr encoder, DecoderPtr decoder) { + ASSERT_EQ(layers.size(), kLayerCount); + ASSERT_NE(layers.back().get(), nullptr); + const uint32_t expected_width = layers.back()->width; + const uint32_t expected_height = layers.back()->height; + EncodeDecodeLayered(std::move(layers), scaling_modes, + /*expected_width=*/expected_width, + /*expected_height=*/expected_height, + /*use_rendered_size_override=*/false, std::move(encoder), + std::move(decoder)); +} + +void EncodeDecodeDimensionChangeExternal(std::vector layers, + EncoderPtr encoder, + DecoderPtr decoder) { + ASSERT_EQ(layers.size(), kLayerCount); + ASSERT_NE(layers.back().get(), nullptr); + const uint32_t expected_width = layers.back()->width; + const uint32_t expected_height = layers.back()->height; + EncodeDecodeLayered(std::move(layers), kNoScalingModes, + /*expected_width=*/expected_width, + /*expected_height=*/expected_height, + /*use_rendered_size_override=*/true, std::move(encoder), + std::move(decoder)); +} + +FUZZ_TEST(LayeredEncodeDecodeAvifFuzzTest, EncodeDecodeDimensionChange) + .WithDomains(ArbitraryAvifLayered(), ArbitraryScalingModes(), + ArbitraryAvifEncoder(), ArbitraryAvifDecoder()); + +FUZZ_TEST(LayeredEncodeDecodeAvifFuzzTest, EncodeDecodeDimensionChangeExternal) + .WithDomains(ArbitraryAvifLayeredRandDim(), ArbitraryAvifEncoder(), + ArbitraryAvifDecoder()); + +} // namespace +} // namespace testutil +} // namespace avif diff --git a/tests/gtest/avif_fuzztest_helpers.cc b/tests/gtest/avif_fuzztest_helpers.cc index cfb0f7b902..0d2728af61 100644 --- a/tests/gtest/avif_fuzztest_helpers.cc +++ b/tests/gtest/avif_fuzztest_helpers.cc @@ -4,6 +4,7 @@ #include "avif_fuzztest_helpers.h" #include +#include #include #include #include @@ -22,6 +23,27 @@ namespace { //------------------------------------------------------------------------------ +constexpr size_t kNumLayeredRandDimSeeds = 2 * (kMaxNumLayers - 1); + +template +std::array, kMaxNumLayers> GetAvifLayeredRandDims( + size_t render_width, size_t render_height, const Sample* seeds) { + assert(render_width > 1); + assert(render_height > 1); + std::array, kMaxNumLayers> sizes = {}; + for (size_t i = 0; i + 1 < kMaxNumLayers; ++i) { + const size_t width = + 1 + (static_cast(seeds[2 * i]) % (render_width - 1)); + const size_t height = + 1 + (static_cast(seeds[2 * i + 1]) % (render_height - 1)); + assert(width < render_width); + assert(height < render_height); + sizes[i] = {width, height}; + } + sizes.back() = {render_width, render_height}; + return sizes; +} + ImagePtr CreateAvifImage(size_t width, size_t height, int depth, avifPixelFormat pixel_format, bool has_alpha, const uint8_t* samples) { @@ -104,6 +126,76 @@ std::vector CreateAvifAnim16b(size_t num_frames, size_t width, return frames; } +std::vector CreateAvifLayered8b(size_t width, size_t height, + avifPixelFormat pixel_format, + bool has_alpha, + const std::vector& samples) { + std::vector layers = CreateAvifAnim8b( + kMaxNumLayers, width, height, pixel_format, has_alpha, samples); + return layers; +} + +std::vector CreateAvifLayered16b( + size_t width, size_t height, int depth, avifPixelFormat pixel_format, + bool has_alpha, const std::vector& samples) { + std::vector layers = CreateAvifAnim16b( + kMaxNumLayers, width, height, depth, pixel_format, has_alpha, samples); + return layers; +} + +size_t GetNumSamplesLayeredRandDim(size_t render_width, size_t render_height, + avifPixelFormat pixel_format, + bool has_alpha) { + return kNumLayeredRandDimSeeds + GetNumSamples(kMaxNumLayers, render_width, + render_height, pixel_format, + has_alpha); +} + +std::vector CreateAvifLayeredRandDim8b( + size_t render_width, size_t render_height, avifPixelFormat pixel_format, + bool has_alpha, const std::vector& samples) { + assert(samples.size() >= kNumLayeredRandDimSeeds); + const auto dims = + GetAvifLayeredRandDims(render_width, render_height, samples.data()); + std::vector layers; + layers.reserve(kMaxNumLayers); + + size_t offset = kNumLayeredRandDimSeeds; + for (const auto& [width, height] : dims) { + const size_t num_samples = GetNumSamples( + /*num_frames=*/1, width, height, pixel_format, has_alpha); + layers.push_back(CreateAvifImage8b( + width, height, pixel_format, has_alpha, + std::vector(samples.begin() + offset, + samples.begin() + offset + num_samples))); + offset += num_samples; + } + return layers; +} + +std::vector CreateAvifLayeredRandDim16b( + size_t render_width, size_t render_height, int depth, + avifPixelFormat pixel_format, bool has_alpha, + const std::vector& samples) { + assert(samples.size() >= kNumLayeredRandDimSeeds); + const auto dims = + GetAvifLayeredRandDims(render_width, render_height, samples.data()); + std::vector layers; + layers.reserve(kMaxNumLayers); + + size_t offset = kNumLayeredRandDimSeeds; + for (const auto& [width, height] : dims) { + const size_t num_samples = GetNumSamples( + /*num_frames=*/1, width, height, pixel_format, has_alpha); + layers.push_back(CreateAvifImage16b( + width, height, depth, pixel_format, has_alpha, + std::vector(samples.begin() + offset, + samples.begin() + offset + num_samples))); + offset += num_samples; + } + return layers; +} + EncoderPtr CreateAvifEncoder(avifCodecChoice codec_choice, int max_threads, int min_quantizer, int max_quantizer, int min_quantizer_alpha, int max_quantizer_alpha, diff --git a/tests/gtest/avif_fuzztest_helpers.h b/tests/gtest/avif_fuzztest_helpers.h index 14dda45825..4ddbe7add9 100644 --- a/tests/gtest/avif_fuzztest_helpers.h +++ b/tests/gtest/avif_fuzztest_helpers.h @@ -58,6 +58,20 @@ std::vector CreateAvifAnim16b(size_t num_frames, size_t width, avifPixelFormat pixel_format, bool has_alpha, const std::vector& samples); +std::vector CreateAvifLayered8b(size_t width, size_t height, + avifPixelFormat pixel_format, + bool has_alpha, + const std::vector& samples); +std::vector CreateAvifLayered16b( + size_t width, size_t height, int depth, avifPixelFormat pixel_format, + bool has_alpha, const std::vector& samples); +std::vector CreateAvifLayeredRandDim8b( + size_t render_width, size_t render_height, avifPixelFormat pixel_format, + bool has_alpha, const std::vector& samples); +std::vector CreateAvifLayeredRandDim16b( + size_t render_width, size_t render_height, int depth, + avifPixelFormat pixel_format, bool has_alpha, + const std::vector& samples); EncoderPtr CreateAvifEncoder(avifCodecChoice codec_choice, int max_threads, int min_quantizer, int max_quantizer, int min_quantizer_alpha, int max_quantizer_alpha, @@ -85,9 +99,14 @@ inline constexpr size_t kMaxNumFramesSquareRoot = 2; // Do not generate animations with more than this number of frames. inline constexpr size_t kMaxNumFrames = kMaxNumFramesSquareRoot * kMaxNumFramesSquareRoot; +// Do not generate layered images with more than this number of layers. +inline constexpr size_t kMaxNumLayers = AVIF_MAX_AV1_LAYER_COUNT; size_t GetNumSamples(size_t num_frames, size_t width, size_t height, avifPixelFormat pixel_format, bool has_alpha); +size_t GetNumSamplesLayeredRandDim(size_t render_width, size_t render_height, + avifPixelFormat pixel_format, + bool has_alpha); // To avoid using fuzztest::internal, the return type of the functions below is // auto. @@ -187,6 +206,107 @@ inline auto ArbitraryAvifAnim() { return fuzztest::OneOf(ArbitraryAvifAnim8b(), ArbitraryAvifAnim16b()); } +// Layered avifImage generator type: fixed number of layers, same width/height, +// pixel format and 8-bit samples. +inline auto ArbitraryAvifLayered8b() { + constexpr uint16_t kMinLayerDimension = 8; + constexpr uint16_t kMaxLayerDimension = + kMaxDimension / kMaxNumFramesSquareRoot; + return fuzztest::FlatMap( + [](size_t width, size_t height, avifPixelFormat pixel_format, + bool has_alpha) { + return fuzztest::Map( + CreateAvifLayered8b, fuzztest::Just(width), fuzztest::Just(height), + fuzztest::Just(pixel_format), fuzztest::Just(has_alpha), + fuzztest::Arbitrary>().WithSize(GetNumSamples( + kMaxNumLayers, width, height, pixel_format, has_alpha))); + }, + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + ArbitraryPixelFormat(), fuzztest::Arbitrary()); +} + +// Layered avifImage generator type: fixed number of layers, same width/height, +// depth, pixel format and 16-bit samples. +inline auto ArbitraryAvifLayered16b() { + constexpr uint16_t kMinLayerDimension = 8; + constexpr uint16_t kMaxLayerDimension = + kMaxDimension / kMaxNumFramesSquareRoot; + return fuzztest::FlatMap( + [](size_t width, size_t height, int depth, avifPixelFormat pixel_format, + bool has_alpha) { + return fuzztest::Map( + CreateAvifLayered16b, fuzztest::Just(width), fuzztest::Just(height), + fuzztest::Just(depth), fuzztest::Just(pixel_format), + fuzztest::Just(has_alpha), + fuzztest::ContainerOf>( + fuzztest::InRange(0, (1 << depth) - 1)) + .WithSize(GetNumSamples(kMaxNumLayers, width, height, + pixel_format, has_alpha))); + }, + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + fuzztest::ElementOf({10, 12}), ArbitraryPixelFormat(), + fuzztest::Arbitrary()); +} + +// Generator for an arbitrary layered still image with same-sized layers. +inline auto ArbitraryAvifLayered() { + return fuzztest::OneOf(ArbitraryAvifLayered8b(), ArbitraryAvifLayered16b()); +} + +// Layered avifImage generator type: fixed number of layers, rendered size, +// pixel format and 8-bit samples. +inline auto ArbitraryAvifLayeredRandDim8b() { + constexpr uint16_t kMinLayerDimension = 8; + constexpr uint16_t kMaxLayerDimension = + kMaxDimension / kMaxNumFramesSquareRoot; + return fuzztest::FlatMap( + [](size_t render_width, size_t render_height, + avifPixelFormat pixel_format, bool has_alpha) { + return fuzztest::Map( + CreateAvifLayeredRandDim8b, fuzztest::Just(render_width), + fuzztest::Just(render_height), fuzztest::Just(pixel_format), + fuzztest::Just(has_alpha), + fuzztest::Arbitrary>().WithSize( + GetNumSamplesLayeredRandDim(render_width, render_height, + pixel_format, has_alpha))); + }, + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + ArbitraryPixelFormat(), fuzztest::Arbitrary()); +} + +// Layered avifImage generator type: fixed number of layers, rendered size, +// depth, pixel format and 16-bit samples. +inline auto ArbitraryAvifLayeredRandDim16b() { + constexpr uint16_t kMinLayerDimension = 8; + constexpr uint16_t kMaxLayerDimension = + kMaxDimension / kMaxNumFramesSquareRoot; + return fuzztest::FlatMap( + [](size_t render_width, size_t render_height, int depth, + avifPixelFormat pixel_format, bool has_alpha) { + return fuzztest::Map( + CreateAvifLayeredRandDim16b, fuzztest::Just(render_width), + fuzztest::Just(render_height), fuzztest::Just(depth), + fuzztest::Just(pixel_format), fuzztest::Just(has_alpha), + fuzztest::ContainerOf>( + fuzztest::InRange(0, (1 << depth) - 1)) + .WithSize(GetNumSamplesLayeredRandDim( + render_width, render_height, pixel_format, has_alpha))); + }, + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), + fuzztest::ElementOf({10, 12}), ArbitraryPixelFormat(), + fuzztest::Arbitrary()); +} + +// Generator for an arbitrary layered still image with rendered size override. +inline auto ArbitraryAvifLayeredRandDim() { + return fuzztest::OneOf(ArbitraryAvifLayeredRandDim8b(), + ArbitraryAvifLayeredRandDim16b()); +} + // Generates two signed fractions where the first one is smaller than or equal // to the second one. inline auto ArbitraryMinMaxSignedFraction() { From 25eb44bce3248d363bbd1f005acb6378314c3103 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Thu, 27 Aug 2026 21:59:55 +0800 Subject: [PATCH 3/8] code review --- src/codec_svt.c | 2 -- src/write.c | 38 ++++++++++++++------------------------ 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/codec_svt.c b/src/codec_svt.c index d5d1b88486..99dcfc6646 100644 --- a/src/codec_svt.c +++ b/src/codec_svt.c @@ -170,8 +170,6 @@ 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 diff --git a/src/write.c b/src/write.c index b8cd40fa77..87be0afd59 100644 --- a/src/write.c +++ b/src/write.c @@ -473,11 +473,8 @@ static avifBool avifEncoderUsesRenderedSizeOverride(const avifEncoder * encoder) static avifBool avifScalingModeIsNoScaling(const avifScalingMode * scalingMode) { - avifFraction horizontal = scalingMode->horizontal; - avifFraction vertical = scalingMode->vertical; - avifFractionSimplify(&horizontal); - avifFractionSimplify(&vertical); - return (horizontal.n == 1) && (horizontal.d == 1) && (vertical.n == 1) && (vertical.d == 1); + return (scalingMode->horizontal.n == scalingMode->horizontal.d) && + (scalingMode->vertical.n == scalingMode->vertical.d); } static avifBool avifImageHasEquivalentTransformProperties(const avifImage * lhs, const avifImage * rhs) @@ -2271,10 +2268,6 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { return AVIF_RESULT_INVALID_IMAGE_GRID; } - if (avifEncoderUsesRenderedSizeOverride(encoder)) { - avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with avifEncoderAddImageGrid()"); - return AVIF_RESULT_NOT_IMPLEMENTED; - } if (encoder->extraLayerCount == 0) { addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // image grids cannot be image sequences } @@ -3335,9 +3328,6 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) #endif // AVIF_ENABLE_EXPERIMENTAL_MINI const avifImage * imageMetadata = encoder->data->imageMetadata; - const uint32_t imageWidth = encoder->width ? encoder->width : imageMetadata->width; - const uint32_t imageHeight = encoder->height ? encoder->height : imageMetadata->height; - uint64_t now = (uint64_t)time(NULL); uint64_t modificationTime = (encoder->modificationTime != 0) ? encoder->modificationTime : now; uint64_t creationTime = (encoder->creationTime != 0) ? encoder->creationTime : modificationTime; @@ -3752,8 +3742,8 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // template int(16) volume = {if track_is_audio 0x0100 else 0}; AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // const unsigned int(16) reserved = 0; AVIF_CHECKRES(avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix))); // template int(32)[9] matrix= // { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; - AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageWidth << 16)); // unsigned int(32) width; - AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageHeight << 16)); // unsigned int(32) height; + AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->width << 16)); // unsigned int(32) width; + AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->height << 16)); // unsigned int(32) height; AVIF_CHECKRES(avifRWStreamFinishBox(&s, tkhd)); if (item->irefToID != 0) { @@ -3829,16 +3819,16 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); // unsigned int(32) entry_count; avifBoxMarker imageItem; AVIF_CHECKRES(avifRWStreamWriteBox(&s, encoder->data->imageItemType, AVIF_BOX_SIZE_TBD, &imageItem)); - AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6)); // const unsigned int(8)[6] reserved = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // unsigned int(16) data_reference_index; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // unsigned int(16) pre_defined = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // const unsigned int(16) reserved = 0; - AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3)); // unsigned int(32)[3] pre_defined = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageWidth)); // unsigned int(16) width; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageHeight)); // unsigned int(16) height; - AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) horizresolution - AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) vertresolution - AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); // const unsigned int(32) reserved = 0; + AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6)); // const unsigned int(8)[6] reserved = 0; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // unsigned int(16) data_reference_index; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // unsigned int(16) pre_defined = 0; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); // const unsigned int(16) reserved = 0; + AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3)); // unsigned int(32)[3] pre_defined = 0; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width)); // unsigned int(16) width; + AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height)); // unsigned int(16) height; + AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) horizresolution + AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); // template unsigned int(32) vertresolution + AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); // const unsigned int(32) reserved = 0; AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); // template unsigned int(16) frame_count = 1; AVIF_CHECKRES(avifRWStreamWriteChars(&s, "\012AOM Coding", 11)); // string[32] compressorname; AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 32 - 11)); // From ae4d14530c5cde83eb12883611d7813ef8ae1d67 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sun, 30 Aug 2026 14:06:41 +0800 Subject: [PATCH 4/8] render size -> display size rename --- apps/avifenc.c | 50 ++++++++++---------- include/avif/avif.h | 38 +++++++++++++-- src/codec_aom.c | 5 +- src/codec_avm.c | 2 +- src/codec_rav1e.c | 2 +- src/codec_svt.c | 2 +- src/write.c | 29 ++++++------ tests/gtest/avif_fuzztest_enc_dec_layered.cc | 8 ++-- tests/gtest/avif_fuzztest_helpers.cc | 30 ++++++------ tests/gtest/avif_fuzztest_helpers.h | 28 +++++------ tests/gtest/avifchangesettingtest.cc | 2 +- tests/test_cmd_progressive.sh | 8 ++-- 12 files changed, 117 insertions(+), 87 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index 2b8de51a61..b45094e9f8 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -244,7 +244,7 @@ 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(" --display-size WxH : Set the size at which the AVIF image is displayed, independent of the encoded pixel dimensions.\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"); @@ -458,7 +458,7 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, uint32_t clapVal return AVIF_TRUE; } -static avifBool avifSettingsUsesRenderedSizeOverride(const avifSettings * settings) +static avifBool avifSettingsUsesDisplaySizeOverride(const avifSettings * settings) { return (settings->width != 0) || (settings->height != 0); } @@ -469,14 +469,14 @@ static void avifSettingsGetEffectiveOutputDimensions(const avifSettings * settin *height = settings->height ? settings->height : image->height; } -static avifBool avifSettingsVerifyRenderedSizeBounds(const avifSettings * settings, const avifImage * image, const char * filename) +static avifBool avifSettingsVerifyDisplaySizeBounds(const avifSettings * settings, const avifImage * image, const char * filename) { - if (!avifSettingsUsesRenderedSizeOverride(settings)) { + if (!avifSettingsUsesDisplaySizeOverride(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", + "ERROR: Input image dimensions [%ux%u] exceed display size [%ux%u]: %s\n", image->width, image->height, settings->width, @@ -988,7 +988,7 @@ static avifBool avifEncodeRestOfImageSequence(avifEncoder * encoder, settings->inputFormat)) { goto cleanup; } - if (!avifSettingsVerifyRenderedSizeBounds(settings, nextImage, avifPrettyFilename(nextFile->filename))) { + if (!avifSettingsVerifyDisplaySizeBounds(settings, nextImage, avifPrettyFilename(nextFile->filename))) { goto cleanup; } if (!avifEncoderVerifyImageCompatibility(firstImage, @@ -1102,7 +1102,7 @@ static avifBool avifEncodeRestOfLayeredImage(avifEncoder * encoder, settings->inputFormat)) { goto cleanup; } - if (!avifSettingsVerifyRenderedSizeBounds(settings, nextImage, avifPrettyFilename(nextFile->filename))) { + if (!avifSettingsVerifyDisplaySizeBounds(settings, nextImage, avifPrettyFilename(nextFile->filename))) { goto cleanup; } // frameIter is NULL if y4m reached end, so single frame y4m is still supported. @@ -1116,7 +1116,7 @@ static avifBool avifEncodeRestOfLayeredImage(avifEncoder * encoder, nextImage, "layer", avifPrettyFilename(nextFile->filename), - avifSettingsUsesRenderedSizeOverride(settings))) { + avifSettingsUsesDisplaySizeOverride(settings))) { goto cleanup; } if (!avifEncodeUpdateEncoderSettings(encoder, nextSettings)) { @@ -1824,15 +1824,15 @@ int main(int argc, char * argv[]) goto cleanup; } settings.layered = AVIF_TRUE; - } else if (!strcmp(arg, "--render-size")) { - uint32_t renderSize[2] = { 0 }; + } else if (!strcmp(arg, "--display-size")) { + uint32_t displaySize[2] = { 0 }; NEXTARG(); - if (!parseU32List(renderSize, 2, arg, 'x') || (renderSize[0] == 0) || (renderSize[1] == 0)) { - fprintf(stderr, "ERROR: Invalid render size: %s\n", arg); + if (!parseU32List(displaySize, 2, arg, 'x') || (displaySize[0] == 0) || (displaySize[1] == 0)) { + fprintf(stderr, "ERROR: Invalid display size: %s\n", arg); goto cleanup; } - settings.width = renderSize[0]; - settings.height = renderSize[1]; + settings.width = displaySize[0]; + settings.height = displaySize[1]; } else if (!strcmp(arg, "--scaling-mode") || strpre(arg, "--scaling-mode:")) { avifOptionSuffixType type = parseOptionSuffix(arg, input.filesCount != 0); if (type == AVIF_OPTION_SUFFIX_INVALID) { @@ -2193,8 +2193,8 @@ 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"); + if (avifSettingsUsesDisplaySizeOverride(&settings) && ((settings.width == 0) || (settings.height == 0))) { + fprintf(stderr, "ERROR: --display-size must be specified as WxH.\n"); goto cleanup; } @@ -2225,12 +2225,12 @@ 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"); + if (avifSettingsUsesDisplaySizeOverride(&settings) && settings.gridDimsPresent) { + fprintf(stderr, "ERROR: --display-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"); + if (avifSettingsUsesDisplaySizeOverride(&settings) && (settings.layers == 1) && (input.filesCount > 1)) { + fprintf(stderr, "ERROR: --display-size is not supported with image sequences. Use --layered for multiple still inputs.\n"); goto cleanup; } @@ -2453,12 +2453,12 @@ int main(int argc, char * argv[]) goto cleanup; } - if (!avifSettingsVerifyRenderedSizeBounds(&settings, image, avifPrettyFilename(firstFile->filename))) { + if (!avifSettingsVerifyDisplaySizeBounds(&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"); + if (avifSettingsUsesDisplaySizeOverride(&settings) && (settings.layers == 1) && input.frameIter) { + fprintf(stderr, "ERROR: --display-size is not supported with image sequences.\n"); goto cleanup; } @@ -2724,8 +2724,8 @@ 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); + if (avifSettingsUsesDisplaySizeOverride(&settings)) { + printf(" * Display Size : %ux%u\n", settings.width, settings.height); } avifEncodedByteSizes byteSizes = { 0, 0, 0 }; diff --git a/include/avif/avif.h b/include/avif/avif.h index 6c57323d75..231b85501c 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -821,6 +821,11 @@ typedef struct avifImage // To encode any of these boxes, set the values in the associated box, then enable the flag in // transformFlags. On decode, only honor the values in boxes with the associated transform flag set. // These also apply to gainMap->image, if any. + // + // When encoding with avifEncoder.width/height set (see its comment), these values are interpreted + // relative to that overridden display size rather than to this avifImage's own width/height. + // No special handling is needed during decode: the decoded avifImage is automatically scaled to + // the display size, so these values are relative to its width/height as usual. avifTransformFlags transformFlags; avifPixelAspectRatioBox pasp; avifCleanApertureBox clap; @@ -1630,10 +1635,35 @@ 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. + // Overrides the image's display size (the width and height AVIF declares this image is meant + // to be shown at) independent of the pixel dimensions actually encoded. Any image passed to + // avifEncoderAddImage()/avifEncoderAddImageGrid() that is encoded smaller than the display size + // is upscaled to it on decode. + // + // Defaults to 0 (disabled). When 0, the display size is the size of the first added image. + // When set, both width and height must be nonzero, and neither can be smaller than the coded + // dimensions of any image passed to avifEncoderAddImage()/avifEncoderAddImageGrid(). + // + // Not supported together with image sequences, image grids, scalingMode, sample + // transforms, or gain maps. Cannot be changed once encoding has started (i.e. after the first + // call to avifEncoderAddImage()/avifEncoderAddImageGrid()). + // + // The main use case is a layered image (see extraLayerCount above) where earlier layers' + // pixel data is already available at a smaller size, so encoding them directly at that size + // avoids the wasted round trip of upscaling to the display size only for the encoder to scale + // it back down, and allows scale ratios that scalingMode cannot express for the encoder in use. + // + // avifImage properties that are relative to image dimensions (clap, irot, imir, pasp; see the + // "Transformations" comment above) are interpreted relative to the display size when set, rather + // than to the coded size of whichever layer they came from. + // + // Implementation note: the display size is stored as the ispe box, the dimensions an AVIF + // decoder is required to honor; a coded image smaller than it is upscaled by the decoder to + // match. Separately, an AV1 encoder session declares a maximum frame size once, up front, and + // every later frame must fit within it; by default that maximum is simply the size of the first + // frame encoded. Setting width/height also declares that AV1-level maximum explicitly, in + // addition to the ispe box, so that later, larger layers stay within it. This is why it must be + // at least as large as every layer's coded size. uint32_t width; uint32_t height; } avifEncoder; diff --git a/src/codec_aom.c b/src/codec_aom.c index 7e305ba77e..9f23c62096 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -897,8 +897,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // 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. + // Still picture header requires frame size to match + // max_frame_width and max_frame_height, + // so we can't use it if frame can have a different size. cfg->g_limit = 1; } if (useAllIntra) { diff --git a/src/codec_avm.c b/src/codec_avm.c index 01fe18a842..adf2699021 100644 --- a/src/codec_avm.c +++ b/src/codec_avm.c @@ -454,7 +454,7 @@ static avifResult avmCodecEncodeImage(avifCodec * codec, encoderChanges &= ~AVIF_ENCODER_CHANGE_SCALING_MODE; if (encoder->width || encoder->height) { - avifDiagnosticsPrintf(codec->diag, "AVM does not support rendered-size override"); + avifDiagnosticsPrintf(codec->diag, "AVM does not support display-size override"); return AVIF_RESULT_NOT_IMPLEMENTED; } diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 1a24fa5443..4b41bfeb52 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c @@ -92,7 +92,7 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec, // 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"); + avifDiagnosticsPrintf(codec->diag, "rav1e does not support display-size override"); return AVIF_RESULT_NOT_IMPLEMENTED; } diff --git a/src/codec_svt.c b/src/codec_svt.c index 99dcfc6646..70be13fa14 100644 --- a/src/codec_svt.c +++ b/src/codec_svt.c @@ -84,7 +84,7 @@ static avifResult svtCodecEncodeImage(avifCodec * codec, (void)disableLaggedOutput; if (encoder->width || encoder->height) { - avifDiagnosticsPrintf(codec->diag, "SVT-AV1 does not support rendered-size override"); + avifDiagnosticsPrintf(codec->diag, "SVT-AV1 does not support display-size override"); return AVIF_RESULT_NOT_IMPLEMENTED; } diff --git a/src/write.c b/src/write.c index 87be0afd59..59095ac5f5 100644 --- a/src/write.c +++ b/src/write.c @@ -466,15 +466,14 @@ static avifResult avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, static const avifScalingMode noScaling = { { 1, 1 }, { 1, 1 } }; -static avifBool avifEncoderUsesRenderedSizeOverride(const avifEncoder * encoder) +static avifBool avifEncoderUsesDisplaySizeOverride(const avifEncoder * encoder) { return (encoder->width != 0) || (encoder->height != 0); } static avifBool avifScalingModeIsNoScaling(const avifScalingMode * scalingMode) { - return (scalingMode->horizontal.n == scalingMode->horizontal.d) && - (scalingMode->vertical.n == scalingMode->vertical.d); + return (scalingMode->horizontal.n == scalingMode->horizontal.d) && (scalingMode->vertical.n == scalingMode->vertical.d); } static avifBool avifImageHasEquivalentTransformProperties(const avifImage * lhs, const avifImage * rhs) @@ -1599,46 +1598,46 @@ static avifCodecType avifEncoderGetCodecType(const avifEncoder * encoder) return avifCodecTypeFromChoice(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); } -static avifResult avifEncoderValidateRenderedSizeOverride(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) +static avifResult avifEncoderValidateDisplaySizeOverride(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) { - if (!avifEncoderUsesRenderedSizeOverride(encoder)) { + if (!avifEncoderUsesDisplaySizeOverride(encoder)) { return AVIF_RESULT_OK; } if ((encoder->width == 0) || (encoder->height == 0)) { avifDiagnosticsPrintf(&encoder->diag, - "rendered-size override requires both avifEncoder.width and avifEncoder.height to be non-zero"); + "display-size override requires both avifEncoder.width and avifEncoder.height to be non-zero"); return AVIF_RESULT_INVALID_ARGUMENT; } if ((gridCols > 1) || (gridRows > 1)) { - avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with grid images"); + avifDiagnosticsPrintf(&encoder->diag, "display-size override is not supported with grid images"); return AVIF_RESULT_NOT_IMPLEMENTED; } if (!avifScalingModeIsNoScaling(&encoder->scalingMode)) { - avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with encoder->scalingMode"); + avifDiagnosticsPrintf(&encoder->diag, "display-size override is not supported with encoder->scalingMode"); return AVIF_RESULT_NOT_IMPLEMENTED; } if (encoder->sampleTransformRecipe != AVIF_SAMPLE_TRANSFORM_NONE) { - avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with sample transforms"); + avifDiagnosticsPrintf(&encoder->diag, "display-size override is not supported with sample transforms"); return AVIF_RESULT_NOT_IMPLEMENTED; } if (firstCell->gainMap && firstCell->gainMap->image) { - avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported with gain maps"); + avifDiagnosticsPrintf(&encoder->diag, "display-size override is not supported with gain maps"); return AVIF_RESULT_NOT_IMPLEMENTED; } if ((encoder->data->items.count > 0) && (encoder->extraLayerCount == 0)) { - avifDiagnosticsPrintf(&encoder->diag, "rendered-size override is not supported for image sequences"); + avifDiagnosticsPrintf(&encoder->diag, "display-size override is not supported for image sequences"); return AVIF_RESULT_NOT_IMPLEMENTED; } if ((encoder->width < firstCell->width) || (encoder->height < firstCell->height)) { avifDiagnosticsPrintf(&encoder->diag, - "rendered-size override %ux%u must be at least the coded image size %ux%u", + "display-size override %ux%u must be at least the coded image size %ux%u", encoder->width, encoder->height, firstCell->width, @@ -1655,7 +1654,7 @@ static avifResult avifEncoderValidateRenderedSizeOverride(avifEncoder * encoder, if ((encoder->data->items.count > 0) && (encoder->extraLayerCount > 0) && !avifImageHasEquivalentTransformProperties(firstCell, encoder->data->imageMetadata)) { - avifDiagnosticsPrintf(&encoder->diag, "rendered-size override requires 'pasp', 'clap', 'irot' and 'imir' to match across layers"); + avifDiagnosticsPrintf(&encoder->diag, "display-size override requires 'pasp', 'clap', 'irot' and 'imir' to match across layers"); return AVIF_RESULT_INCOMPATIBLE_IMAGE; } @@ -1844,7 +1843,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return AVIF_RESULT_NO_CONTENT; } - AVIF_CHECKRES(avifEncoderValidateRenderedSizeOverride(encoder, gridCols, gridRows, firstCell)); + AVIF_CHECKRES(avifEncoderValidateDisplaySizeOverride(encoder, gridCols, gridRows, firstCell)); AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_FALSE, &encoder->diag)); @@ -1976,7 +1975,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, if (encoder->data->items.count == 0) { // Make a copy of the first image's metadata (sans pixels) for future writing/validation AVIF_CHECKRES(avifImageCopy(encoder->data->imageMetadata, firstCell, 0)); - if (avifEncoderUsesRenderedSizeOverride(encoder)) { + if (avifEncoderUsesDisplaySizeOverride(encoder)) { encoder->data->imageMetadata->width = encoder->width; encoder->data->imageMetadata->height = encoder->height; } diff --git a/tests/gtest/avif_fuzztest_enc_dec_layered.cc b/tests/gtest/avif_fuzztest_enc_dec_layered.cc index ed2f3fd1c3..e1d108ad71 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_layered.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_layered.cc @@ -109,7 +109,7 @@ void EncodeDecodeLayered( std::vector layers, const std::array& scaling_modes, uint32_t expected_width, uint32_t expected_height, - bool use_rendered_size_override, EncoderPtr encoder, DecoderPtr decoder) { + bool use_display_size_override, EncoderPtr encoder, DecoderPtr decoder) { ASSERT_EQ(layers.size(), kLayerCount); ASSERT_NE(encoder, nullptr); ASSERT_NE(decoder, nullptr); @@ -127,7 +127,7 @@ void EncodeDecodeLayered( encoder->codecChoice = AVIF_CODEC_CHOICE_AOM; encoder->extraLayerCount = static_cast(layers.size() - 1); - if (use_rendered_size_override) { + if (use_display_size_override) { encoder->width = expected_width; encoder->height = expected_height; } @@ -193,7 +193,7 @@ void EncodeDecodeDimensionChange( EncodeDecodeLayered(std::move(layers), scaling_modes, /*expected_width=*/expected_width, /*expected_height=*/expected_height, - /*use_rendered_size_override=*/false, std::move(encoder), + /*use_display_size_override=*/false, std::move(encoder), std::move(decoder)); } @@ -207,7 +207,7 @@ void EncodeDecodeDimensionChangeExternal(std::vector layers, EncodeDecodeLayered(std::move(layers), kNoScalingModes, /*expected_width=*/expected_width, /*expected_height=*/expected_height, - /*use_rendered_size_override=*/true, std::move(encoder), + /*use_display_size_override=*/true, std::move(encoder), std::move(decoder)); } diff --git a/tests/gtest/avif_fuzztest_helpers.cc b/tests/gtest/avif_fuzztest_helpers.cc index 0d2728af61..10fc0dd9e3 100644 --- a/tests/gtest/avif_fuzztest_helpers.cc +++ b/tests/gtest/avif_fuzztest_helpers.cc @@ -27,20 +27,20 @@ constexpr size_t kNumLayeredRandDimSeeds = 2 * (kMaxNumLayers - 1); template std::array, kMaxNumLayers> GetAvifLayeredRandDims( - size_t render_width, size_t render_height, const Sample* seeds) { - assert(render_width > 1); - assert(render_height > 1); + size_t display_width, size_t display_height, const Sample* seeds) { + assert(display_width > 1); + assert(display_height > 1); std::array, kMaxNumLayers> sizes = {}; for (size_t i = 0; i + 1 < kMaxNumLayers; ++i) { const size_t width = - 1 + (static_cast(seeds[2 * i]) % (render_width - 1)); + 1 + (static_cast(seeds[2 * i]) % (display_width - 1)); const size_t height = - 1 + (static_cast(seeds[2 * i + 1]) % (render_height - 1)); - assert(width < render_width); - assert(height < render_height); + 1 + (static_cast(seeds[2 * i + 1]) % (display_height - 1)); + assert(width < display_width); + assert(height < display_height); sizes[i] = {width, height}; } - sizes.back() = {render_width, render_height}; + sizes.back() = {display_width, display_height}; return sizes; } @@ -143,20 +143,20 @@ std::vector CreateAvifLayered16b( return layers; } -size_t GetNumSamplesLayeredRandDim(size_t render_width, size_t render_height, +size_t GetNumSamplesLayeredRandDim(size_t display_width, size_t display_height, avifPixelFormat pixel_format, bool has_alpha) { - return kNumLayeredRandDimSeeds + GetNumSamples(kMaxNumLayers, render_width, - render_height, pixel_format, + return kNumLayeredRandDimSeeds + GetNumSamples(kMaxNumLayers, display_width, + display_height, pixel_format, has_alpha); } std::vector CreateAvifLayeredRandDim8b( - size_t render_width, size_t render_height, avifPixelFormat pixel_format, + size_t display_width, size_t display_height, avifPixelFormat pixel_format, bool has_alpha, const std::vector& samples) { assert(samples.size() >= kNumLayeredRandDimSeeds); const auto dims = - GetAvifLayeredRandDims(render_width, render_height, samples.data()); + GetAvifLayeredRandDims(display_width, display_height, samples.data()); std::vector layers; layers.reserve(kMaxNumLayers); @@ -174,12 +174,12 @@ std::vector CreateAvifLayeredRandDim8b( } std::vector CreateAvifLayeredRandDim16b( - size_t render_width, size_t render_height, int depth, + size_t display_width, size_t display_height, int depth, avifPixelFormat pixel_format, bool has_alpha, const std::vector& samples) { assert(samples.size() >= kNumLayeredRandDimSeeds); const auto dims = - GetAvifLayeredRandDims(render_width, render_height, samples.data()); + GetAvifLayeredRandDims(display_width, display_height, samples.data()); std::vector layers; layers.reserve(kMaxNumLayers); diff --git a/tests/gtest/avif_fuzztest_helpers.h b/tests/gtest/avif_fuzztest_helpers.h index 4ddbe7add9..ab9a9ba15e 100644 --- a/tests/gtest/avif_fuzztest_helpers.h +++ b/tests/gtest/avif_fuzztest_helpers.h @@ -66,10 +66,10 @@ std::vector CreateAvifLayered16b( size_t width, size_t height, int depth, avifPixelFormat pixel_format, bool has_alpha, const std::vector& samples); std::vector CreateAvifLayeredRandDim8b( - size_t render_width, size_t render_height, avifPixelFormat pixel_format, + size_t display_width, size_t display_height, avifPixelFormat pixel_format, bool has_alpha, const std::vector& samples); std::vector CreateAvifLayeredRandDim16b( - size_t render_width, size_t render_height, int depth, + size_t display_width, size_t display_height, int depth, avifPixelFormat pixel_format, bool has_alpha, const std::vector& samples); EncoderPtr CreateAvifEncoder(avifCodecChoice codec_choice, int max_threads, @@ -104,7 +104,7 @@ inline constexpr size_t kMaxNumLayers = AVIF_MAX_AV1_LAYER_COUNT; size_t GetNumSamples(size_t num_frames, size_t width, size_t height, avifPixelFormat pixel_format, bool has_alpha); -size_t GetNumSamplesLayeredRandDim(size_t render_width, size_t render_height, +size_t GetNumSamplesLayeredRandDim(size_t display_width, size_t display_height, avifPixelFormat pixel_format, bool has_alpha); @@ -255,21 +255,21 @@ inline auto ArbitraryAvifLayered() { return fuzztest::OneOf(ArbitraryAvifLayered8b(), ArbitraryAvifLayered16b()); } -// Layered avifImage generator type: fixed number of layers, rendered size, +// Layered avifImage generator type: fixed number of layers, display size, // pixel format and 8-bit samples. inline auto ArbitraryAvifLayeredRandDim8b() { constexpr uint16_t kMinLayerDimension = 8; constexpr uint16_t kMaxLayerDimension = kMaxDimension / kMaxNumFramesSquareRoot; return fuzztest::FlatMap( - [](size_t render_width, size_t render_height, + [](size_t display_width, size_t display_height, avifPixelFormat pixel_format, bool has_alpha) { return fuzztest::Map( - CreateAvifLayeredRandDim8b, fuzztest::Just(render_width), - fuzztest::Just(render_height), fuzztest::Just(pixel_format), + CreateAvifLayeredRandDim8b, fuzztest::Just(display_width), + fuzztest::Just(display_height), fuzztest::Just(pixel_format), fuzztest::Just(has_alpha), fuzztest::Arbitrary>().WithSize( - GetNumSamplesLayeredRandDim(render_width, render_height, + GetNumSamplesLayeredRandDim(display_width, display_height, pixel_format, has_alpha))); }, fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), @@ -277,23 +277,23 @@ inline auto ArbitraryAvifLayeredRandDim8b() { ArbitraryPixelFormat(), fuzztest::Arbitrary()); } -// Layered avifImage generator type: fixed number of layers, rendered size, +// Layered avifImage generator type: fixed number of layers, display size, // depth, pixel format and 16-bit samples. inline auto ArbitraryAvifLayeredRandDim16b() { constexpr uint16_t kMinLayerDimension = 8; constexpr uint16_t kMaxLayerDimension = kMaxDimension / kMaxNumFramesSquareRoot; return fuzztest::FlatMap( - [](size_t render_width, size_t render_height, int depth, + [](size_t display_width, size_t display_height, int depth, avifPixelFormat pixel_format, bool has_alpha) { return fuzztest::Map( - CreateAvifLayeredRandDim16b, fuzztest::Just(render_width), - fuzztest::Just(render_height), fuzztest::Just(depth), + CreateAvifLayeredRandDim16b, fuzztest::Just(display_width), + fuzztest::Just(display_height), fuzztest::Just(depth), fuzztest::Just(pixel_format), fuzztest::Just(has_alpha), fuzztest::ContainerOf>( fuzztest::InRange(0, (1 << depth) - 1)) .WithSize(GetNumSamplesLayeredRandDim( - render_width, render_height, pixel_format, has_alpha))); + display_width, display_height, pixel_format, has_alpha))); }, fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), fuzztest::InRange(kMinLayerDimension, kMaxLayerDimension), @@ -301,7 +301,7 @@ inline auto ArbitraryAvifLayeredRandDim16b() { fuzztest::Arbitrary()); } -// Generator for an arbitrary layered still image with rendered size override. +// Generator for an arbitrary layered still image with display size override. inline auto ArbitraryAvifLayeredRandDim() { return fuzztest::OneOf(ArbitraryAvifLayeredRandDim8b(), ArbitraryAvifLayeredRandDim16b()); diff --git a/tests/gtest/avifchangesettingtest.cc b/tests/gtest/avifchangesettingtest.cc index 31c37dfb50..7cbc83b7dc 100644 --- a/tests/gtest/avifchangesettingtest.cc +++ b/tests/gtest/avifchangesettingtest.cc @@ -241,7 +241,7 @@ TEST(ChangeSettingTest, UnchangeableImageChromaSamplePosition) { AVIF_RESULT_INCOMPATIBLE_IMAGE); } -TEST(ChangeSettingTest, UnchangeableRenderedSizeOverride) { +TEST(ChangeSettingTest, UnchangeableDisplaySizeOverride) { if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr) { GTEST_SKIP() << "Codec unavailable, skip test."; diff --git a/tests/test_cmd_progressive.sh b/tests/test_cmd_progressive.sh index 6cdd1c6730..fc4af73c4a 100755 --- a/tests/test_cmd_progressive.sh +++ b/tests/test_cmd_progressive.sh @@ -47,16 +47,16 @@ pushd ${TMP_DIR} "${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE}" "${AVIFDEC}" --progressive "${ENCODED_FILE}" "${DECODED_FILE}" - echo "Testing manual layered encoding with rendered-size override and mixed input sizes" - "${AVIFENC}" -s 8 --layered --render-size 768x512 \ + echo "Testing manual layered encoding with display-size override and mixed input sizes" + "${AVIFENC}" -s 8 --layered --display-size 768x512 \ "${INPUT_SMALL_Y4M}" "${INPUT_Y4M}" -o "${ENCODED_FILE}" "${AVIFDEC}" --info "${ENCODED_FILE}" > "${INFO_FILE}" grep -F "[768x512]" "${INFO_FILE}" "${AVIFDEC}" "${ENCODED_FILE}" "${DECODED_FILE}" "${AVIFDEC}" --progressive "${ENCODED_FILE}" "${DECODED_FILE}" - echo "Testing rendered-size override smaller than an input layer" - "${AVIFENC}" -s 8 --layered --render-size 384x256 \ + echo "Testing display-size override smaller than an input layer" + "${AVIFENC}" -s 8 --layered --display-size 384x256 \ "${INPUT_SMALL_Y4M}" "${INPUT_Y4M}" -o "${ENCODED_FILE}" && exit 1 # libavif relies on libyuv to do scaling From d78bc6c6cae0be0f3826b3588cc71ef7ad3e9fe3 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Mon, 7 Sep 2026 23:17:01 +0800 Subject: [PATCH 5/8] patch from Wan-Teh Chang --- apps/avifenc.c | 2 +- include/avif/avif.h | 17 +++++++++-------- src/codec_aom.c | 16 +++++++++++----- src/codec_avm.c | 10 +++++----- src/codec_rav1e.c | 12 ++++++------ src/codec_svt.c | 10 +++++----- src/write.c | 10 +++++----- 7 files changed, 42 insertions(+), 35 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index b45094e9f8..d8dad2cce5 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -2230,7 +2230,7 @@ int main(int argc, char * argv[]) goto cleanup; } if (avifSettingsUsesDisplaySizeOverride(&settings) && (settings.layers == 1) && (input.filesCount > 1)) { - fprintf(stderr, "ERROR: --display-size is not supported with image sequences. Use --layered for multiple still inputs.\n"); + fprintf(stderr, "ERROR: --display-size is not supported with image sequences. Use --layered for multiple still image inputs.\n"); goto cleanup; } diff --git a/include/avif/avif.h b/include/avif/avif.h index 231b85501c..556359cb88 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -822,10 +822,11 @@ typedef struct avifImage // transformFlags. On decode, only honor the values in boxes with the associated transform flag set. // These also apply to gainMap->image, if any. // - // When encoding with avifEncoder.width/height set (see its comment), these values are interpreted - // relative to that overridden display size rather than to this avifImage's own width/height. - // No special handling is needed during decode: the decoded avifImage is automatically scaled to - // the display size, so these values are relative to its width/height as usual. + // When encoding with avifEncoder.width/height set (see their comment), these transformations + // are interpreted relative to that overridden display size rather than to this avifImage's own + // width/height. No special handling is needed during decode: the decoded avifImage is + // automatically scaled to the display size, so these transformations are relative to the + // decoded avifImage's width/height as usual. avifTransformFlags transformFlags; avifPixelAspectRatioBox pasp; avifCleanApertureBox clap; @@ -1638,7 +1639,7 @@ typedef struct avifEncoder // Overrides the image's display size (the width and height AVIF declares this image is meant // to be shown at) independent of the pixel dimensions actually encoded. Any image passed to // avifEncoderAddImage()/avifEncoderAddImageGrid() that is encoded smaller than the display size - // is upscaled to it on decode. + // is upscaled to the display size on decode. // // Defaults to 0 (disabled). When 0, the display size is the size of the first added image. // When set, both width and height must be nonzero, and neither can be smaller than the coded @@ -1661,9 +1662,9 @@ typedef struct avifEncoder // decoder is required to honor; a coded image smaller than it is upscaled by the decoder to // match. Separately, an AV1 encoder session declares a maximum frame size once, up front, and // every later frame must fit within it; by default that maximum is simply the size of the first - // frame encoded. Setting width/height also declares that AV1-level maximum explicitly, in - // addition to the ispe box, so that later, larger layers stay within it. This is why it must be - // at least as large as every layer's coded size. + // frame encoded. Setting width/height also declares that AV1-level maximum frame size + // explicitly, in addition to the ispe box, so that later, larger layers stay within it. This is + // why it must be at least as large as every layer's coded size. uint32_t width; uint32_t height; } avifEncoder; diff --git a/src/codec_aom.c b/src/codec_aom.c index 9f23c62096..8edd3f8a2a 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -893,14 +893,20 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - if ((addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) && (encoder->width == 0) && (encoder->height == 0)) { + if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) { // 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 frame size to match - // max_frame_width and max_frame_height, - // so we can't use it if frame can have a different size. cfg->g_limit = 1; + // Reduced still picture header requires frame size to match max + // frame size, so we can't use reduced still picture header if frame + // size is different from max frame size. + if (cfg->g_forced_max_frame_width > image->width) { + cfg->g_forced_max_frame_width = image->width; + } + if (cfg->g_forced_max_frame_height > image->height) { + cfg->g_forced_max_frame_height = image->height; + } } if (useAllIntra) { #if !defined(AOM_USAGE_ALL_INTRA) @@ -939,7 +945,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, 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 + // aom_codec_enc_config_set() does not allow changing frame dimensions if // g_lag_in_frames > 1. cfg->g_lag_in_frames = 1; } diff --git a/src/codec_avm.c b/src/codec_avm.c index adf2699021..ad829fc85b 100644 --- a/src/codec_avm.c +++ b/src/codec_avm.c @@ -444,6 +444,11 @@ static avifResult avmCodecEncodeImage(avifCodec * codec, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output) { + if (encoder->width || encoder->height) { + avifDiagnosticsPrintf(codec->diag, "AVM does not support display-size override"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + struct avm_codec_enc_cfg * cfg = &codec->internal->cfg; avifBool quantizerUpdated = AVIF_FALSE; const int quantizer = avmQualityToQuantizer(quality, image->depth); @@ -453,11 +458,6 @@ 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 display-size override"); - return AVIF_RESULT_NOT_IMPLEMENTED; - } - if (!codec->internal->encoderInitialized) { int avmCpuUsed = -1; if (encoder->speed != AVIF_SPEED_DEFAULT) { diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 4b41bfeb52..d94553d204 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c @@ -82,6 +82,12 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec, return AVIF_RESULT_NOT_IMPLEMENTED; } + // rav1e does not support overriding maximum frame width/height in sequence header + if (encoder->width || encoder->height) { + avifDiagnosticsPrintf(codec->diag, "rav1e does not support display-size override"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + // rav1e does not support encoding layered image. if (encoder->extraLayerCount > 0) { return AVIF_RESULT_NOT_IMPLEMENTED; @@ -90,12 +96,6 @@ 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 display-size override"); - return AVIF_RESULT_NOT_IMPLEMENTED; - } - avifResult result = AVIF_RESULT_UNKNOWN_ERROR; RaConfig * rav1eConfig = NULL; diff --git a/src/codec_svt.c b/src/codec_svt.c index 70be13fa14..71bdac06c2 100644 --- a/src/codec_svt.c +++ b/src/codec_svt.c @@ -75,6 +75,11 @@ static avifResult svtCodecEncodeImage(avifCodec * codec, } } + if (encoder->width || encoder->height) { + avifDiagnosticsPrintf(codec->diag, "SVT-AV1 does not support display-size override"); + return AVIF_RESULT_NOT_IMPLEMENTED; + } + // SVT-AV1 does not support encoding layered image. if (encoder->extraLayerCount > 0) { return AVIF_RESULT_NOT_IMPLEMENTED; @@ -83,11 +88,6 @@ 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 display-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. diff --git a/src/write.c b/src/write.c index 59095ac5f5..3da38e2aeb 100644 --- a/src/write.c +++ b/src/write.c @@ -525,8 +525,6 @@ avifEncoder * avifEncoderCreate(void) encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY; encoder->minQuantizerAlpha = AVIF_QUANTIZER_BEST_QUALITY; encoder->maxQuantizerAlpha = AVIF_QUANTIZER_WORST_QUALITY; - encoder->width = 0; - encoder->height = 0; encoder->tileRowsLog2 = 0; encoder->tileColsLog2 = 0; encoder->autoTiling = AVIF_FALSE; @@ -541,6 +539,8 @@ avifEncoder * avifEncoderCreate(void) encoder->creationTime = 0; encoder->modificationTime = 0; encoder->sampleTransformRecipe = AVIF_SAMPLE_TRANSFORM_NONE; + encoder->width = 0; + encoder->height = 0; return encoder; } @@ -574,8 +574,6 @@ static void avifEncoderBackupSettings(avifEncoder * encoder) lastEncoder->timescale = encoder->timescale; lastEncoder->repetitionCount = encoder->repetitionCount; lastEncoder->extraLayerCount = encoder->extraLayerCount; - lastEncoder->width = encoder->width; - lastEncoder->height = encoder->height; lastEncoder->minQuantizer = encoder->minQuantizer; lastEncoder->maxQuantizer = encoder->maxQuantizer; lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha; @@ -586,6 +584,8 @@ static void avifEncoderBackupSettings(avifEncoder * encoder) encoder->data->lastTileColsLog2 = encoder->data->tileColsLog2; lastEncoder->scalingMode = encoder->scalingMode; lastEncoder->sampleTransformRecipe = encoder->sampleTransformRecipe; + lastEncoder->width = encoder->width; + lastEncoder->height = encoder->height; } // This function detects changes made on avifEncoder. It returns true on success (i.e., if every @@ -1598,7 +1598,7 @@ static avifCodecType avifEncoderGetCodecType(const avifEncoder * encoder) return avifCodecTypeFromChoice(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); } -static avifResult avifEncoderValidateDisplaySizeOverride(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) +static avifResult avifEncoderValidateDisplaySizeOverride(const avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) { if (!avifEncoderUsesDisplaySizeOverride(encoder)) { return AVIF_RESULT_OK; From 43279ab89ce4bc1fc0661134cc6884d40afa0c58 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Mon, 7 Sep 2026 23:30:59 +0800 Subject: [PATCH 6/8] additional changes --- CHANGELOG.md | 2 ++ tests/gtest/avif_fuzztest_enc_dec_layered.cc | 24 ++++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27558f07b2..6152efd886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ The changes are relative to the previous release, unless the baseline is specifi * Add the ignoreICC option to avifDecoder * Support ignoring alpha in avifDecoder::imageContentToDecode +* Support overriding display size in avifEncoder * avifenc: add --ignore-alpha flag to discard alpha channel on encode +* avifenc: add --display-size flag to override display size of encoded image * avifgainmaputil: add --ignore-alpha flag to discard alpha channel * avifgainmaputil: add --ignore-exif and --ignore-xmp flags * avifdec: add --ignore-exif and --ignore-xmp flags diff --git a/tests/gtest/avif_fuzztest_enc_dec_layered.cc b/tests/gtest/avif_fuzztest_enc_dec_layered.cc index e1d108ad71..4a8a750c67 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_layered.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_layered.cc @@ -18,24 +18,18 @@ namespace { constexpr size_t kLayerCount = kMaxNumLayers; -struct UniformScale { - int32_t n; - int32_t d; -}; - -constexpr UniformScale kNoScale = {1, 1}; +constexpr avifFraction kNoScale = {1, 1}; constexpr size_t kRandomScalingModeCount = kLayerCount - 1; static_assert(kRandomScalingModeCount == 3); -constexpr std::array kSupportedScalingModes = { - UniformScale{1, 8}, UniformScale{1, 4}, UniformScale{1, 2}, - UniformScale{3, 5}, UniformScale{3, 4}, UniformScale{4, 5}}; -constexpr avifScalingMode kNoScalingMode = {{kNoScale.n, kNoScale.d}, - {kNoScale.n, kNoScale.d}}; +constexpr std::array kSupportedScalingModes = { + avifFraction{1, 8}, avifFraction{1, 4}, avifFraction{1, 2}, + avifFraction{3, 5}, avifFraction{3, 4}, avifFraction{4, 5}}; +constexpr avifScalingMode kNoScalingMode = {kNoScale, kNoScale}; constexpr std::array kNoScalingModes = { kNoScalingMode, kNoScalingMode, kNoScalingMode, kNoScalingMode}; -constexpr avifScalingMode MakeScalingMode(UniformScale horizontal, - UniformScale vertical) { +constexpr avifScalingMode MakeScalingMode(avifFraction horizontal, + avifFraction vertical) { return avifScalingMode{{horizontal.n, horizontal.d}, {vertical.n, vertical.d}}; } @@ -62,9 +56,9 @@ constexpr size_t CombinationCountWithRepetition(size_t value_count, constexpr size_t kScalingModeCombinationCount = CombinationCountWithRepetition( kSupportedScalingModes.size(), kRandomScalingModeCount); -std::array GetScalingModeCombination( +std::array GetScalingModeCombination( size_t combination_index) { - std::array combination = {}; + std::array combination = {}; size_t remaining_index = combination_index; size_t next_candidate = 0; for (size_t slot = 0; slot < combination.size(); ++slot) { From d313cafb10a5b437d0bc0150c6d4775c455b028e Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Mon, 7 Sep 2026 23:46:25 +0800 Subject: [PATCH 7/8] extra fix --- src/write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/write.c b/src/write.c index 3da38e2aeb..33eacf0d2d 100644 --- a/src/write.c +++ b/src/write.c @@ -1598,7 +1598,7 @@ static avifCodecType avifEncoderGetCodecType(const avifEncoder * encoder) return avifCodecTypeFromChoice(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE); } -static avifResult avifEncoderValidateDisplaySizeOverride(const avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) +static avifResult avifEncoderValidateDisplaySizeOverride(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell) { if (!avifEncoderUsesDisplaySizeOverride(encoder)) { return AVIF_RESULT_OK; From 9f5af4d87375f668e9cea4b3c0ae31fa943dea82 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Tue, 8 Sep 2026 01:17:18 +0800 Subject: [PATCH 8/8] Strict image size == last layer size --- apps/avifenc.c | 15 +++++++-------- include/avif/avif.h | 39 +++++++-------------------------------- src/codec_aom.c | 9 --------- src/write.c | 37 +++++++++++++++++++++++++++++++++++-- 4 files changed, 49 insertions(+), 51 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index d8dad2cce5..5c007a6418 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -245,7 +245,7 @@ static void syntaxLong(void) 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(" --display-size WxH : Set the size at which the AVIF image is displayed, independent of the encoded pixel dimensions.\n"); - printf(" Supported for still images and layered still images only.\n"); + printf(" Only supported for layered still images (--layered/--progressive); the last layer must exactly match it.\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"); @@ -2229,8 +2229,12 @@ int main(int argc, char * argv[]) fprintf(stderr, "ERROR: --display-size is not supported with --grid.\n"); goto cleanup; } - if (avifSettingsUsesDisplaySizeOverride(&settings) && (settings.layers == 1) && (input.filesCount > 1)) { - fprintf(stderr, "ERROR: --display-size is not supported with image sequences. Use --layered for multiple still image inputs.\n"); + if (avifSettingsUsesDisplaySizeOverride(&settings) && (settings.layers == 1)) { + if (input.filesCount > 1) { + fprintf(stderr, "ERROR: --display-size is not supported with image sequences. Use --layered for multiple still image inputs.\n"); + } else { + fprintf(stderr, "ERROR: --display-size is only supported with --layered.\n"); + } goto cleanup; } @@ -2457,11 +2461,6 @@ int main(int argc, char * argv[]) goto cleanup; } - if (avifSettingsUsesDisplaySizeOverride(&settings) && (settings.layers == 1) && input.frameIter) { - fprintf(stderr, "ERROR: --display-size is not supported with image sequences.\n"); - goto cleanup; - } - uint32_t outputImageWidth; uint32_t outputImageHeight; avifSettingsGetEffectiveOutputDimensions(&settings, image, &outputImageWidth, &outputImageHeight); diff --git a/include/avif/avif.h b/include/avif/avif.h index 556359cb88..75b67e1068 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -823,9 +823,9 @@ typedef struct avifImage // These also apply to gainMap->image, if any. // // When encoding with avifEncoder.width/height set (see their comment), these transformations - // are interpreted relative to that overridden display size rather than to this avifImage's own - // width/height. No special handling is needed during decode: the decoded avifImage is - // automatically scaled to the display size, so these transformations are relative to the + // are interpreted relative to that size rather than to this intermediate layer's own + // width/height. No special handling is needed during decode: the decoded layer is + // automatically scaled to the configured size, so these transformations are relative to the // decoded avifImage's width/height as usual. avifTransformFlags transformFlags; avifPixelAspectRatioBox pasp; @@ -1636,35 +1636,10 @@ typedef struct avifEncoder // Version 1.4.0 ends here. Add any new members after this line. // -------------------------------------------------------------------------------------------- - // Overrides the image's display size (the width and height AVIF declares this image is meant - // to be shown at) independent of the pixel dimensions actually encoded. Any image passed to - // avifEncoderAddImage()/avifEncoderAddImageGrid() that is encoded smaller than the display size - // is upscaled to the display size on decode. - // - // Defaults to 0 (disabled). When 0, the display size is the size of the first added image. - // When set, both width and height must be nonzero, and neither can be smaller than the coded - // dimensions of any image passed to avifEncoderAddImage()/avifEncoderAddImageGrid(). - // - // Not supported together with image sequences, image grids, scalingMode, sample - // transforms, or gain maps. Cannot be changed once encoding has started (i.e. after the first - // call to avifEncoderAddImage()/avifEncoderAddImageGrid()). - // - // The main use case is a layered image (see extraLayerCount above) where earlier layers' - // pixel data is already available at a smaller size, so encoding them directly at that size - // avoids the wasted round trip of upscaling to the display size only for the encoder to scale - // it back down, and allows scale ratios that scalingMode cannot express for the encoder in use. - // - // avifImage properties that are relative to image dimensions (clap, irot, imir, pasp; see the - // "Transformations" comment above) are interpreted relative to the display size when set, rather - // than to the coded size of whichever layer they came from. - // - // Implementation note: the display size is stored as the ispe box, the dimensions an AVIF - // decoder is required to honor; a coded image smaller than it is upscaled by the decoder to - // match. Separately, an AV1 encoder session declares a maximum frame size once, up front, and - // every later frame must fit within it; by default that maximum is simply the size of the first - // frame encoded. Setting width/height also declares that AV1-level maximum frame size - // explicitly, in addition to the ispe box, so that later, larger layers stay within it. This is - // why it must be at least as large as every layer's coded size. + // Only for layered image (extraLayerCount < 0), otherwise must be the default value 0. + // Declares the size of the encoded image beforehand, which shall be exactly the size of the + // last layer. This allows adding smaller images as earlier layers to avoid a wasted scaling + // round trip, or scale ratios that the encoder does not support via scalingMode. uint32_t width; uint32_t height; } avifEncoder; diff --git a/src/codec_aom.c b/src/codec_aom.c index 8edd3f8a2a..77b8168c6a 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -898,15 +898,6 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // libaom to set still_picture and reduced_still_picture_header to // 1 in AV1 sequence headers. cfg->g_limit = 1; - // Reduced still picture header requires frame size to match max - // frame size, so we can't use reduced still picture header if frame - // size is different from max frame size. - if (cfg->g_forced_max_frame_width > image->width) { - cfg->g_forced_max_frame_width = image->width; - } - if (cfg->g_forced_max_frame_height > image->height) { - cfg->g_forced_max_frame_height = image->height; - } } if (useAllIntra) { #if !defined(AOM_USAGE_ALL_INTRA) diff --git a/src/write.c b/src/write.c index 33eacf0d2d..ee7061218f 100644 --- a/src/write.c +++ b/src/write.c @@ -1630,11 +1630,22 @@ static avifResult avifEncoderValidateDisplaySizeOverride(avifEncoder * encoder, return AVIF_RESULT_NOT_IMPLEMENTED; } - if ((encoder->data->items.count > 0) && (encoder->extraLayerCount == 0)) { - avifDiagnosticsPrintf(&encoder->diag, "display-size override is not supported for image sequences"); + if (encoder->extraLayerCount == 0) { + // Not supported for non-layered still images or image sequences. A non-layered still image + // has no earlier, smaller layer to benefit from the override; per section 2.2.2 of AV1 Image + // File Format specification v1.2.0 (see below), its coded size must equal the display size + // anyway, so the override would be a no-op at best. + avifDiagnosticsPrintf(&encoder->diag, "display-size override is only supported for layered images (extraLayerCount > 0)"); return AVIF_RESULT_NOT_IMPLEMENTED; } + // According to section 2.2.2 of AV1 Image File Format specification v1.2.0: + // NOTE: The dimensions of possible intermediate output images might not match the ones given + // in the 'ispe' property. If renderers display these intermediate images, they are expected + // to scale the output image to match the 'ispe' property. + // See https://aomediacodec.github.io/av1-avif/v1.2.0.html#image-spatial-extents-property. + // This allows earlier layers to be no larger than the display size. The last layer has a + // stricter, exact-match requirement which is enforced when that final layer is being added. if ((encoder->width < firstCell->width) || (encoder->height < firstCell->height)) { avifDiagnosticsPrintf(&encoder->diag, "display-size override %ux%u must be at least the coded image size %ux%u", @@ -1966,6 +1977,28 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } avifEncoderBackupSettings(encoder); + // According to section 2.2.2 of AV1 Image File Format specification v1.2.0: + // [...] the values of image_width and image_height shall respectively equal the values of + // UpscaledWidth and FrameHeight as defined in [AV1] but for a specific frame in the item + // payload. [...] + // In the absence of a 'lsel' property associated with the item, or if it is present and its + // layer_id value is set to 0xFFFF: + // If no OperatingPointSelectorProperty is associated with the item, the 'ispe' property + // shall document the dimensions of the last frame decoded when processing the operating + // point whose index is 0. + // See https://aomediacodec.github.io/av1-avif/v1.2.0.html#image-spatial-extents-property. + // This requires display size to exactly match the coded size of the last layer. + if (avifEncoderUsesDisplaySizeOverride(encoder) && (encoder->data->frames.count == encoder->extraLayerCount) && + ((encoder->width != firstCell->width) || (encoder->height != firstCell->height))) { + avifDiagnosticsPrintf(&encoder->diag, + "display-size override %ux%u must exactly match the coded size %ux%u of the last layer", + encoder->width, + encoder->height, + firstCell->width, + firstCell->height); + return AVIF_RESULT_INCOMPATIBLE_IMAGE; + } + // ----------------------------------------------------------------------- if (durationInTimescales == 0) {