Skip to content

Commit

Permalink
AVIF/AVIFS support using libavif
Browse files Browse the repository at this point in the history
Bring back AVIF/AVIFS support.
libheif does not support animated avifs

Signed-off-by: Zetrin0 <[email protected]>
  • Loading branch information
appashchenko authored Nov 29, 2023
1 parent c5464fd commit 257aa12
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand All @@ -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())
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -208,6 +213,7 @@ executable(
exr,
gif,
heif,
avif,
jpeg,
jxl,
png,
Expand Down
4 changes: 4 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
166 changes: 166 additions & 0 deletions src/formats/avif.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT
// AV1 (AVIF/AVIFS) format decoder.
// Copyright (C) 2023 Artem Senichev <[email protected]>

#include "loader.h"
#include "src/image.h"

#include <avif/avif.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


// 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;
}
12 changes: 12 additions & 0 deletions src/formats/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 257aa12

Please sign in to comment.