diff --git a/apps/avifenc.c b/apps/avifenc.c index 7529e896f9..7e83db171a 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -9,6 +9,7 @@ #include "y4m.h" #include +#include #include #include #include @@ -83,9 +84,9 @@ static void syntax(void) AVIF_QUANTIZER_LOSSLESS); printf(" --tilerowslog2 R : Set log2 of number of tile rows (0-6, default: 0)\n"); printf(" --tilecolslog2 C : Set log2 of number of tile columns (0-6, default: 0)\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"); + printf(" -g,--grid MxN(xL) : Encode a single-image grid AVIF with M cols & N rows & L layers. Either supply MxNxL identical W/H/D images,\n"); + printf(" or L identical W/H/D images that each can be evenly split into the MxN grid and follow AVIF grid image restrictions.\n"); + printf(" The grid will adopt the color profile of the first image supplied.\n"); printf(" -s,--speed S : Encoder speed (%d-%d, slowest-fastest, 'default' or 'd' for codec internal defaults. default speed: 6)\n", AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST); @@ -104,6 +105,35 @@ static void syntax(void) printf(" --clap WN,WD,HN,HD,HON,HOD,VON,VOD: Add clap property (clean aperture). Width, Height, HOffset, VOffset (in num/denom pairs)\n"); printf(" --irot ANGLE : Add irot property (rotation). [0-3], makes (90 * ANGLE) degree rotation anti-clockwise\n"); printf(" --imir MODE : Add imir property (mirroring). 0=top-to-bottom, 1=left-to-right\n"); + printf(" --progressive LAYER_CONFIG : Encode progressive AVIF with given layer config\n"); + printf("\n"); + printf("progressive layer config format:\n"); + printf(" LAYER_CONFIG can be one of the two forms:\n"); + printf(" 1. SUB_CONFIG apply SUB_CONFIG to both color (YUV) planes and alpha plane\n"); + printf(" 2. SUB_CONFIG;SUB_CONFIG apply first SUB_CONFIG to color planes, second SUB_CONFIG to alpha plane\n"); + printf("\n"); + printf(" SUB_CONFIG is 0-4 LAYER_CONFIG joined by colon(:), and LAYER_CONFIG is in this form:\n"); + printf(" MinQ[,MaxQ][-ScaleH[,ScaleV]]\n"); + printf("\n"); + printf(" MinQ and MaxQ are min and max quantizers for this layer, and will overwrite values given by --min and --max.\n"); + printf(" Specially, when using aom with end-usage set to q or cq, min and max quantizers will use values given by --min and --max,\n"); + printf(" and cq-level of this layer will set to average of MinQ and MaxQ.\n"); + printf(" ScaleH and ScaleV are horizontal and vertical scale ratios [default=1, or any fraction].\n"); + printf(" If MaxQ is eliminated it uses the value of MinQ.\n"); + printf(" If ScaleH is eliminated it uses default value 1 (no scaling); if ScaleV is eliminated it uses the value of ScaleH.\n"); + printf("\n"); + printf(" Examples:\n"); + printf(" 40,62-1/2,1/4:30-1/2:10\n"); + printf(" Color and alpha planes both have 3 layers and share the same following config:\n"); + printf(" #0: min quantizer 40, max quantizer 62, 1/2 width, 1/4 height\n"); + printf(" #1: min quantizer 30, max quantizer 30, 1/2 width, 1/2 height\n"); + printf(" #2: min quantizer 10, max quantizer 10, full width, full height\n"); + printf("\n"); + printf(" 30,1/2:10;\n"); + printf(" Color planes have 2 layers, alpha plane is not layered.\n"); + printf("\n"); + printf(" ;30,1/2:10\n"); + printf(" Color planes is not layered, alpha plane have 2 layers.\n"); printf("\n"); if (avifCodecName(AVIF_CODEC_CHOICE_AOM, 0)) { printf("aom-specific advanced options:\n"); @@ -230,6 +260,267 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat return AVIF_TRUE; } +#define CHECK(condition, reason) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "ERROR: Failed reading progressive config: %s\n", reason); \ + return AVIF_FALSE; \ + } \ + } while (0) + +struct avifEncoderLayerConfig +{ + int extraLayerCount; // Image layers for color sub image; 0 to disable layer image (default). + int extraLayerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). + avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; + avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; +}; + +static const avifScalingMode avifScalingModeNormal = { 1, 1 }; + +static avifBool avifParseScalingMode(const char ** pArg, avifScalingMode * mode) +{ + char * end; + uint64_t value = strtoull(*pArg, &end, 10); + CHECK(errno != ERANGE, "overflowed while reading scale nominator"); + CHECK(end != *pArg, "can't parse scale nominator"); + if (*end != '/') { + if (value == 1) { + *mode = avifScalingModeNormal; + *pArg = end; + return AVIF_TRUE; + } + return AVIF_FALSE; + } + mode->numerator = value; + *pArg = end + 1; + value = strtoull(*pArg, &end, 10); + CHECK(errno != ERANGE, "overflowed while reading scale denominator"); + CHECK(end != *pArg, "can't parse scale denominator"); + mode->denominator = value; + *pArg = end; + return AVIF_TRUE; +} + +enum avifProgressiveConfigScannerState +{ + AVIF_PROGRESSIVE_SCANNER_STATE_VALUE, + AVIF_PROGRESSIVE_SCANNER_STATE_COMMA, + AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN, + AVIF_PROGRESSIVE_SCANNER_STATE_COLON, + AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON +}; + +enum avifProgressiveConfigValueType +{ + AVIF_PROGRESSIVE_VALUE_TYPE_NONE, + AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q, + AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q, + AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE, + AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE, +}; + +static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * config, const char * arg) +{ + int * currLayerCount = &config->extraLayerCount; + avifLayerConfig * currLayers = config->layers; + uint8_t currLayer = 0; + + enum avifProgressiveConfigScannerState currState = *arg == ';' ? AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON + : AVIF_PROGRESSIVE_SCANNER_STATE_VALUE; + enum avifProgressiveConfigScannerState targetState = AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON; + + enum avifProgressiveConfigValueType prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; + + for (;;) { + switch (currState) { + case AVIF_PROGRESSIVE_SCANNER_STATE_VALUE: { + int64_t value; + avifScalingMode mode; + char * end; + switch (prevReadValue) { + case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: + value = strtoll(arg, &end, 10); + CHECK(errno != ERANGE, "overflowed while reading min quantizer"); + CHECK(end != arg, "can't parse min quantizer"); + CHECK(value <= 63, "min quantizer too big"); + + arg = end; + currLayers[currLayer].minQuantizer = (int)value; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: + value = strtoll(arg, &end, 10); + CHECK(errno != ERANGE, "overflowed while reading max quantizer"); + CHECK(end != arg, "can't parse max quantizer"); + CHECK(value <= 63, "max quantizer too big"); + + arg = end; + currLayers[currLayer].maxQuantizer = (int)value; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: + CHECK(avifParseScalingMode(&arg, &mode), "unknown scaling mode"); + + currLayers[currLayer].horizontalMode = mode; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: + CHECK(avifParseScalingMode(&arg, &mode), "unknown scaling mode"); + + currLayers[currLayer].verticalMode = mode; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_COLON; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: + CHECK(AVIF_FALSE, "too many values in layer config"); + } + break; + } + + case AVIF_PROGRESSIVE_SCANNER_STATE_COMMA: + case AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN: + case AVIF_PROGRESSIVE_SCANNER_STATE_COLON: + case AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON: + switch (*arg) { + case ',': + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; + break; + case '-': + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN; + break; + case ':': + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_COLON; + break; + case ';': + case '\0': + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON; + break; + default: + CHECK(AVIF_FALSE, "unexpected separator"); + } + + CHECK(currState <= targetState, "too many config entries"); + + avifBool earlyEnd = currState < targetState; + switch (targetState) { + case AVIF_PROGRESSIVE_SCANNER_STATE_VALUE: + CHECK(AVIF_FALSE, "unknown state"); + break; + + case AVIF_PROGRESSIVE_SCANNER_STATE_COMMA: + CHECK(!earlyEnd, "unknown state"); + break; + + case AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN: + if (!earlyEnd) { + CHECK(prevReadValue == AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q, "unknown state"); + break; + } + + CHECK(prevReadValue == AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q, "unknown state"); + currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q; + break; + + case AVIF_PROGRESSIVE_SCANNER_STATE_COLON: + if (earlyEnd) { + switch (prevReadValue) { + case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: + currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: + currLayers[currLayer].verticalMode = currLayers[currLayer].horizontalMode; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: + case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: + CHECK(AVIF_FALSE, "unknown state"); + } + } + + ++currLayer; + CHECK(currLayer < MAX_AV1_LAYER_COUNT, "too many layers"); + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; + break; + + case AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON: + if (earlyEnd) { + switch (prevReadValue) { + case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: + currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: + currLayers[currLayer].verticalMode = currLayers[currLayer].horizontalMode; + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: + break; + + case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: + CHECK(AVIF_FALSE, "unknown state"); + } + + ++currLayer; + } + + *currLayerCount = currLayer - 1; + if (*arg == ';') { + CHECK(currLayers == config->layers, "too many sub image configurations"); + currLayers = config->layersAlpha; + currLayerCount = &config->extraLayerCountAlpha; + + if (*(arg + 1) == '\0') { + goto finish; + } + + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; + currLayer = 0; + } else { + // reached \0 + if (currLayers == config->layers) { + memcpy(config->layersAlpha, config->layers, sizeof(config->layers)); + config->extraLayerCountAlpha = config->extraLayerCount; + } + + goto finish; + } + break; + } + + ++arg; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_VALUE; + break; + } + } + +finish: + return AVIF_TRUE; +} + static avifInputFile * avifInputGetNextFile(avifInput * input) { if (input->useStdin) { @@ -337,7 +628,7 @@ static avifBool readEntireFile(const char * filename, avifRWData * raw) return AVIF_TRUE; } -static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, avifImage ** gridCells) +static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, uint32_t layerCount, avifImage ** gridCells) { if ((gridSplitImage->width % gridCols) != 0) { fprintf(stderr, "ERROR: Can't split image width (%u) evenly into %u columns.\n", gridSplitImage->width, gridCols); @@ -363,7 +654,7 @@ static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gr for (uint32_t gridX = 0; gridX < gridCols; ++gridX) { uint32_t gridIndex = gridX + (gridY * gridCols); avifImage * cellImage = avifImageCreateEmpty(); - gridCells[gridIndex] = cellImage; + gridCells[gridIndex * layerCount] = cellImage; avifImageCopy(cellImage, gridSplitImage, 0); cellImage->width = cellWidth; @@ -455,8 +746,9 @@ int main(int argc, char * argv[]) avifBool cicpExplicitlySet = AVIF_FALSE; avifBool premultiplyAlpha = AVIF_FALSE; int gridDimsCount = 0; - uint32_t gridDims[8]; // only the first two are used + uint32_t gridDims[8]; // only the first two or three are used uint32_t gridCellCount = 0; + uint32_t gridCellLayerCount = 0; avifImage ** gridCells = NULL; avifImage * gridSplitImage = NULL; // used for cleanup tracking memset(gridDims, 0, sizeof(gridDims)); @@ -475,6 +767,9 @@ int main(int argc, char * argv[]) avifTransferCharacteristics transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; avifMatrixCoefficients matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; + struct avifEncoderLayerConfig layerConfig; + memset(&layerConfig, 0, sizeof(layerConfig)); + int argIndex = 1; while (argIndex < argc) { const char * arg = argv[argIndex]; @@ -583,7 +878,9 @@ int main(int argc, char * argv[]) } else if (!strcmp(arg, "-g") || !strcmp(arg, "--grid")) { NEXTARG(); gridDimsCount = parseU32List(gridDims, arg); - if (gridDimsCount != 2) { + if (gridDimsCount == 2) { + gridDims[2] = 1; + } else if (gridDimsCount != 3) { fprintf(stderr, "ERROR: Invalid grid dims: %s\n", arg); returnCode = 1; goto cleanup; @@ -593,6 +890,11 @@ int main(int argc, char * argv[]) returnCode = 1; goto cleanup; } + if ((gridDims[2] == 0 || gridDims[2] > MAX_AV1_LAYER_COUNT)) { + fprintf(stderr, "ERROR: Invalid layer count (valid layer range [1-4]): %s\n", arg); + returnCode = 1; + goto cleanup; + } } else if (!strcmp(arg, "--cicp") || !strcmp(arg, "--nclx")) { NEXTARG(); int cicp[3]; @@ -756,6 +1058,12 @@ int main(int argc, char * argv[]) matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; // this is key for lossless } else if (!strcmp(arg, "-p") || !strcmp(arg, "--premultiply")) { premultiplyAlpha = AVIF_TRUE; + } else if (!strcmp(arg, "--progressive")) { + NEXTARG(); + if (!avifParseProgressiveConfig(&layerConfig, arg)) { + returnCode = 1; + goto cleanup; + } } else { // Positional argument input.files[input.filesCount].filename = arg; @@ -811,6 +1119,12 @@ int main(int argc, char * argv[]) } } + if (gridDimsCount == 0 && input.filesCount > 1 && (layerConfig.extraLayerCount > 0 || layerConfig.extraLayerCountAlpha > 0)) { + fprintf(stderr, "Progressive animated AVIF currently not supported.\n"); + returnCode = 1; + goto cleanup; + } + avifInputFile * firstFile = avifInputGetNextFile(&input); uint32_t sourceDepth = 0; avifAppSourceTiming firstSourceTiming; @@ -994,17 +1308,18 @@ int main(int argc, char * argv[]) gridCellCount = gridDims[0] * gridDims[1]; printf("Preparing to encode a %ux%u grid (%u cells)...\n", gridDims[0], gridDims[1], gridCellCount); - gridCells = calloc(gridCellCount, sizeof(avifImage *)); + gridCellLayerCount = gridCellCount * gridDims[2]; + gridCells = calloc(gridCellLayerCount, sizeof(avifImage *)); gridCells[0] = image; // take ownership of image - uint32_t gridCellIndex = 0; + uint32_t gridCellLayerIndex = 0; avifInputFile * nextFile; while ((nextFile = avifInputGetNextFile(&input)) != NULL) { - if (!gridCellIndex) { + if (!gridCellLayerIndex) { printf("Loading additional cells for grid image (%u cells)...\n", gridCellCount); } - ++gridCellIndex; - if (gridCellIndex >= gridCellCount) { + ++gridCellLayerIndex; + if (gridCellLayerIndex >= gridCellLayerCount) { // We have enough, warn and continue fprintf(stderr, "WARNING: [--grid] More than %u images were supplied for this %ux%u grid. The rest will be ignored.\n", @@ -1020,7 +1335,7 @@ int main(int argc, char * argv[]) cellImage->matrixCoefficients = image->matrixCoefficients; cellImage->yuvRange = image->yuvRange; cellImage->alphaPremultiplied = image->alphaPremultiplied; - gridCells[gridCellIndex] = cellImage; + gridCells[gridCellLayerIndex] = cellImage; avifAppFileFormat nextInputFormat = avifInputReadImage(&input, cellImage, NULL, NULL); if (nextInputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) { @@ -1056,19 +1371,22 @@ int main(int argc, char * argv[]) } } - if (gridCellIndex == 0) { + if (gridCellLayerIndex == gridDims[2] - 1 && (gridDims[0] != 1 || gridDims[1] != 1)) { printf("Single image input for a grid image. Attempting to split into %u cells...\n", gridCellCount); - gridSplitImage = image; - gridCells[0] = NULL; - if (!avifImageSplitGrid(gridSplitImage, gridDims[0], gridDims[1], gridCells)) { - returnCode = 1; - goto cleanup; + for (uint32_t layerIndex = 0; layerIndex < gridDims[2]; ++layerIndex) { + gridSplitImage = gridCells[layerIndex]; + gridCells[layerIndex] = NULL; + if (!avifImageSplitGrid(gridSplitImage, gridDims[0], gridDims[1], gridDims[2], gridCells + layerIndex)) { + returnCode = 1; + goto cleanup; + } } - gridCellIndex = gridCellCount - 1; + + gridCellLayerIndex = gridCellLayerCount - 1; } - if (gridCellIndex != gridCellCount - 1) { + if (gridCellLayerIndex != gridCellLayerCount - 1) { fprintf(stderr, "ERROR: Not enough input files for grid image! (expecting %u, or a single image to be split)\n", gridCellCount); returnCode = 1; goto cleanup; @@ -1080,7 +1398,11 @@ int main(int argc, char * argv[]) lossyHint = " (Lossless)"; } printf("AVIF to be written:%s\n", lossyHint); - avifImageDump(gridCells ? gridCells[0] : image, gridDims[0], gridDims[1], AVIF_PROGRESSIVE_STATE_UNAVAILABLE); + avifBool progressive = layerConfig.extraLayerCount > 0 || layerConfig.extraLayerCountAlpha > 0; + avifImageDump(gridCells ? gridCells[0] : image, + gridDims[0], + gridDims[1], + progressive ? AVIF_PROGRESSIVE_STATE_AVAILABLE : AVIF_PROGRESSIVE_STATE_UNAVAILABLE); printf("Encoding with AV1 codec '%s' speed [%d], color QP [%d (%s) <-> %d (%s)], alpha QP [%d (%s) <-> %d (%s)], tileRowsLog2 [%d], tileColsLog2 [%d], %d worker thread(s), please wait...\n", avifCodecName(codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE), @@ -1101,6 +1423,12 @@ int main(int argc, char * argv[]) encoder->maxQuantizer = maxQuantizer; encoder->minQuantizerAlpha = minQuantizerAlpha; encoder->maxQuantizerAlpha = maxQuantizerAlpha; + + encoder->extraLayerCount = layerConfig.extraLayerCount; + encoder->extraLayerCountAlpha = layerConfig.extraLayerCountAlpha; + memcpy(encoder->layers, layerConfig.layers, sizeof(encoder->layers)); + memcpy(encoder->layersAlpha, layerConfig.layersAlpha, sizeof(encoder->layersAlpha)); + encoder->tileRowsLog2 = tileRowsLog2; encoder->tileColsLog2 = tileColsLog2; encoder->codecChoice = codecChoice; @@ -1109,8 +1437,14 @@ int main(int argc, char * argv[]) encoder->keyframeInterval = keyframeInterval; if (gridDimsCount > 0) { - avifResult addImageResult = - avifEncoderAddImageGrid(encoder, gridDims[0], gridDims[1], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); + avifResult addImageResult; + if (gridDims[2] > 1) { + addImageResult = + avifEncoderAddImageProgressiveGrid(encoder, gridDims[0], gridDims[1], gridDims[2], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); + } else { + addImageResult = + avifEncoderAddImageGrid(encoder, gridDims[0], gridDims[1], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); + } if (addImageResult != AVIF_RESULT_OK) { fprintf(stderr, "ERROR: Failed to encode image grid: %s\n", avifResultToString(addImageResult)); returnCode = 1; diff --git a/include/avif/avif.h b/include/avif/avif.h index 2a7078eade..b96a1cf873 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -85,6 +85,8 @@ typedef int avifBool; #define AVIF_SPEED_SLOWEST 0 #define AVIF_SPEED_FASTEST 10 +#define MAX_AV1_LAYER_COUNT 4 + typedef enum avifPlanesFlag { AVIF_PLANES_YUV = (1 << 0), @@ -151,6 +153,7 @@ typedef enum avifResult AVIF_RESULT_WAITING_ON_IO, // similar to EAGAIN/EWOULDBLOCK, this means the avifIO doesn't have necessary data available yet AVIF_RESULT_INVALID_ARGUMENT, // an argument passed into this function is invalid AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented + AVIF_RESULT_INVALID_LAYERS, AVIF_RESULT_OUT_OF_MEMORY } avifResult; @@ -787,9 +790,11 @@ typedef enum avifProgressiveState // for an image sequence. AVIF_PROGRESSIVE_STATE_UNAVAILABLE = 0, - // The current AVIF/Source offers a progressive image, but avifDecoder.allowProgressive is not - // enabled, so it will behave as if the image was not progressive and will simply decode the - // best version of this item. + // For decoder, this means the current AVIF/Source offers a progressive image, but + // avifDecoder.allowProgressive is not enabled, so it will behave as if the image was not + // progressive and will simply decode the best version of this item. + // For encoder, this means at least one of color and alpha image has multiple layers and + // indicates this is a progressive image. AVIF_PROGRESSIVE_STATE_AVAILABLE, // The current AVIF/Source offers a progressive image, and avifDecoder.allowProgressive is true. @@ -997,6 +1002,18 @@ AVIF_API avifResult avifDecoderNthImageMaxExtent(const avifDecoder * decoder, ui struct avifEncoderData; struct avifCodecSpecificOptions; +typedef struct avifScalingMode { + uint64_t numerator; + uint64_t denominator; +} avifScalingMode; + +typedef struct avifLayerConfig { + int minQuantizer; + int maxQuantizer; + avifScalingMode horizontalMode; + avifScalingMode verticalMode; +} avifLayerConfig; + // Notes: // * If avifEncoderWrite() returns AVIF_RESULT_OK, output must be freed with avifRWDataFree() // * If (maxThreads < 2), multithreading is disabled @@ -1025,6 +1042,11 @@ typedef struct avifEncoder int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default). uint64_t timescale; // timescale of the media (Hz) + int extraLayerCount; // Extra layers for color sub image; 0 to disable layer image (default). + int extraLayerCountAlpha; // Extra layers for alpha sub image; 0 to disable layer image (default). + avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; + avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; + // stats from the most recent write avifIOStats ioStats; @@ -1073,6 +1095,16 @@ AVIF_API avifResult avifEncoderAddImageGrid(avifEncoder * encoder, uint32_t gridRows, const avifImage * const * cellImages, avifAddImageFlags addImageFlags); +avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags); +avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, + uint32_t gridCols, + uint32_t gridRows, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags); AVIF_API avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output); // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs. These diff --git a/include/avif/internal.h b/include/avif/internal.h index ace7611b25..5ea4d877a0 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -259,6 +259,7 @@ typedef avifResult (*avifCodecEncodeImageFunc)(struct avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + uint32_t layerIndex, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output); typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifCodecEncodeOutput * output); diff --git a/src/avif.c b/src/avif.c index 739066ce6d..19c9963e56 100644 --- a/src/avif.c +++ b/src/avif.c @@ -93,6 +93,7 @@ const char * avifResultToString(avifResult result) case AVIF_RESULT_WAITING_ON_IO: return "Waiting on IO"; case AVIF_RESULT_INVALID_ARGUMENT: return "Invalid argument"; case AVIF_RESULT_NOT_IMPLEMENTED: return "Not implemented"; + case AVIF_RESULT_INVALID_LAYERS: return "Invalid layer image"; case AVIF_RESULT_OUT_OF_MEMORY: return "Out of memory"; case AVIF_RESULT_UNKNOWN_ERROR: default: diff --git a/src/codec_aom.c b/src/codec_aom.c index 44deab9bb0..4e29a21515 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -48,6 +48,15 @@ #endif #endif +typedef enum avifAOMScaleConfigMethod +{ + // Set using AV1E_SET_SVC_PARAMS + AVIF_AOM_SCALE_SVC_PARAMS, + + // Set using AOME_SET_SCALEMODE + AVIF_AOM_SCALE_SCALEMODE +} avifAOMScaleConfigMethod; + struct avifCodecInternal { #if defined(AVIF_CODEC_AOM_DECODE) @@ -62,8 +71,9 @@ struct avifCodecInternal aom_codec_ctx_t encoder; avifPixelFormatInfo formatInfo; aom_img_fmt_t aomFormat; + aom_codec_enc_cfg_t cfg; avifBool monochromeEnabled; - // Whether cfg.rc_end_usage was set with an + // Whether cfg->rc_end_usage was set with an // avifEncoderSetCodecSpecificOption(encoder, "end-usage", value) call. avifBool endUsageSet; // Whether cq-level was set with an @@ -72,6 +82,7 @@ struct avifCodecInternal // Whether 'tuning' (of the specified distortion metric) was set with an // avifEncoderSetCodecSpecificOption(encoder, "tune", value) call. avifBool tuningSet; + avifAOMScaleConfigMethod scaleConfigMethod; #endif }; @@ -284,6 +295,31 @@ static aom_img_fmt_t avifImageCalcAOMFmt(const avifImage * image, avifBool alpha return fmt; } +struct aomScalingModeMapList +{ + avifScalingMode avifMode; + AOM_SCALING_MODE aomMode; +}; + +static const struct aomScalingModeMapList scalingModeMap[] = { + { { 1, 1 }, AOME_NORMAL }, { { 1, 2 }, AOME_ONETWO }, { { 1, 4 }, AOME_ONEFOUR }, { { 1, 8 }, AOME_ONEEIGHT }, + { { 3, 4 }, AOME_THREEFOUR }, { { 3, 5 }, AOME_THREEFIVE }, { { 4, 5 }, AOME_FOURFIVE }, +}; + +static const int scalingModeMapSize = sizeof(scalingModeMap) / sizeof(scalingModeMap[0]); + +static avifBool avifFindAOMScalingMode(const avifScalingMode * avifMode, AOM_SCALING_MODE * aomMode) +{ + for (int i = 0; i < scalingModeMapSize; ++i) { + if (scalingModeMap[i].avifMode.numerator == avifMode->numerator && scalingModeMap[i].avifMode.denominator == avifMode->denominator) { + *aomMode = scalingModeMap[i].aomMode; + return AVIF_TRUE; + } + } + + return AVIF_FALSE; +} + #if !defined(HAVE_AOM_CODEC_SET_OPTION) static avifBool aomOptionParseInt(const char * str, int * val) { @@ -525,23 +561,34 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + uint32_t layerIndex, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output) { + uint32_t extraLayerCount = alpha ? encoder->extraLayerCountAlpha : encoder->extraLayerCount; + const avifLayerConfig * layers = alpha ? encoder->layersAlpha : encoder->layers; + // Disable single image flag to allow interlayer compression + // todo: we clearly don't want ALL_INTRA, but other tweaks should be reviewed as well. + if (extraLayerCount > 0) { + addImageFlags &= ~AVIF_ADD_IMAGE_FLAG_SINGLE; + } + + aom_codec_enc_cfg_t * cfg = &codec->internal->cfg; + // Map encoder speed to AOM usage + CpuUsed: + // Speed 0: GoodQuality CpuUsed 0 + // Speed 1: GoodQuality CpuUsed 1 + // Speed 2: GoodQuality CpuUsed 2 + // Speed 3: GoodQuality CpuUsed 3 + // Speed 4: GoodQuality CpuUsed 4 + // Speed 5: GoodQuality CpuUsed 5 + // Speed 6: GoodQuality CpuUsed 6 + // Speed 7: RealTime CpuUsed 7 + // Speed 8: RealTime CpuUsed 8 + // Speed 9: RealTime CpuUsed 9 + // Speed 10: RealTime CpuUsed 9 + unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY; + if (!codec->internal->encoderInitialized) { - // Map encoder speed to AOM usage + CpuUsed: - // Speed 0: GoodQuality CpuUsed 0 - // Speed 1: GoodQuality CpuUsed 1 - // Speed 2: GoodQuality CpuUsed 2 - // Speed 3: GoodQuality CpuUsed 3 - // Speed 4: GoodQuality CpuUsed 4 - // Speed 5: GoodQuality CpuUsed 5 - // Speed 6: GoodQuality CpuUsed 6 - // Speed 7: RealTime CpuUsed 7 - // Speed 8: RealTime CpuUsed 8 - // Speed 9: RealTime CpuUsed 9 - // Speed 10: RealTime CpuUsed 9 - unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY; // Use the new AOM_USAGE_ALL_INTRA (added in https://crbug.com/aomedia/2959) for still // image encoding if it is available. #if defined(AOM_USAGE_ALL_INTRA) @@ -594,8 +641,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo); aom_codec_iface_t * encoderInterface = aom_codec_av1_cx(); - struct aom_codec_enc_cfg cfg; - aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, &cfg, aomUsage); + aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, cfg, aomUsage); if (err != AOM_CODEC_OK) { avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err)); return AVIF_RESULT_UNKNOWN_ERROR; @@ -607,16 +653,16 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // libaom's default is AOM_VBR. Change the default to AOM_Q since we don't need to // hit a certain target bit rate. It's easier to control the worst quality in Q // mode. - cfg.rc_end_usage = AOM_Q; + cfg->rc_end_usage = AOM_Q; break; case AOM_USAGE_REALTIME: // For real-time mode we need to use CBR rate control mode. AOM_Q doesn't fit the // rate control requirements for real-time mode. CBR does. - cfg.rc_end_usage = AOM_CBR; + cfg->rc_end_usage = AOM_CBR; break; #if defined(AOM_USAGE_ALL_INTRA) case AOM_USAGE_ALL_INTRA: - cfg.rc_end_usage = AOM_Q; + cfg->rc_end_usage = AOM_Q; break; #endif } @@ -655,16 +701,16 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - cfg.g_profile = seqProfile; - cfg.g_bit_depth = image->depth; - cfg.g_input_bit_depth = image->depth; - cfg.g_w = image->width; - cfg.g_h = image->height; + cfg->g_profile = seqProfile; + cfg->g_bit_depth = image->depth; + cfg->g_input_bit_depth = image->depth; + cfg->g_w = image->width; + cfg->g_h = image->height; 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. - cfg.g_limit = 1; + cfg->g_limit = 1; // Use the default settings of the new AOM_USAGE_ALL_INTRA (added in // https://crbug.com/aomedia/2959). @@ -672,25 +718,18 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // Set g_lag_in_frames to 0 to reduce the number of frame buffers // (from 20 to 2) in libaom's lookahead structure. This reduces // memory consumption when encoding a single image. - cfg.g_lag_in_frames = 0; + cfg->g_lag_in_frames = 0; // Disable automatic placement of key frames by the encoder. - cfg.kf_mode = AOM_KF_DISABLED; + cfg->kf_mode = AOM_KF_DISABLED; // Tell libaom that all frames will be key frames. - cfg.kf_max_dist = 0; + cfg->kf_max_dist = 0; } - if (encoder->maxThreads > 1) { - cfg.g_threads = encoder->maxThreads; + if (extraLayerCount > 0) { + cfg->g_lag_in_frames = 0; } - - int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63); - int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63); - if (alpha) { - minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63); - maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63); + if (encoder->maxThreads > 1) { + cfg->g_threads = encoder->maxThreads; } - avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS)); - cfg.rc_min_quantizer = minQuantizer; - cfg.rc_max_quantizer = maxQuantizer; codec->internal->monochromeEnabled = AVIF_FALSE; if (aomVersion > aomVersion_2_0_0) { @@ -698,17 +737,17 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // access nonexistent UV planes when encoding monochrome at faster libavif "speeds". It // was fixed shortly after the 2.0.0 libaom release, and the fix exists in both the // master and applejack branches. This ensures that the next version *after* 2.0.0 will - // have the fix, and we must avoid cfg.monochrome until then. + // have the fix, and we must avoid cfg->monochrome until then. // // Bugfix Change-Id: https://aomedia-review.googlesource.com/q/I26a39791f820b4d4e1d63ff7141f594c3c7181f5 if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) { codec->internal->monochromeEnabled = AVIF_TRUE; - cfg.monochrome = 1; + cfg->monochrome = 1; } } - if (!avifProcessAOMOptionsPreInit(codec, alpha, &cfg)) { + if (!avifProcessAOMOptionsPreInit(codec, alpha, cfg)) { return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION; } @@ -716,7 +755,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, if (image->depth > 8) { encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH; } - if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, &cfg, encoderFlags) != AOM_CODEC_OK) { + if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, cfg, encoderFlags) != AOM_CODEC_OK) { avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_init() failed: %s: %s", aom_codec_error(&codec->internal->encoder), @@ -725,9 +764,6 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } codec->internal->encoderInitialized = AVIF_TRUE; - if (lossless) { - aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1); - } if (encoder->maxThreads > 1) { aom_codec_control(&codec->internal->encoder, AV1E_SET_ROW_MT, 1); } @@ -744,21 +780,63 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, return AVIF_RESULT_UNKNOWN_ERROR; } } + + if (extraLayerCount > 0) { + int layerCount = extraLayerCount + 1; + +#if defined(AVIF_AOM_LAYER_CONFIG_PREFER_SVC_PARAMS) + avifBool useSvcParams = AVIF_TRUE; + for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { + avifLayerConfig * layer = &layers[configIndex]; + if (layer->horizontalMode.numerator != layer->verticalMode.numerator || + layer->horizontalMode.denominator != layer->verticalMode.denominator) { + useSvcParams = AVIF_FALSE; + break; + } + } +#else + avifBool useSvcParams = AVIF_FALSE; + for (int configIndex = 0; configIndex < layerCount; ++configIndex) { + const avifLayerConfig * layer = &layers[configIndex]; + AOM_SCALING_MODE mode; + if (layer->horizontalMode.numerator == layer->verticalMode.numerator && + layer->horizontalMode.denominator == layer->verticalMode.denominator && + !avifFindAOMScalingMode(&layer->horizontalMode, &mode)) { + useSvcParams = AVIF_TRUE; + break; + } + } +#endif + + codec->internal->scaleConfigMethod = useSvcParams ? AVIF_AOM_SCALE_SVC_PARAMS : AVIF_AOM_SCALE_SCALEMODE; + if (useSvcParams) { + aom_svc_params_t svcParams; + memset(&svcParams, 0, sizeof(aom_svc_params_t)); + svcParams.number_temporal_layers = 1; + svcParams.number_spatial_layers = layerCount; + svcParams.framerate_factor[0] = 1; + for (int configIndex = 0; configIndex < layerCount; ++configIndex) { + const avifLayerConfig * layer = &layers[configIndex]; + svcParams.min_quantizers[configIndex] = layer->minQuantizer; + svcParams.max_quantizers[configIndex] = layer->maxQuantizer; + svcParams.scaling_factor_num[configIndex] = (int)layer->horizontalMode.numerator; + svcParams.scaling_factor_den[configIndex] = (int)layer->horizontalMode.denominator; + } + + if (aom_codec_control(&codec->internal->encoder, AV1E_SET_SVC_PARAMS, &svcParams) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } else { + if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, layerCount) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } + } + if (!avifProcessAOMOptionsPostInit(codec, alpha)) { return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION; } -#if defined(AOM_USAGE_ALL_INTRA) - if (aomUsage == AOM_USAGE_ALL_INTRA && !codec->internal->endUsageSet && !codec->internal->cqLevelSet) { - // The default rc_end_usage in all intra mode is AOM_Q, which requires cq-level to - // function. A libavif user may not know this internal detail and therefore may only - // set the min and max quantizers in the avifEncoder struct. If this is the case, set - // cq-level to a reasonable value for the user, otherwise the default cq-level - // (currently 10) will be unknowingly used. - assert(cfg.rc_end_usage == AOM_Q); - unsigned int cqLevel = (cfg.rc_min_quantizer + cfg.rc_max_quantizer) / 2; - aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); - } -#endif + if (!codec->internal->tuningSet) { if (aom_codec_control(&codec->internal->encoder, AOME_SET_TUNING, AOM_TUNE_SSIM) != AOM_CODEC_OK) { return AVIF_RESULT_UNKNOWN_ERROR; @@ -766,6 +844,58 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } + int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63); + int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63); + if (alpha) { + minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63); + } + + if (extraLayerCount > 0) { + // Provide a way to set per-layer cq-level to allow using q and cq mode in layered image. + if (cfg->rc_end_usage == AOM_Q || cfg->rc_end_usage == AOM_CQ) { + unsigned int cqLevel; + if (alpha) { + cqLevel = (encoder->layersAlpha[layerIndex].minQuantizer + encoder->layersAlpha[layerIndex].maxQuantizer) / 2; + } else { + cqLevel = (encoder->layers[layerIndex].minQuantizer + encoder->layers[layerIndex].maxQuantizer) / 2; + } + aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); + } else { + minQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].minQuantizer, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].maxQuantizer, 0, 63); + if (alpha) { + minQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].minQuantizer, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].maxQuantizer, 0, 63); + } + } + } + + avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS)); + cfg->rc_min_quantizer = minQuantizer; + cfg->rc_max_quantizer = maxQuantizer; + + if (lossless) { + aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1); + } + + if (aom_codec_enc_config_set(&codec->internal->encoder, cfg) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + +#if defined(AOM_USAGE_ALL_INTRA) + if (aomUsage == AOM_USAGE_ALL_INTRA && !codec->internal->endUsageSet && !codec->internal->cqLevelSet) { + // The default rc_end_usage in all intra mode is AOM_Q, which requires cq-level to + // function. A libavif user may not know this internal detail and therefore may only + // set the min and max quantizers in the avifEncoder struct. If this is the case, set + // cq-level to a reasonable value for the user, otherwise the default cq-level + // (currently 10) will be unknowingly used. + assert(cfg->rc_end_usage == AOM_Q); + unsigned int cqLevel = (cfg->rc_min_quantizer + cfg->rc_max_quantizer) / 2; + aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); + } +#endif + aom_image_t aomImage; // We prefer to simply set the aomImage.planes[] pointers to the plane buffers in 'image'. When // doing this, we set aomImage.w equal to aomImage.d_w and aomImage.h equal to aomImage.d_h and @@ -874,6 +1004,36 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, aom_codec_control(&codec->internal->encoder, AV1E_SET_CHROMA_SAMPLE_POSITION, aomImage.csp); } + if (extraLayerCount > 0) { + switch (codec->internal->scaleConfigMethod) { + case AVIF_AOM_SCALE_SVC_PARAMS: { + aom_svc_layer_id_t layerId = { layerIndex, 0 }; + if (aom_codec_control(&codec->internal->encoder, AV1E_SET_SVC_LAYER_ID, &layerId) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } break; + + case AVIF_AOM_SCALE_SCALEMODE: { + aom_scaling_mode_t scaling_mode; + if (!avifFindAOMScalingMode(&layers[layerIndex].horizontalMode, &scaling_mode.h_scaling_mode)) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if (!avifFindAOMScalingMode(&layers[layerIndex].verticalMode, &scaling_mode.v_scaling_mode)) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } break; + } + } + unsigned char * monoUVPlane = NULL; if (monochromeRequested && !codec->internal->monochromeEnabled) { // The user requested monochrome (via alpha or YUV400) but libaom cannot currently support @@ -916,6 +1076,13 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) { encodeFlags |= AOM_EFLAG_FORCE_KF; } + if (extraLayerCount > 0) { + if (layerIndex > 0) { + encodeFlags |= AOM_EFLAG_NO_REF_GF | AOM_EFLAG_NO_REF_ARF | AOM_EFLAG_NO_REF_BWD | AOM_EFLAG_NO_REF_ARF2 | + AOM_EFLAG_NO_UPD_GF | AOM_EFLAG_NO_UPD_ARF; + } + } + aom_codec_err_t encodeErr = aom_codec_encode(&codec->internal->encoder, &aomImage, 0, 1, encodeFlags); avifFree(monoUVPlane); if (aomImageAllocated) { @@ -940,8 +1107,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) { - // Flush and clean up encoder resources early to save on overhead when encoding alpha or grid images + if ((addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) || ((extraLayerCount > 0) && (layerIndex == extraLayerCount))) { + // Flush and clean up encoder resources early to save on overhead when encoding alpha or grid images. + // (For layered image, this is when the last layer is encoded.) if (!aomCodecEncodeFinish(codec, output)) { return AVIF_RESULT_UNKNOWN_ERROR; diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 438ff5e415..b5179a65b2 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c @@ -51,6 +51,7 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + uint32_t layerIndex, uint32_t addImageFlags, avifCodecEncodeOutput * output) { @@ -59,6 +60,10 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec, RaConfig * rav1eConfig = NULL; RaFrame * rav1eFrame = NULL; + if (layerIndex != 0) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + if (!codec->internal->rav1eContext) { if (codec->csOptions->count > 0) { // None are currently supported! diff --git a/src/codec_svt.c b/src/codec_svt.c index 1ddf83e2ba..125adb6000 100644 --- a/src/codec_svt.c +++ b/src/codec_svt.c @@ -46,6 +46,7 @@ static avifResult svtCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + uint32_t layerIndex, uint32_t addImageFlags, avifCodecEncodeOutput * output) { @@ -54,6 +55,10 @@ static avifResult svtCodecEncodeImage(avifCodec * codec, EbBufferHeaderType * input_buffer = NULL; EbErrorType res = EB_ErrorNone; + if (layerIndex != 0) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + int y_shift = 0; // EbColorRange svt_range; if (alpha) { diff --git a/src/read.c b/src/read.c index bf5a7b567a..ec5b23b9f1 100644 --- a/src/read.c +++ b/src/read.c @@ -37,8 +37,6 @@ static const size_t xmpContentTypeSize = sizeof(xmpContentType); // can't be more than 4 unique tuples right now. #define MAX_IPMA_VERSION_AND_FLAGS_SEEN 4 -#define MAX_AV1_LAYER_COUNT 4 - // --------------------------------------------------------------------------- // Box data structures @@ -1608,7 +1606,7 @@ static avifBool avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, s } } - uint16_t dataReferenceIndex; // unsigned int(16) data_ref rence_index; + uint16_t dataReferenceIndex; // unsigned int(16) data_reference_index; CHECK(avifROStreamReadU16(&s, &dataReferenceIndex)); // uint64_t baseOffset; // unsigned int(base_offset_size*8) base_offset; CHECK(avifROStreamReadUX8(&s, &baseOffset, baseOffsetSize)); // @@ -1901,7 +1899,7 @@ static avifBool avifParseAV1LayeredImageIndexingProperty(avifProperty * prop, co } } - // Layer sizes will be validated layer (when the item's size is known) + // Layer sizes will be validated later (when the item's size is known) return AVIF_TRUE; } diff --git a/src/write.c b/src/write.c index ad2a95605f..1296567dfc 100644 --- a/src/write.c +++ b/src/write.c @@ -100,12 +100,25 @@ typedef struct avifEncoderItem uint32_t gridCols; // if non-zero (legal range [1-256]), this is a grid item uint32_t gridRows; // if non-zero (legal range [1-256]), this is a grid item + uint32_t extraLayerCount; + uint16_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id struct ipmaArray ipma; } avifEncoderItem; AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item); +// --------------------------------------------------------------------------- +// avifEncoderItemReference + +// pointer to one "item" interested in + +typedef struct avifEncoderItemReference +{ + avifEncoderItem * item; +} avifEncoderItemReference; +AVIF_ARRAY_DECLARE(avifEncoderItemReferenceArray, avifEncoderItemReference, ref); + // --------------------------------------------------------------------------- // avifEncoderFrame @@ -297,6 +310,8 @@ avifEncoder * avifEncoderCreate(void) encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS; encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; + encoder->extraLayerCount = 0; + encoder->extraLayerCountAlpha = 0; encoder->tileRowsLog2 = 0; encoder->tileColsLog2 = 0; encoder->speed = AVIF_SPEED_DEFAULT; @@ -595,6 +610,7 @@ static avifResult avifEncoderDataCreateXMPItem(avifEncoderData * data, const avi static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, + uint32_t layerCount, const avifImage * const * cellImages, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) @@ -606,10 +622,16 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return AVIF_RESULT_NO_CODEC_AVAILABLE; } + // Currently, layered image can only have one frame. + if (((encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0)) && encoder->data->frames.count > 0) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + // ----------------------------------------------------------------------- // Validate images const uint32_t cellCount = gridCols * gridRows; + const uint32_t imageCount = cellCount * layerCount; if (cellCount == 0) { return AVIF_RESULT_INVALID_ARGUMENT; } @@ -728,8 +750,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, // when encoding a single image. encoder->data->alphaPresent = AVIF_FALSE; - for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { - const avifImage * cellImage = cellImages[cellIndex]; + for (uint32_t imageIndex = 0; imageIndex < imageCount; ++imageIndex) { + const avifImage * cellImage = cellImages[imageIndex]; if (!avifImageIsOpaque(cellImage)) { encoder->data->alphaPresent = AVIF_TRUE; break; @@ -820,14 +842,18 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; if (item->codec) { - const avifImage * cellImage = cellImages[item->cellIndex]; - avifResult encodeResult = - item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, addImageFlags, item->encodeOutput); - if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) { - encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; - } - if (encodeResult != AVIF_RESULT_OK) { - return encodeResult; + item->extraLayerCount = item->alpha ? encoder->extraLayerCountAlpha : encoder->extraLayerCount; + for (uint32_t layerIndex = 0; layerIndex < item->extraLayerCount + 1; ++layerIndex) { + const uint32_t index = (layerIndex > (layerCount - 1)) ? (layerCount - 1) : layerIndex; + const avifImage * layerImage = cellImages[item->cellIndex * layerCount + index]; + avifResult encodeResult = + item->codec->encodeImage(item->codec, encoder, layerImage, item->alpha, layerIndex, addImageFlags, item->encodeOutput); + if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) { + encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; + } + if (encodeResult != AVIF_RESULT_OK) { + return encodeResult; + } } } } @@ -840,7 +866,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags); + return avifEncoderAddImageInternal(encoder, 1, 1, 1, &image, durationInTimescales, addImageFlags); } avifResult avifEncoderAddImageGrid(avifEncoder * encoder, @@ -853,7 +879,36 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { return AVIF_RESULT_INVALID_IMAGE_GRID; } - return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, 1, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported +} + + +avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags) +{ + avifDiagnosticsClearError(&encoder->diag); + if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { + return AVIF_RESULT_INVALID_LAYERS; + } + return avifEncoderAddImageInternal(encoder, 1, 1, layerCount, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported +} + +avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, + uint32_t gridCols, + uint32_t gridRows, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags) { + avifDiagnosticsClearError(&encoder->diag); + if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { + return AVIF_RESULT_INVALID_LAYERS; + } + if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, layerCount, layerImages, 1, addImageFlags); } static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size) @@ -889,7 +944,7 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } - if (item->encodeOutput->samples.count != encoder->data->frames.count) { + if (item->encodeOutput->samples.count != encoder->data->frames.count * (item->extraLayerCount + 1)) { return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } } @@ -990,25 +1045,29 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; - uint32_t contentSize = (uint32_t)item->metadataPayload.size; - if (item->encodeOutput->samples.count > 0) { - // This is choosing sample 0's size as there are two cases here: - // * This is a single image, in which case this is correct - // * This is an image sequence, but this file should still be a valid single-image avif, - // so there must still be a primary item pointing at a sync sample. Since the first - // frame of the image sequence is guaranteed to be a sync sample, it is chosen here. - // - // TODO: Offer the ability for a user to specify which frame in the sequence should - // become the primary item's image, and force that frame to be a keyframe. - contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size; - } + avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; + avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; + avifRWStreamWriteU16(&s, item->extraLayerCount + 1); // unsigned int(16) extent_count; - avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; - avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; - avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; - avifEncoderItemAddMdatFixup(item, &s); // - avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; - avifRWStreamWriteU32(&s, (uint32_t)contentSize); // unsigned int(length_size*8) extent_length; + for (uint32_t i = 0; i < item->extraLayerCount + 1; ++i) { + avifEncoderItemAddMdatFixup(item, &s); + avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; + + if (item->encodeOutput->samples.count == 0) { + avifRWStreamWriteU32(&s, (uint32_t)item->metadataPayload.size); // unsigned int(length_size*8) extent_length; + assert(item->extraLayerCount == 0); + } else { + // For non-layered image, this is choosing sample 0's size as there are two cases here: + // * This is a single image, in which case this is correct + // * This is an image sequence, but this file should still be a valid single-image avif, + // so there must still be a primary item pointing at a sync sample. Since the first + // frame of the image sequence is guaranteed to be a sync sample, it is chosen here. + // + // TODO: Offer the ability for a user to specify which frame in the sequence should + // become the primary item's image, and force that frame to be a keyframe. + avifRWStreamWriteU32(&s, (uint32_t)item->encodeOutput->samples.sample[i].data.size); // unsigned int(length_size*8) extent_length; + } + } } avifRWStreamFinishBox(&s, iloc); @@ -1099,15 +1158,16 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) continue; } - if (item->dimgFromID) { - // All image cells from a grid should share the exact same properties, so see if we've - // already written properties out for another cell in this grid, and if so, just steal - // their ipma and move on. This is a sneaky way to provide iprp deduplication. + if (item->dimgFromID && item->extraLayerCount == 0) { + // All image cells from a grid should share the exact same properties unless they are + // layered image which have different a1lx, so see if we've already written properties + // out for another cell in this grid, and if so, just steal their ipma and move on. + // This is a sneaky way to provide iprp deduplication. avifBool foundPreviousCell = AVIF_FALSE; for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) { avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex]; - if (item->dimgFromID == dedupItem->dimgFromID) { + if (item->dimgFromID == dedupItem->dimgFromID && dedupItem->extraLayerCount == 0) { // We've already written dedup's items out. Steal their ipma indices and move on! item->ipma = dedupItem->ipma; foundPreviousCell = AVIF_TRUE; @@ -1164,6 +1224,38 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup); } + + if (item->extraLayerCount != 0) { + // Layered Image Indexing Property + + avifItemPropertyDedupStart(dedup); + avifBoxMarker a1lx = avifRWStreamWriteBox(&dedup->s, "a1lx", AVIF_BOX_SIZE_TBD); + uint32_t layerSize[MAX_AV1_LAYER_COUNT - 1] = { 0 }; + avifBool largeSize = AVIF_FALSE; + + for (uint32_t validLayer = 0; validLayer < item->extraLayerCount; ++validLayer) { + uint32_t size = (uint32_t)item->encodeOutput->samples.sample[validLayer].data.size; + layerSize[validLayer] = size; + if (size > 0xffff) { + largeSize = AVIF_TRUE; + } + } + + avifRWStreamWriteU8(&dedup->s, largeSize); // unsigned int(7) reserved = 0; + // unsigned int(1) large_size; + + // FieldLength = (large_size + 1) * 16; + // unsigned int(FieldLength) layer_size[3]; + for (uint32_t layer = 0; layer < MAX_AV1_LAYER_COUNT - 1; ++layer) { + if (largeSize) { + avifRWStreamWriteU32(&dedup->s, layerSize[layer]); + } else { + avifRWStreamWriteU16(&dedup->s, (uint16_t)layerSize[layer]); + } + } + avifRWStreamFinishBox(&dedup->s, a1lx); + ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); + } } avifRWStreamFinishBox(&s, ipco); avifItemPropertyDedupDestroy(dedup); @@ -1441,11 +1533,17 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", AVIF_BOX_SIZE_TBD); const size_t mdatStartOffset = avifRWStreamOffset(&s); - for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) { + + avifEncoderItemReferenceArray layeredAlphaItems; + avifEncoderItemReferenceArray layeredColorItems; + avifArrayCreate(&layeredAlphaItems, sizeof(avifEncoderItemReference), 1); + avifArrayCreate(&layeredColorItems, sizeof(avifEncoderItemReference), 1); + avifBool useInterleave = (encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0); + + for (uint32_t itemPasses = 0; itemPasses < 2; ++itemPasses) { // Use multiple passes to pack in the following order: // * Pass 0: metadata (Exif/XMP) - // * Pass 1: alpha (AV1) - // * Pass 2: all other item data (AV1 color) + // * Pass 1: item data (AV1 color & alpha) // // See here for the discussion on alpha coming before color: // https://github.com/AOMediaCodec/libavif/issues/287 @@ -1455,7 +1553,6 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // and ignoreExif are enabled. // const avifBool metadataPass = (itemPasses == 0); - const avifBool alphaPass = (itemPasses == 1); for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; @@ -1468,13 +1565,24 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // only process metadata (XMP/Exif) payloads when metadataPass is true continue; } - if (alphaPass != item->alpha) { - // only process alpha payloads when alphaPass is true - continue; - } size_t chunkOffset = 0; + // Interleave - Pick out and record layered image items, interleave them later. + // Expect layer image item has same number of samples and fixups. + if (useInterleave && item->encodeOutput->samples.count > 0 && + item->encodeOutput->samples.count == item->mdatFixups.count) { + + avifEncoderItemReference * ref; + if (item->alpha) { + ref = (avifEncoderItemReference *)avifArrayPushPtr(&layeredAlphaItems); + } else { + ref = (avifEncoderItemReference *)avifArrayPushPtr(&layeredColorItems); + } + ref->item = item; + continue; + } + // Deduplication - See if an identical chunk to this has already been written if (item->encodeOutput->samples.count > 0) { avifEncodeSample * sample = &item->encodeOutput->samples.sample[0]; @@ -1511,6 +1619,64 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) } } } + + // Interleave each sample (layer) of each item. + // + // - for each layer + // - for each grid cell + // - write alpha of this layer of this grid cell + // - write color of this layer of this grid cell + // + // See here for the discussion on alpha coming before color: + // https://github.com/AOMediaCodec/libavif/issues/287 + + if (useInterleave) { + avifBool hasMoreSample; + uint32_t layerIndex = 0; + do { + hasMoreSample = AVIF_FALSE; + // Assume color and alpha having same number of items (both single image, or both grid of same dimension) + for (uint32_t itemIndex = 0; itemIndex < layeredColorItems.count; ++itemIndex) { + for (int samplePass = 0; samplePass < 2; ++samplePass) { + avifEncoderItemReferenceArray * currentItems = (samplePass == 0) ? &layeredAlphaItems : &layeredColorItems; + if (itemIndex >= currentItems->count) { + break; + } + + // TODO: Offer the ability for a user to specify which grid cell should be written first. + avifEncoderItem * item = currentItems->ref[itemIndex].item; + if (item->encodeOutput->samples.count <= layerIndex) { + // We've already written all samples from this item + continue; + } else if (item->encodeOutput->samples.count > layerIndex + 1) { + hasMoreSample = AVIF_TRUE; + } + avifRWData * data = &item->encodeOutput->samples.sample[layerIndex].data; + size_t chunkOffset = avifEncoderFindExistingChunk(&s, mdatStartOffset, data->data, data->size); + if (!chunkOffset) { + // We've never seen this chunk before; write it out + chunkOffset = avifRWStreamOffset(&s); + avifRWStreamWrite(&s, data->data, data->size); + if (samplePass == 0) { + encoder->ioStats.alphaOBUSize += data->size; + } else { + encoder->ioStats.colorOBUSize += data->size; + } + + size_t prevOffset = avifRWStreamOffset(&s); + avifRWStreamSetOffset(&s, item->mdatFixups.fixup[layerIndex].offset); + avifRWStreamWriteU32(&s, (uint32_t)chunkOffset); + avifRWStreamSetOffset(&s, prevOffset); + } + } + } + ++layerIndex; + } while (hasMoreSample); + } + + avifArrayDestroy(&layeredColorItems); + avifArrayDestroy(&layeredAlphaItems); + avifRWStreamFinishBox(&s, mdat); // -----------------------------------------------------------------------