Skip to content

Commit 89e420a

Browse files
committed
chafa: Add support for HEIF files
This format is used by Apple devices. Fixes #299 (GitHub).
1 parent aaf2d37 commit 89e420a

File tree

7 files changed

+300
-2
lines changed

7 files changed

+300
-2
lines changed

configure.ac

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ AS_IF([test "$with_tools" != no], [
135135
with_avif=no)])
136136
AS_IF([test "$with_avif" != no], [AC_DEFINE([HAVE_AVIF], [1], [Define if we have AVIF support.])])
137137
138+
dnl libheif (optional)
139+
AC_ARG_WITH(heif,
140+
[AS_HELP_STRING([--without-heif], [don't build HEIF loader [default=on]])],
141+
,
142+
with_heif=yes)
143+
AS_IF([test "$with_heif" != no], [
144+
PKG_CHECK_MODULES(HEIF, [libheif],,
145+
missing_rpms="$missing_rpms libheif-devel"
146+
missing_debs="$missing_debs libheif-dev"
147+
with_heif=no)])
148+
AS_IF([test "$with_heif" != no], [AC_DEFINE([HAVE_HEIF], [1], [Define if we have HEIF support.])])
149+
138150
dnl libjpeg (optional)
139151
AC_ARG_WITH(jpeg,
140152
[AS_HELP_STRING([--without-jpeg], [don't build JPEG loader [default=on]])],
@@ -199,6 +211,7 @@ AM_CONDITIONAL([HAVE_SVG], [test "$with_tools" != no -a "$with_svg" != no])
199211
AM_CONDITIONAL([HAVE_TIFF], [test "$with_tools" != no -a "$with_tiff" != no])
200212
AM_CONDITIONAL([HAVE_WEBP], [test "$with_tools" != no -a "$with_webp" != no])
201213
AM_CONDITIONAL([HAVE_AVIF], [test "$with_tools" != no -a "$with_avif" != no])
214+
AM_CONDITIONAL([HAVE_HEIF], [test "$with_tools" != no -a "$with_heif" != no])
202215
AM_CONDITIONAL([HAVE_JXL], [test "$with_tools" != no -a "$with_jxl" != no])
203216

204217
# Used by gtk-doc's fixxref.
@@ -531,6 +544,7 @@ colorize_vars="
531544
with_webp
532545
with_jxl
533546
with_avif
547+
with_heif
534548
"
535549

536550
dnl Only use colors if the terminal supports the aixterm-style bright ones (16 total).
@@ -588,6 +602,7 @@ echo >&AS_MESSAGE_FD "Build command-line tool ..... $pwith_tools"
588602
if test "x$with_tools" != xno; then
589603
echo >&AS_MESSAGE_FD "With AVIF loader ............ $pwith_avif"
590604
echo >&AS_MESSAGE_FD "With GIF loader ............. $pyes (internal)"
605+
echo >&AS_MESSAGE_FD "With HEIF loader ............ $pwith_heif"
591606
echo >&AS_MESSAGE_FD "With JPEG loader ............ $pwith_jpeg"
592607
echo >&AS_MESSAGE_FD "With PNG loader ............. $pyes (internal)"
593608
echo >&AS_MESSAGE_FD "With QOI loader ............. $pyes (internal)"

tests/data/good/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ EXTRA_DIST = \
1111
noise-32x32.xwd \
1212
pixel.avif \
1313
pixel.gif \
14+
pixel.heif \
1415
pixel.jpeg \
1516
pixel.jxl \
1617
pixel.png \

tests/data/good/pixel.heif

1.18 KB
Binary file not shown.

tools/chafa/Makefile.am

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ chafa_SOURCES += \
5353
chicle-avif-loader.h
5454
endif
5555

56+
if HAVE_HEIF
57+
chafa_SOURCES += \
58+
chicle-heif-loader.c \
59+
chicle-heif-loader.h
60+
endif
61+
5662
if HAVE_JPEG
5763
chafa_SOURCES += \
5864
chicle-jpeg-loader.c \
@@ -89,11 +95,11 @@ endif
8995
#
9096
# This is disabled by default.
9197

92-
chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(JXL_CFLAGS) $(AVIF_CFLAGS) $(FREETYPE_CFLAGS)
98+
chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(JXL_CFLAGS) $(AVIF_CFLAGS) $(HEIF_CFLAGS) $(FREETYPE_CFLAGS)
9399
if ENABLE_RPATH
94100
chafa_LDFLAGS = $(CHAFA_LDFLAGS) -rpath $(libdir)
95101
endif
96-
chafa_LDADD = $(GLIB_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(JXL_LIBS) $(AVIF_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la -lm $(WIN32_LDADD)
102+
chafa_LDADD = $(GLIB_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(JXL_LIBS) $(AVIF_LIBS) $(HEIF_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la -lm $(WIN32_LDADD)
97103

98104
# On Microsoft Windows, we compile a resource file with windres and link it in.
99105
# This enables UTF-8 support in filenames, environment variables, etc.

tools/chafa/chicle-heif-loader.c

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2+
3+
/* Copyright (C) 2025 Hans Petter Jansson
4+
*
5+
* This file is part of Chafa, a program that shows pictures on text terminals.
6+
*
7+
* Chafa is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Lesser General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* Chafa is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with Chafa. If not, see <http://www.gnu.org/licenses/>. */
19+
20+
#include "config.h"
21+
#include <assert.h>
22+
#include <errno.h>
23+
#include <stdbool.h>
24+
#include <stdlib.h>
25+
#include <stdio.h>
26+
#include <string.h>
27+
#include <unistd.h>
28+
#include <sys/types.h>
29+
#include <sys/stat.h>
30+
31+
#include <chafa.h>
32+
#include <libheif/heif.h>
33+
#include "chicle-heif-loader.h"
34+
35+
#define BYTES_PER_PIXEL 4
36+
#define IMAGE_BUFFER_SIZE_MAX (0xffffffffU >> 2)
37+
38+
struct ChicleHeifLoader
39+
{
40+
ChicleFileMapping *mapping;
41+
const guint8 *file_data;
42+
size_t file_data_len;
43+
gint width, height;
44+
gint stride;
45+
46+
heif_context *ctx;
47+
heif_image_handle *handle;
48+
heif_image *image;
49+
const uint8_t *frame_data;
50+
};
51+
52+
static void
53+
free_heif_handles (ChicleHeifLoader *loader)
54+
{
55+
if (loader->image)
56+
heif_image_release (loader->image);
57+
if (loader->handle)
58+
heif_image_handle_release (loader->handle);
59+
if (loader->ctx)
60+
heif_context_free (loader->ctx);
61+
62+
loader->image = NULL;
63+
loader->handle = NULL;
64+
loader->ctx = NULL;
65+
}
66+
67+
static ChicleHeifLoader *
68+
chicle_heif_loader_new (void)
69+
{
70+
return g_new0 (ChicleHeifLoader, 1);
71+
}
72+
73+
ChicleHeifLoader *
74+
chicle_heif_loader_new_from_mapping (ChicleFileMapping *mapping)
75+
{
76+
ChicleHeifLoader *loader = NULL;
77+
gboolean success = FALSE;
78+
79+
g_return_val_if_fail (mapping != NULL, NULL);
80+
81+
/* Quick check for the ISOBMFF ftyp box to filter out files that are
82+
* something else entirely */
83+
if (!chicle_file_mapping_has_magic (mapping, 4, "ftyp", 4))
84+
goto out;
85+
86+
loader = chicle_heif_loader_new ();
87+
loader->mapping = mapping;
88+
89+
loader->file_data = chicle_file_mapping_get_data (loader->mapping, &loader->file_data_len);
90+
if (!loader->file_data)
91+
goto out;
92+
93+
loader->ctx = heif_context_alloc ();
94+
if (!loader->ctx)
95+
goto out;
96+
97+
if (heif_context_read_from_memory_without_copy (loader->ctx,
98+
loader->file_data,
99+
loader->file_data_len,
100+
NULL).code
101+
!= heif_error_Ok)
102+
goto out;
103+
104+
heif_context_get_primary_image_handle (loader->ctx,
105+
&loader->handle);
106+
if (!loader->handle)
107+
goto out;
108+
109+
heif_decode_image (loader->handle,
110+
&loader->image,
111+
heif_colorspace_RGB,
112+
heif_chroma_interleaved_RGBA,
113+
NULL);
114+
if (!loader->image)
115+
goto out;
116+
117+
loader->width = heif_image_get_primary_width (loader->image);
118+
loader->height = heif_image_get_primary_height (loader->image);
119+
120+
if (loader->width < 1 || loader->width >= (1 << 28)
121+
|| loader->height < 1 || loader->height >= (1 << 28))
122+
goto out;
123+
124+
if ((unsigned int) loader->width * loader->height * BYTES_PER_PIXEL > IMAGE_BUFFER_SIZE_MAX)
125+
goto out;
126+
127+
loader->frame_data = heif_image_get_plane_readonly (loader->image,
128+
heif_channel_interleaved,
129+
&loader->stride);
130+
if (!loader->frame_data || loader->stride < 1)
131+
goto out;
132+
133+
success = TRUE;
134+
135+
out:
136+
if (!success)
137+
{
138+
if (loader)
139+
{
140+
free_heif_handles (loader);
141+
g_free (loader);
142+
loader = NULL;
143+
}
144+
}
145+
146+
return loader;
147+
}
148+
149+
void
150+
chicle_heif_loader_destroy (ChicleHeifLoader *loader)
151+
{
152+
free_heif_handles (loader);
153+
154+
if (loader->mapping)
155+
chicle_file_mapping_destroy (loader->mapping);
156+
157+
g_free (loader);
158+
}
159+
160+
gboolean
161+
chicle_heif_loader_get_is_animation (ChicleHeifLoader *loader)
162+
{
163+
g_return_val_if_fail (loader != NULL, 0);
164+
165+
return FALSE;
166+
}
167+
168+
gconstpointer
169+
chicle_heif_loader_get_frame_data (ChicleHeifLoader *loader,
170+
ChafaPixelType *pixel_type_out,
171+
gint *width_out,
172+
gint *height_out,
173+
gint *rowstride_out)
174+
{
175+
g_return_val_if_fail (loader != NULL, NULL);
176+
177+
if (pixel_type_out)
178+
{
179+
*pixel_type_out = heif_image_is_premultiplied_alpha (loader->image)
180+
? CHAFA_PIXEL_RGBA8_PREMULTIPLIED : CHAFA_PIXEL_RGBA8_UNASSOCIATED;
181+
}
182+
if (width_out)
183+
*width_out = loader->width;
184+
if (height_out)
185+
*height_out = loader->height;
186+
if (rowstride_out)
187+
*rowstride_out = loader->stride;
188+
189+
return loader->frame_data;
190+
}
191+
192+
gint
193+
chicle_heif_loader_get_frame_delay (ChicleHeifLoader *loader)
194+
{
195+
g_return_val_if_fail (loader != NULL, 0);
196+
197+
return 0;
198+
}
199+
200+
void
201+
chicle_heif_loader_goto_first_frame (ChicleHeifLoader *loader)
202+
{
203+
g_return_if_fail (loader != NULL);
204+
}
205+
206+
gboolean
207+
chicle_heif_loader_goto_next_frame (ChicleHeifLoader *loader)
208+
{
209+
g_return_val_if_fail (loader != NULL, FALSE);
210+
211+
return FALSE;
212+
}

tools/chafa/chicle-heif-loader.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2+
3+
/* Copyright (C) 2025 Hans Petter Jansson
4+
*
5+
* This file is part of Chafa, a program that shows pictures on text terminals.
6+
*
7+
* Chafa is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Lesser General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* Chafa is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with Chafa. If not, see <http://www.gnu.org/licenses/>. */
19+
20+
#ifndef __CHICLE_HEIF_LOADER_H__
21+
#define __CHICLE_HEIF_LOADER_H__
22+
23+
#include <glib.h>
24+
#include "chicle-file-mapping.h"
25+
26+
G_BEGIN_DECLS
27+
28+
typedef struct ChicleHeifLoader ChicleHeifLoader;
29+
30+
ChicleHeifLoader *chicle_heif_loader_new_from_mapping (ChicleFileMapping *mapping);
31+
void chicle_heif_loader_destroy (ChicleHeifLoader *loader);
32+
33+
gboolean chicle_heif_loader_get_is_animation (ChicleHeifLoader *loader);
34+
35+
gconstpointer chicle_heif_loader_get_frame_data (ChicleHeifLoader *loader,
36+
ChafaPixelType *pixel_type_out,
37+
gint *width_out,
38+
gint *height_out,
39+
gint *rowstride_out);
40+
gint chicle_heif_loader_get_frame_delay (ChicleHeifLoader *loader);
41+
42+
void chicle_heif_loader_goto_first_frame (ChicleHeifLoader *loader);
43+
gboolean chicle_heif_loader_goto_next_frame (ChicleHeifLoader *loader);
44+
45+
G_END_DECLS
46+
47+
#endif /* __CHICLE_HEIF_LOADER_H__ */

tools/chafa/chicle-media-loader.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "chicle-webp-loader.h"
4242
#include "chicle-avif-loader.h"
4343
#include "chicle-jxl-loader.h"
44+
#include "chicle-heif-loader.h"
4445

4546
typedef enum
4647
{
@@ -54,6 +55,7 @@ typedef enum
5455
LOADER_TYPE_AVIF,
5556
LOADER_TYPE_SVG,
5657
LOADER_TYPE_JXL,
58+
LOADER_TYPE_HEIF,
5759

5860
LOADER_TYPE_LAST
5961
}
@@ -207,6 +209,21 @@ loader_vtable [LOADER_TYPE_LAST] =
207209
(gint (*) (gpointer)) chicle_jxl_loader_get_frame_delay
208210
},
209211
#endif
212+
#ifdef HAVE_HEIF
213+
/* Due to its complexity and broad format support, libheif should run last */
214+
[LOADER_TYPE_HEIF] =
215+
{
216+
"HEIF",
217+
(void (*)(void)) chicle_heif_loader_new_from_mapping,
218+
(gpointer (*)(gconstpointer)) NULL,
219+
(void (*)(gpointer)) chicle_heif_loader_destroy,
220+
(gboolean (*)(gpointer)) chicle_heif_loader_get_is_animation,
221+
(void (*)(gpointer)) chicle_heif_loader_goto_first_frame,
222+
(gboolean (*)(gpointer)) chicle_heif_loader_goto_next_frame,
223+
(gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) chicle_heif_loader_get_frame_data,
224+
(gint (*) (gpointer)) chicle_heif_loader_get_frame_delay
225+
},
226+
#endif
210227
};
211228

212229
struct ChicleMediaLoader

0 commit comments

Comments
 (0)