Skip to content

Commit

Permalink
chafa: Add AVIF format support
Browse files Browse the repository at this point in the history
  • Loading branch information
hpjansson committed Sep 15, 2023
1 parent 1c2a91d commit 13dd60c
Show file tree
Hide file tree
Showing 8 changed files with 584 additions and 168 deletions.
15 changes: 15 additions & 0 deletions configure.ac
Expand Up @@ -111,6 +111,18 @@ AS_IF([test "$with_tools" != no], [
with_imagemagick=no)])
AS_IF([test "$with_imagemagick" != no], [AC_DEFINE([HAVE_MAGICKWAND], [1], [Define if we have ImageMagick support.])])
dnl libavif (optional)
AC_ARG_WITH(avif,
[AS_HELP_STRING([--without-avif], [don't build AVIF loader [default=on]])],
,
with_avif=yes)
AS_IF([test "$with_avif" != no], [
PKG_CHECK_MODULES(AVIF, [libavif],,
missing_rpms="$missing_rpms libavif-devel"
missing_debs="$missing_debs libavif-dev"
with_avif=no)])
AS_IF([test "$with_avif" != no], [AC_DEFINE([HAVE_AVIF], [1], [Define if we have AVIF support.])])
dnl libjpeg (optional)
AC_ARG_WITH(jpeg,
[AS_HELP_STRING([--without-jpeg], [don't build JPEG loader [default=on]])],
Expand Down Expand Up @@ -163,6 +175,7 @@ AM_CONDITIONAL([HAVE_JPEG], [test "$with_tools" != no -a "$with_jpeg" != no])
AM_CONDITIONAL([HAVE_SVG], [test "$with_tools" != no -a "$with_svg" != no])
AM_CONDITIONAL([HAVE_TIFF], [test "$with_tools" != no -a "$with_tiff" != no])
AM_CONDITIONAL([HAVE_WEBP], [test "$with_tools" != no -a "$with_webp" != no])
AM_CONDITIONAL([HAVE_AVIF], [test "$with_tools" != no -a "$with_avif" != no])

# Used by gtk-doc's fixxref.
GLIB_PREFIX="`$PKG_CONFIG --variable=prefix glib-2.0`"
Expand Down Expand Up @@ -557,6 +570,7 @@ colorize_vars="
with_svg
with_tiff
with_webp
with_avif
"

dnl Only use colors if the terminal supports the aixterm-style bright ones (16 total).
Expand Down Expand Up @@ -612,6 +626,7 @@ echo >&AS_MESSAGE_FD
echo >&AS_MESSAGE_FD "Build command-line tool ..... $pwith_tools"

if test "x$with_tools" != xno; then
echo >&AS_MESSAGE_FD "With AVIF loader ............ $pwith_avif"
echo >&AS_MESSAGE_FD "With GIF loader ............. $pyes (internal)"
echo >&AS_MESSAGE_FD "With ImageMagick loader ..... $pwith_imagemagick"
echo >&AS_MESSAGE_FD "With JPEG loader ............ $pwith_jpeg"
Expand Down
12 changes: 10 additions & 2 deletions tools/chafa/Makefile.am
Expand Up @@ -19,6 +19,8 @@ chafa_SOURCES = \
png-loader.h \
named-colors.c \
named-colors.h \
util.c \
util.h \
xwd-loader.c \
xwd-loader.h

Expand All @@ -28,6 +30,12 @@ chafa_SOURCES += \
im-loader.h
endif

if HAVE_AVIF
chafa_SOURCES += \
avif-loader.c \
avif-loader.h
endif

if HAVE_JPEG
chafa_SOURCES += \
jpeg-loader.c \
Expand Down Expand Up @@ -58,11 +66,11 @@ endif
#
# This is disabled by default.

chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(MAGICKWAND_CFLAGS) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(FREETYPE_CFLAGS)
chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(MAGICKWAND_CFLAGS) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(AVIF_CFLAGS) $(FREETYPE_CFLAGS)
if ENABLE_RPATH
chafa_LDFLAGS = $(CHAFA_LDFLAGS) -rpath $(libdir)
endif
chafa_LDADD = $(GLIB_LIBS) $(MAGICKWAND_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la $(WIN32_LDADD)
chafa_LDADD = $(GLIB_LIBS) $(MAGICKWAND_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(AVIF_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la $(WIN32_LDADD)

# On Microsoft Windows, we compile a resource file with windres and link it in.
# This enables UTF-8 support in filenames, environment variables, etc.
Expand Down
273 changes: 273 additions & 0 deletions tools/chafa/avif-loader.c
@@ -0,0 +1,273 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/* Copyright (C) 2018-2023 Hans Petter Jansson
*
* This file is part of Chafa, a program that turns images into character art.
*
* Chafa is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chafa is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Chafa. If not, see <http://www.gnu.org/licenses/>. */

#include "config.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <chafa.h>
#include <avif/avif.h>
#include "avif-loader.h"
#include "util.h"

#define N_CHANNELS 4
#define BYTES_PER_PIXEL N_CHANNELS

struct AvifLoader
{
FileMapping *mapping;
const guint8 *file_data;
size_t file_data_len;
gpointer frame_data;
guint width, height;
guint rowstride;
avifDecoder *decoder;
gint current_frame_index;
guint frame_is_decoded : 1;
guint frame_is_success : 1;
};

static RotationType rotation [4] [3] =
{
{ ROTATION_180_MIRROR, ROTATION_0_MIRROR, ROTATION_NONE },
{ ROTATION_270_MIRROR, ROTATION_90_MIRROR, ROTATION_270 },
{ ROTATION_0_MIRROR, ROTATION_180_MIRROR, ROTATION_180 },
{ ROTATION_90_MIRROR, ROTATION_270_MIRROR, ROTATION_90 }
};

static RotationType
calc_rotation (avifTransformFlags tflags, guint angle, guint axis)
{
guint8 rot, mir;

if (angle > 3 || axis > 1)
return ROTATION_NONE;

rot = (tflags & AVIF_TRANSFORM_IROT) ? angle : 0;
mir = (tflags & AVIF_TRANSFORM_IMIR) ? axis : 2;

return rotation [rot] [mir];
}

static gboolean
maybe_decode_frame (AvifLoader *loader)
{
avifResult avif_result;
avifImage *image;
avifRGBImage rgb;

if (loader->frame_is_decoded)
return loader->frame_is_success;

avif_result = avifDecoderNextImage (loader->decoder);
loader->frame_is_success = (avif_result == AVIF_RESULT_OK ? TRUE : FALSE);
if (!loader->frame_is_success)
goto out;

image = loader->decoder->image;
avifRGBImageSetDefaults (&rgb, image);

rgb.depth = 8;
rgb.format = AVIF_RGB_FORMAT_RGBA;
rgb.rowBytes = image->width * BYTES_PER_PIXEL;
rgb.pixels = g_malloc (image->height * rgb.rowBytes);

avif_result = avifImageYUVToRGB(image, &rgb);
if (avif_result != AVIF_RESULT_OK)
goto out;

loader->width = image->width;
loader->height = image->height;
loader->rowstride = rgb.rowBytes;

g_free (loader->frame_data);
loader->frame_data = rgb.pixels;

rotate_image ((guchar **) &loader->frame_data,
&loader->width, &loader->height,
&loader->rowstride, N_CHANNELS,
calc_rotation (image->transformFlags,
image->irot.angle, image->imir.axis));

out:
return loader->frame_is_success;
}

static AvifLoader *
avif_loader_new (void)
{
return g_new0 (AvifLoader, 1);
}

AvifLoader *
avif_loader_new_from_mapping (FileMapping *mapping)
{
AvifLoader *loader = NULL;
gboolean success = FALSE;
avifResult avif_result;
avifImage *image;

g_return_val_if_fail (mapping != NULL, NULL);

/* Quick check for the ISOBMFF ftyp box to filter out files that are
* something else entirely */
if (!file_mapping_has_magic (mapping, 4, "ftyp", 4))
goto out;

loader = avif_loader_new ();
loader->mapping = mapping;

loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len);
if (!loader->file_data)
goto out;

loader->decoder = avifDecoderCreate ();

avif_result = avifDecoderSetIOMemory (loader->decoder, loader->file_data, loader->file_data_len);
if (avif_result != AVIF_RESULT_OK)
goto out;

avif_result = avifDecoderParse (loader->decoder);
if (avif_result != AVIF_RESULT_OK)
goto out;

image = loader->decoder->image;

if (image->width < 1 || image->width >= (1 << 28)
|| image->height < 1 || image->height >= (1 << 28))
goto out;

loader->width = image->width;
loader->height = image->height;
loader->rowstride = loader->width * BYTES_PER_PIXEL;

success = TRUE;

out:
if (!success)
{
if (loader)
{
if (loader->decoder)
{
avifDecoderDestroy (loader->decoder);
loader->decoder = NULL;
}

g_free (loader);
loader = NULL;
}
}

return loader;
}

void
avif_loader_destroy (AvifLoader *loader)
{
if (loader->decoder)
avifDecoderDestroy (loader->decoder);

if (loader->mapping)
file_mapping_destroy (loader->mapping);

if (loader->frame_data)
g_free (loader->frame_data);

g_free (loader);
}

gboolean
avif_loader_get_is_animation (AvifLoader *loader)
{
g_return_val_if_fail (loader != NULL, 0);

return loader->decoder->imageCount > 1 ? TRUE : FALSE;
}

gconstpointer
avif_loader_get_frame_data (AvifLoader *loader, ChafaPixelType *pixel_type_out,
gint *width_out, gint *height_out, gint *rowstride_out)
{
g_return_val_if_fail (loader != NULL, NULL);

if (!maybe_decode_frame (loader))
return NULL;

if (width_out)
*width_out = loader->width;
if (height_out)
*height_out = loader->height;
if (pixel_type_out)
*pixel_type_out = CHAFA_PIXEL_RGBA8_UNASSOCIATED;
if (rowstride_out)
*rowstride_out = loader->rowstride;

return loader->frame_data;
}

gint
avif_loader_get_frame_delay (AvifLoader *loader)
{
gdouble duration_s;

g_return_val_if_fail (loader != NULL, 0);

duration_s = loader->decoder->imageTiming.duration;
if (duration_s < 0.000001)
duration_s = 0.05;

return duration_s * 1000.0;
}

void
avif_loader_goto_first_frame (AvifLoader *loader)
{
g_return_if_fail (loader != NULL);

if (loader->current_frame_index == 0)
return;

loader->current_frame_index = 0;
loader->frame_is_decoded = FALSE;
loader->frame_is_success = FALSE;

avifDecoderReset (loader->decoder);
}

gboolean
avif_loader_goto_next_frame (AvifLoader *loader)
{
g_return_val_if_fail (loader != NULL, FALSE);

if (loader->current_frame_index + 1 >= (gint) loader->decoder->imageCount)
return FALSE;

loader->current_frame_index++;
loader->frame_is_decoded = FALSE;
loader->frame_is_success = FALSE;
return TRUE;
}
44 changes: 44 additions & 0 deletions tools/chafa/avif-loader.h
@@ -0,0 +1,44 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/* Copyright (C) 2018-2023 Hans Petter Jansson
*
* This file is part of Chafa, a program that turns images into character art.
*
* Chafa is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chafa is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Chafa. If not, see <http://www.gnu.org/licenses/>. */

#ifndef __AVIF_LOADER_H__
#define __AVIF_LOADER_H__

#include <glib.h>
#include "file-mapping.h"

G_BEGIN_DECLS

typedef struct AvifLoader AvifLoader;

AvifLoader *avif_loader_new_from_mapping (FileMapping *mapping);
void avif_loader_destroy (AvifLoader *loader);

gboolean avif_loader_get_is_animation (AvifLoader *loader);

gconstpointer avif_loader_get_frame_data (AvifLoader *loader, ChafaPixelType *pixel_type_out,
gint *width_out, gint *height_out, gint *rowstride_out);
gint avif_loader_get_frame_delay (AvifLoader *loader);

void avif_loader_goto_first_frame (AvifLoader *loader);
gboolean avif_loader_goto_next_frame (AvifLoader *loader);

G_END_DECLS

#endif /* __AVIF_LOADER_H__ */

0 comments on commit 13dd60c

Please sign in to comment.