2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ jobs:
- name: Run AVIF Tests (on Linux)
if: runner.os == 'Linux'
working-directory: ./build
run: ./aviftest ../tests/data
run: ./aviftest ../tests/data --io-only
47 changes: 43 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.9.3] - 2021-10-20

### Added
* Support for progressive AVIFs and operating point selection
* Add automatic tile scaling to the item's ispe or track's dims
* Add diagnostic messages for AV1 decode failures
* avifdec: Add PNG compression level arg
* Make image size limit configurable, expose to avifdec
* Add the AVIF_STRICT_ALPHA_ISPE_REQUIRED flag

### Changed
* Mandate ispe and disallow zero width or height (#640).
* Re-map libavif speed 7-10 to libaom speed 7-9 (#682)
* Refer to https://aomedia-review.googlesource.com/c/aom/+/140624
* If you were using libaom with the following avif speed setting:
* - speed 0-6: no change is needed
* - speed 7: change to speed 6 for the same results
* - speed 8-9: re-test and re-adjust speed according to your app needs
* Update aom.cmd: v3.2.0
* Update dav1d.cmd: 0.9.2
* Pass TestCase's minQuantizer, maxQuantizer, speed to encoder.
* Regenerate tests.json
* Disable JSON-based tests for now, the metrics are inconsistent/unreliable
* Set diagnostic message for aom_codec_set_option()
* Re-map libavif-libaom speed settings (#682)
* Bump of version in CMakeLists.txt was forgotten
* avifdec: Better message for unsupported file extension
* Do not copy input image when encoding with libaom unless width or height is 1
* Fix the comment for AVIF_STRICT_PIXI_REQUIRED
* Update libavif.pc.cmake (#692)
* In 32-bit builds set dav1d's frame_size_limit setting to 8192*8192
* Allocate alpha alongside YUV (if necessary) during y4m decode to avoid incorrect alphaRowBytes math
* Change avif_decode_fuzzer to be more like Chrome
* Update codec_dav1d.c for the new threading model
* Generalized ipco property deduplication
* Rename avifParseMoovBox to avifParseMovieBox for consistency
* Simplify idat storage for avifMeta structure (#756)
* Fix oss-fuzz coverage build failure of dav1d
* Redesign AVIF_DECODER_SOURCE_AUTO to honor the FileTypeBox's major brand
* Use "C420" as default Y4M color space parameter

## [0.9.2] - 2021-06-23

### Added

* avifenc, avifdec: Allow "-j all" to automatically use all of the cores on the machine (#670)

### Changed

* Refactor imir implementation to match HEIF Draft Amendment 2 (#665)
* Merge avifCodec's open call with its getNextImage call to avoid codec init during parse, and simplify the codec API (#637)
* Update aom.cmd: v3.1.1 (#674)
Expand Down Expand Up @@ -44,7 +83,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Declare the param of avifDumpDiagnostics as const (#633)
* Adjust gdk-pixbuf loader for new API change (#668)
* Fix gdk-pixbuf loader install path (#615)
* Don't need to disable MSVC warnings 5031 and 5032 (#681)

## [0.9.1] - 2021-05-19

Expand Down Expand Up @@ -664,7 +702,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Constants `AVIF_VERSION`, `AVIF_VERSION_MAJOR`, `AVIF_VERSION_MINOR`, `AVIF_VERSION_PATCH`
- `avifVersion()` function

[Unreleased]: https://github.com/AOMediaCodec/libavif/compare/v0.9.2...HEAD
[Unreleased]: https://github.com/AOMediaCodec/libavif/compare/v0.9.3...HEAD
[0.9.3]: https://github.com/AOMediaCodec/libavif/compare/v0.9.2...v0.9.3
[0.9.2]: https://github.com/AOMediaCodec/libavif/compare/v0.9.1...v0.9.2
[0.9.1]: https://github.com/AOMediaCodec/libavif/compare/v0.9.0...v0.9.1
[0.9.0]: https://github.com/AOMediaCodec/libavif/compare/v0.8.4...v0.9.0
Expand Down
8 changes: 4 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.5)
# and find_package()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules")

project(libavif LANGUAGES C VERSION 0.9.1)
project(libavif LANGUAGES C VERSION 0.9.3)

# Set C99 as the default
set(CMAKE_C_STANDARD 99)
Expand All @@ -19,7 +19,7 @@ set(CMAKE_C_STANDARD 99)
# Increment MINOR. Set PATCH to 0
# If the source code was changed, but there were no interface changes:
# Increment PATCH.
set(LIBRARY_VERSION_MAJOR 12)
set(LIBRARY_VERSION_MAJOR 13)
set(LIBRARY_VERSION_MINOR 0)
set(LIBRARY_VERSION_PATCH 0)
set(LIBRARY_VERSION "${LIBRARY_VERSION_MAJOR}.${LIBRARY_VERSION_MINOR}.${LIBRARY_VERSION_PATCH}")
Expand Down Expand Up @@ -206,6 +206,7 @@ set(AVIF_SRCS
src/read.c
src/reformat.c
src/reformat_libyuv.c
src/scale.c
src/stream.c
src/utils.c
src/write.c
Expand Down Expand Up @@ -554,8 +555,6 @@ if(AVIF_BUILD_TESTS)
endif()
endif()

configure_file(libavif.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libavif.pc @ONLY)

if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL)
install(TARGETS avif
EXPORT ${PROJECT_NAME}-config
Expand All @@ -577,6 +576,7 @@ if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL)
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
endif()

configure_file(libavif.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libavif.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libavif.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()
Expand Down
147 changes: 106 additions & 41 deletions apps/avifdec.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ static void syntax(void)
printf(" -c,--codec C : AV1 codec to use (choose from versions list below)\n");
printf(" -d,--depth D : Output depth [8,16]. (PNG only; For y4m, depth is retained, and JPEG is always 8bpc)\n");
printf(" -q,--quality Q : Output quality [0-100]. (JPEG only, default: %d)\n", DEFAULT_JPEG_QUALITY);
printf(" --png-compress L : Set PNG compression level (PNG only; 0-9, 0=none, 9=max). Defaults to libpng's builtin default.\n");
printf(" -u,--upsampling U : Chroma upsampling (for 420/422). automatic (default), fastest, best, nearest, or bilinear\n");
printf(" -r,--raw-color : Output raw RGB values instead of multiplying by alpha when saving to opaque formats\n");
printf(" (JPEG only; not applicable to y4m)\n");
printf(" --index : When decoding an image sequence or progressive image, specify which frame index to decode (Default: 0)\n");
printf(" --progressive : Enable progressive AVIF processing. If a progressive image is encountered and --progressive is passed,\n");
printf(" avifdec will use --index to choose which layer to decode (in progressive order).\n");
printf(" --no-strict : Disable strict decoding, which disables strict validation checks and errors\n");
printf(" -i,--info : Decode all frames and display all image information instead of saving to disk\n");
printf(" --ignore-icc : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n");
printf(" --size-limit C : Specifies the image size limit (in total pixels) that should be tolerated.\n");
printf(" Default: %u, set to a smaller value to further restrict.\n", AVIF_DEFAULT_IMAGE_SIZE_LIMIT);
printf("\n");
avifPrintVersions();
}
Expand All @@ -50,12 +56,16 @@ int main(int argc, char * argv[])
int requestedDepth = 0;
int jobs = 1;
int jpegQuality = DEFAULT_JPEG_QUALITY;
int pngCompressionLevel = -1; // -1 is a sentinel to avifPNGWrite() to skip calling png_set_compression_level()
avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO;
avifBool infoOnly = AVIF_FALSE;
avifChromaUpsampling chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC;
avifBool ignoreICC = AVIF_FALSE;
avifBool rawColor = AVIF_FALSE;
avifBool allowProgressive = AVIF_FALSE;
avifStrictFlags strictFlags = AVIF_STRICT_ENABLED;
uint32_t frameIndex = 0;
uint32_t imageSizeLimit = AVIF_DEFAULT_IMAGE_SIZE_LIMIT;

if (argc < 2) {
syntax();
Expand Down Expand Up @@ -110,6 +120,14 @@ int main(int argc, char * argv[])
} else if (jpegQuality > 100) {
jpegQuality = 100;
}
} else if (!strcmp(arg, "--png-compress")) {
NEXTARG();
pngCompressionLevel = atoi(arg);
if (pngCompressionLevel < 0) {
pngCompressionLevel = 0;
} else if (pngCompressionLevel > 9) {
pngCompressionLevel = 9;
}
} else if (!strcmp(arg, "-u") || !strcmp(arg, "--upsampling")) {
NEXTARG();
if (!strcmp(arg, "automatic")) {
Expand All @@ -128,12 +146,24 @@ int main(int argc, char * argv[])
}
} else if (!strcmp(arg, "-r") || !strcmp(arg, "--raw-color")) {
rawColor = AVIF_TRUE;
} else if (!strcmp(arg, "--progressive")) {
allowProgressive = AVIF_TRUE;
} else if (!strcmp(arg, "--index")) {
NEXTARG();
frameIndex = (uint32_t)atoi(arg);
} else if (!strcmp(arg, "--no-strict")) {
strictFlags = AVIF_STRICT_DISABLED;
} else if (!strcmp(arg, "-i") || !strcmp(arg, "--info")) {
infoOnly = AVIF_TRUE;
} else if (!strcmp(arg, "--ignore-icc")) {
ignoreICC = AVIF_TRUE;
} else if (!strcmp(arg, "--size-limit")) {
NEXTARG();
imageSizeLimit = strtoul(arg, NULL, 10);
if ((imageSizeLimit > AVIF_DEFAULT_IMAGE_SIZE_LIMIT) || (imageSizeLimit == 0)) {
fprintf(stderr, "ERROR: invalid image size limit: %s\n", arg);
return 1;
}
} else {
// Positional argument
if (!inputFilename) {
Expand Down Expand Up @@ -164,7 +194,9 @@ int main(int argc, char * argv[])
avifDecoder * decoder = avifDecoderCreate();
decoder->maxThreads = jobs;
decoder->codecChoice = codecChoice;
decoder->imageSizeLimit = imageSizeLimit;
decoder->strictFlags = strictFlags;
decoder->allowProgressive = allowProgressive;
avifResult result = avifDecoderSetIOFile(decoder, inputFilename);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "Cannot open file for read: %s\n", inputFilename);
Expand All @@ -182,17 +214,30 @@ int main(int argc, char * argv[])
decoder->durationInTimescales,
decoder->imageCount,
(decoder->imageCount == 1) ? "" : "s");
printf(" * Frames:\n");
if (decoder->imageCount > 1) {
printf(" * %s Frames: (%u expected frames)\n",
(decoder->progressiveState != AVIF_PROGRESSIVE_STATE_UNAVAILABLE) ? "Progressive Image" : "Image Sequence",
decoder->imageCount);
} else {
printf(" * Frame:\n");
}

int frameIndex = 0;
while (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) {
printf(" * Decoded frame [%d] [pts %2.2f (%" PRIu64 " timescales)] [duration %2.2f (%" PRIu64 " timescales)]\n",
frameIndex,
int currIndex = 0;
avifResult nextImageResult;
while ((nextImageResult = avifDecoderNextImage(decoder)) == AVIF_RESULT_OK) {
printf(" * Decoded frame [%d] [pts %2.2f (%" PRIu64 " timescales)] [duration %2.2f (%" PRIu64 " timescales)] [%ux%u]\n",
currIndex,
decoder->imageTiming.pts,
decoder->imageTiming.ptsInTimescales,
decoder->imageTiming.duration,
decoder->imageTiming.durationInTimescales);
++frameIndex;
decoder->imageTiming.durationInTimescales,
decoder->image->width,
decoder->image->height);
++currIndex;
}
if (nextImageResult != AVIF_RESULT_NO_IMAGES_REMAINING) {
printf("ERROR: Failed to decode frame: %s\n", avifResultToString(nextImageResult));
avifDumpDiagnostics(&decoder->diag);
}
} else {
printf("ERROR: Failed to decode image: %s\n", avifResultToString(result));
Expand All @@ -214,52 +259,72 @@ int main(int argc, char * argv[])
(jobs == 1) ? "" : "s");

int returnCode = 0;
avifImage * avif = avifImageCreateEmpty();
avifDecoder * decoder = avifDecoderCreate();
decoder->maxThreads = jobs;
decoder->codecChoice = codecChoice;
decoder->imageSizeLimit = imageSizeLimit;
decoder->strictFlags = strictFlags;
avifResult decodeResult = avifDecoderReadFile(decoder, avif, inputFilename);
if (decodeResult == AVIF_RESULT_OK) {
printf("Image decoded: %s\n", inputFilename);
printf("Image details:\n");
avifImageDump(avif, 0, 0);
decoder->allowProgressive = allowProgressive;

if (ignoreICC && (avif->icc.size > 0)) {
printf("[--ignore-icc] Discarding ICC profile.\n");
avifImageSetProfileICC(avif, NULL, 0);
}
avifResult result = avifDecoderSetIOFile(decoder, inputFilename);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "Cannot open file for read: %s\n", inputFilename);
returnCode = 1;
goto cleanup;
}

result = avifDecoderParse(decoder);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to parse image: %s\n", avifResultToString(result));
returnCode = 1;
goto cleanup;
}

result = avifDecoderNthImage(decoder, frameIndex);
if (result != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to decode image: %s\n", avifResultToString(result));
returnCode = 1;
goto cleanup;
}

avifAppFileFormat outputFormat = avifGuessFileFormat(outputFilename);
if (outputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
fprintf(stderr, "Cannot determine output file extension: %s\n", outputFilename);
printf("Image decoded: %s\n", inputFilename);
printf("Image details:\n");
avifImageDump(decoder->image, 0, 0, decoder->progressiveState);

if (ignoreICC && (decoder->image->icc.size > 0)) {
printf("[--ignore-icc] Discarding ICC profile.\n");
avifImageSetProfileICC(decoder->image, NULL, 0);
}

avifAppFileFormat outputFormat = avifGuessFileFormat(outputFilename);
if (outputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
fprintf(stderr, "Cannot determine output file extension: %s\n", outputFilename);
returnCode = 1;
} else if (outputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
if (!y4mWrite(outputFilename, decoder->image)) {
returnCode = 1;
} else if (outputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
if (!y4mWrite(outputFilename, avif)) {
returnCode = 1;
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
// Bypass alpha multiply step during conversion
if (rawColor) {
avif->alphaPremultiplied = AVIF_TRUE;
}
if (!avifJPEGWrite(outputFilename, avif, jpegQuality, chromaUpsampling)) {
returnCode = 1;
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_PNG) {
if (!avifPNGWrite(outputFilename, avif, requestedDepth, chromaUpsampling)) {
returnCode = 1;
}
} else {
fprintf(stderr, "Unrecognized file extension: %s\n", outputFilename);
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
// Bypass alpha multiply step during conversion
if (rawColor) {
decoder->image->alphaPremultiplied = AVIF_TRUE;
}
if (!avifJPEGWrite(outputFilename, decoder->image, jpegQuality, chromaUpsampling)) {
returnCode = 1;
}
} else if (outputFormat == AVIF_APP_FILE_FORMAT_PNG) {
if (!avifPNGWrite(outputFilename, decoder->image, requestedDepth, chromaUpsampling, pngCompressionLevel)) {
returnCode = 1;
}
} else {
printf("ERROR: Failed to decode image: %s\n", avifResultToString(decodeResult));
avifDumpDiagnostics(&decoder->diag);
fprintf(stderr, "Unsupported output file extension: %s\n", outputFilename);
returnCode = 1;
}

cleanup:
if (returnCode != 0) {
avifDumpDiagnostics(&decoder->diag);
}
avifDecoderDestroy(decoder);
avifImageDestroy(avif);
return returnCode;
}
2 changes: 1 addition & 1 deletion apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ 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]);
avifImageDump(gridCells ? gridCells[0] : image, gridDims[0], gridDims[1], 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),
Expand Down
9 changes: 7 additions & 2 deletions apps/shared/avifpng.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ avifBool avifPNGRead(const char * inputFilename, avifImage * avif, avifPixelForm
avif->yuvFormat = requestedFormat;
if (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
// Identity is only valid with YUV444.
avif->yuvFormat = (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) ? AVIF_PIXEL_FORMAT_YUV444 : AVIF_APP_DEFAULT_PIXEL_FORMAT;
avif->yuvFormat = (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) ? AVIF_PIXEL_FORMAT_YUV444
: AVIF_APP_DEFAULT_PIXEL_FORMAT;
}
avif->depth = requestedDepth;
if (avif->depth == 0) {
Expand Down Expand Up @@ -160,7 +161,7 @@ avifBool avifPNGRead(const char * inputFilename, avifImage * avif, avifPixelForm
return readResult;
}

avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint32_t requestedDepth, avifChromaUpsampling chromaUpsampling)
avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint32_t requestedDepth, avifChromaUpsampling chromaUpsampling, int compressionLevel)
{
volatile avifBool writeResult = AVIF_FALSE;
png_structp png = NULL;
Expand Down Expand Up @@ -217,6 +218,10 @@ avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint3
// It is up to the enduser to decide if they want to keep their ICC profiles or not.
png_set_option(png, PNG_SKIP_sRGB_CHECK_PROFILE, 1);

if (compressionLevel >= 0) {
png_set_compression_level(png, compressionLevel);
}

png_set_IHDR(png, info, avif->width, avif->height, rgb.depth, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
if (avif->icc.data && (avif->icc.size > 0)) {
png_set_iCCP(png, info, "libavif", 0, avif->icc.data, (png_uint_32)avif->icc.size);
Expand Down
Loading