From 257aa12e6842da8d5a3b7f38e42612c2fe7a66da Mon Sep 17 00:00:00 2001 From: Zetrin0 Date: Wed, 29 Nov 2023 12:42:55 +0000 Subject: [PATCH] AVIF/AVIFS support using libavif Bring back AVIF/AVIFS support. libheif does not support animated avifs Signed-off-by: Zetrin0 --- .github/workflows/ci.yml | 4 +- README.md | 1 + meson.build | 6 ++ meson_options.txt | 4 + src/formats/avif.c | 166 +++++++++++++++++++++++++++++++++++++++ src/formats/loader.c | 12 +++ 6 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/formats/avif.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1e90ebd..7b7ed585 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,8 @@ jobs: build-essential meson pkg-config wayland-protocols libwayland-dev libjson-c-dev libxkbcommon-dev libfreetype-dev libfontconfig-dev - libopenexr-dev libgif-dev libheif-dev libjpeg-dev - librsvg2-dev libtiff-dev libwebp-dev + libopenexr-dev libgif-dev libheif-dev libavif-dev + libjpeg-dev librsvg2-dev libtiff-dev libwebp-dev - name: Check out source code uses: actions/checkout@v3 diff --git a/README.md b/README.md index cedded73..11718f6e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ the image directly in a terminal window. - SVG (via [librsvg](https://gitlab.gnome.org/GNOME/librsvg)); - WebP (via [libwebp](https://chromium.googlesource.com/webm/libwebp)); - HEIF/AVIF (via [libheif](https://github.com/strukturag/libheif)); +- AV1F/AVIFS (via [libavif](https://github.com/AOMediaCodec/libavif)); - TIFF (via [libtiff](https://libtiff.gitlab.io/libtiff)); - EXR (via [OpenEXR](https://openexr.com)); - BMP (built-in); diff --git a/meson.build b/meson.build index 84cc0a54..ac8188b9 100644 --- a/meson.build +++ b/meson.build @@ -45,6 +45,7 @@ rt = cc.find_library('rt') exr = dependency('OpenEXR', version: '>=3.1', required: get_option('exr')) gif = cc.find_library('gif', required: get_option('gif')) heif = dependency('libheif', required: get_option('heif')) +avif = dependency('libavif', required: get_option('avif')) jpeg = dependency('libjpeg', required: get_option('jpeg')) png = dependency('libpng', required: get_option('png')) rsvg = dependency('librsvg-2.0', version: '>=2.46', required: get_option('svg')) @@ -68,6 +69,7 @@ conf = configuration_data() conf.set('HAVE_LIBEXR', exr.found()) conf.set('HAVE_LIBGIF', gif.found()) conf.set('HAVE_LIBHEIF', heif.found()) +conf.set('HAVE_LIBAVIF', avif.found()) conf.set('HAVE_LIBJPEG', jpeg.found()) conf.set('HAVE_LIBJXL', jxl.found()) conf.set('HAVE_LIBPNG', png.found()) @@ -171,6 +173,9 @@ endif if heif.found() sources += 'src/formats/heif.c' endif +if avif.found() + sources += 'src/formats/avif.c' +endif if jpeg.found() sources += 'src/formats/jpeg.c' endif @@ -208,6 +213,7 @@ executable( exr, gif, heif, + avif, jpeg, jxl, png, diff --git a/meson_options.txt b/meson_options.txt index f6cded6f..b45a53f3 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -11,6 +11,10 @@ option('heif', type: 'feature', value: 'auto', description: 'Enable HEIF and AVIF format support') +option('avif', + type: 'feature', + value: 'auto', + description: 'Enable AVIF and AVIFS format support') option('jpeg', type: 'feature', value: 'auto', diff --git a/src/formats/avif.c b/src/formats/avif.c new file mode 100644 index 00000000..ede054b3 --- /dev/null +++ b/src/formats/avif.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +// AV1 (AVIF/AVIFS) format decoder. +// Copyright (C) 2023 Artem Senichev + +#include "loader.h" +#include "src/image.h" + +#include +#include +#include +#include + + +// AVI signature +static const uint32_t signature = 'f' | 't' << 8 | 'y' << 16 | 'p' << 24; +#define SIGNATURE_OFFSET 4 + +static int decode_frame(struct image* ctx, avifDecoder *decoder) +{ + avifRGBImage rgb; + avifResult rc; + struct image_frame* frame; + + rc = avifDecoderNextImage(decoder); + if (rc != AVIF_RESULT_OK) { + goto decode_fail; + } + + memset(&rgb, 0, sizeof(rgb)); + avifRGBImageSetDefaults(&rgb, decoder->image); + + rgb.depth = 8; + rgb.format = AVIF_RGB_FORMAT_BGRA; + avifRGBImageAllocatePixels(&rgb); + + rc = avifImageYUVToRGB(decoder->image, &rgb); + if (rc != AVIF_RESULT_OK) { + goto fail_pixels; + } + + frame = image_create_frame(ctx, decoder->image->width, + decoder->image->height); + if (!frame) { + goto fail_pixels; + } + + memcpy((void*)frame->data, rgb.pixels, + rgb.width * rgb.height * sizeof(argb_t)); + + avifRGBImageFreePixels(&rgb); + return 0; + +fail_pixels: + avifRGBImageFreePixels(&rgb); +decode_fail: + image_print_error(ctx, "AV1 decode failed: %s", avifResultToString(rc)); + return -1; +} + +static int decode_frames(struct image* ctx, avifDecoder *decoder) +{ + avifImageTiming timing; + avifRGBImage rgb; + avifResult rc; + + if (!image_create_frames(ctx, decoder->imageCount)) { + goto decode_fail; + } + + for (size_t i = 0; i < ctx->num_frames; ++i) { + rc = avifDecoderNthImage(decoder, i); + if (rc != AVIF_RESULT_OK) { + goto decode_fail; + } + + avifRGBImageSetDefaults(&rgb, decoder->image); + rgb.depth = 8; + rgb.format = AVIF_RGB_FORMAT_BGRA; + + avifRGBImageAllocatePixels(&rgb); + + rc = avifImageYUVToRGB(decoder->image, &rgb); + if (rc != AVIF_RESULT_OK) { + goto fail_pixels; + } + + if (!image_frame_allocate(&ctx->frames[i], rgb.width, rgb.height)) { + goto fail_pixels; + } + + rc = avifDecoderNthImageTiming(decoder, i, &timing); + if (rc != AVIF_RESULT_OK) { + goto fail_pixels; + } + + ctx->frames[i].duration = (size_t)(1000.0f / (float)timing.timescale * (float)timing.durationInTimescales); + + memcpy((void*)ctx->frames[i].data, rgb.pixels, + rgb.width * rgb.height * sizeof(argb_t)); + + avifRGBImageFreePixels(&rgb); + } + + return 0; + +fail_pixels: + avifRGBImageFreePixels(&rgb); +decode_fail: + return -1; +} + +// AV1 loader implementation +enum loader_status decode_avif(struct image* ctx, const uint8_t* data, + size_t size) +{ + avifResult rc; + avifDecoder* decoder = NULL; + int ret; + + // check signature + if (size < SIGNATURE_OFFSET + sizeof(signature) || + *(const uint32_t*)(data + SIGNATURE_OFFSET) != signature) { + return ldr_unsupported; + } + + // open file in decoder + decoder = avifDecoderCreate(); + if (!decoder) { + image_print_error(ctx, "unable to create av1 decoder"); + return ldr_fmterror; + } + rc = avifDecoderSetIOMemory(decoder, data, size); + if (rc != AVIF_RESULT_OK) { + goto fail; + } + rc = avifDecoderParse(decoder); + if (rc != AVIF_RESULT_OK) { + goto fail; + } + + if (decoder->imageCount > 1) { + ret = decode_frames(ctx, decoder); + } else { + ret = decode_frame(ctx, decoder); + } + + if (ret != 0) { + goto fail; + } + + ctx->alpha = decoder->alphaPresent; + + image_set_format(ctx, "AV1 %dbpc %s", decoder->image->depth, + avifPixelFormatToString(decoder->image->yuvFormat)); + + avifDecoderDestroy(decoder); + return ldr_success; + +fail: + avifDecoderDestroy(decoder); + if (rc != AVIF_RESULT_OK) { + image_print_error(ctx, "error decoding av1: %s\n", avifResultToString(rc)); + } + image_free_frames(ctx); + return ldr_fmterror; +} diff --git a/src/formats/loader.c b/src/formats/loader.c index bcbcd655..0bf901b9 100644 --- a/src/formats/loader.c +++ b/src/formats/loader.c @@ -32,6 +32,12 @@ const char* supported_formats = "bmp, pnm" #ifdef HAVE_LIBHEIF ", heif, avif" #endif +#ifdef HAVE_LIBAVIF +#ifndef HAVE_LIBHEIF + ", avif" +#endif + ", avifs" +#endif #ifdef HAVE_LIBJXL ", jxl" #endif @@ -55,6 +61,9 @@ LOADER_DECLARE(gif); #ifdef HAVE_LIBHEIF LOADER_DECLARE(heif); #endif +#ifdef HAVE_LIBAVIF +LOADER_DECLARE(avif); +#endif #ifdef HAVE_LIBJPEG LOADER_DECLARE(jpeg); #endif @@ -93,6 +102,9 @@ static const image_decoder decoders[] = { #ifdef HAVE_LIBHEIF &LOADER_FUNCTION(heif), #endif +#ifdef HAVE_LIBAVIF + &LOADER_FUNCTION(avif), +#endif #ifdef HAVE_LIBRSVG &LOADER_FUNCTION(svg), #endif