aboutsummaryrefslogtreecommitdiff
path: root/fastiv-io.c
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-12-11 14:40:10 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-12-11 16:11:25 +0100
commitac70c7724b1aa5020f358768451c08d4c77ac1ff (patch)
tree9dc5de2f5b2dd0a0da0d8f05ba9da9ebd9673d31 /fastiv-io.c
parent5f4090aaee642b905c8c1a1385bb975072fca1f6 (diff)
downloadfiv-ac70c7724b1aa5020f358768451c08d4c77ac1ff.tar.gz
fiv-ac70c7724b1aa5020f358768451c08d4c77ac1ff.tar.xz
fiv-ac70c7724b1aa5020f358768451c08d4c77ac1ff.zip
Add preliminary HEIF/AVIF support
The gdk-pixbuf plugin does not work here, for whatever reason. Moreover, close integration exposes higher bit depths, metadata, and auxiliary images. The library is awful and copylefted, but it's the only reasonable thing that works.
Diffstat (limited to 'fastiv-io.c')
-rw-r--r--fastiv-io.c161
1 files changed, 159 insertions, 2 deletions
diff --git a/fastiv-io.c b/fastiv-io.c
index e0c3dea..639038b 100644
--- a/fastiv-io.c
+++ b/fastiv-io.c
@@ -33,6 +33,9 @@
#ifdef HAVE_XCURSOR
#include <X11/Xcursor/Xcursor.h>
#endif // HAVE_XCURSOR
+#ifdef HAVE_LIBHEIF
+#include <libheif/heif.h>
+#endif // HAVE_LIBHEIF
#ifdef HAVE_LIBTIFF
#include <tiff.h>
#include <tiffio.h>
@@ -79,6 +82,11 @@ const char *fastiv_io_supported_media_types[] = {
#ifdef HAVE_XCURSOR
"image/x-xcursor",
#endif // HAVE_XCURSOR
+#ifdef HAVE_LIBHEIF
+ "image/heic",
+ "image/heif",
+ "image/avif",
+#endif // HAVE_LIBHEIF
#ifdef HAVE_LIBTIFF
"image/tiff",
#endif // HAVE_LIBTIFF
@@ -1078,6 +1086,146 @@ open_xcursor(const gchar *data, gsize len, GError **error)
}
#endif // HAVE_XCURSOR --------------------------------------------------------
+#ifdef HAVE_LIBHEIF //---------------------------------------------------------
+
+static cairo_surface_t *
+load_libheif_image(struct heif_context *ctx, heif_item_id id, GError **error)
+{
+ struct heif_image_handle *handle = NULL;
+ struct heif_error err = heif_context_get_image_handle(ctx, id, &handle);
+ if (err.code != heif_error_Ok) {
+ set_error(error, err.message);
+ return NULL;
+ }
+
+ cairo_surface_t *surface = NULL;
+ int has_alpha = heif_image_handle_has_alpha_channel(handle);
+ int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
+ if (bit_depth < 0) {
+ set_error(error, "undefined bit depth");
+ goto fail;
+ }
+
+ // Setting `convert_hdr_to_8bit` seems to be a no-op for RGBA32/64.
+ struct heif_decoding_options *opts = heif_decoding_options_alloc();
+
+ // TODO(p): We can get 16-bit depth, in reality most likely 10-bit.
+ struct heif_image *image = NULL;
+ err = heif_decode_image(handle, &image, heif_colorspace_RGB,
+ heif_chroma_interleaved_RGBA, opts);
+ if (err.code != heif_error_Ok) {
+ set_error(error, err.message);
+ goto fail_decode;
+ }
+
+ int w = heif_image_get_width(image, heif_channel_interleaved);
+ int h = heif_image_get_height(image, heif_channel_interleaved);
+
+ // TODO(p): Add more pages with depth, thumbnails, and auxiliary images.
+ surface = cairo_image_surface_create(
+ has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, w, h);
+ cairo_status_t surface_status = cairo_surface_status(surface);
+ if (surface_status != CAIRO_STATUS_SUCCESS) {
+ set_error(error, cairo_status_to_string(surface_status));
+ cairo_surface_destroy(surface);
+ surface = NULL;
+ goto fail_process;
+ }
+
+ // As of writing, the library is using 16-byte alignment, unlike Cairo.
+ int src_stride = 0;
+ const uint8_t *src = heif_image_get_plane_readonly(
+ image, heif_channel_interleaved, &src_stride);
+ int dst_stride = cairo_image_surface_get_stride(surface);
+ const uint8_t *dst = cairo_image_surface_get_data(surface);
+
+ for (int y = 0; y < h; y++) {
+ uint32_t *dstp = (uint32_t *) (dst + dst_stride * y);
+ const uint32_t *srcp = (const uint32_t *) (src + src_stride * y);
+ for (int x = 0; x < w; x++) {
+ uint32_t rgba = g_ntohl(srcp[x]);
+ *dstp++ = rgba << 24 | rgba >> 8;
+ }
+ }
+
+ // TODO(p): Test real behaviour on real transparent images.
+ if (has_alpha && !heif_image_handle_is_premultiplied_alpha(handle)) {
+ for (int y = 0; y < h; y++) {
+ uint32_t *dstp = (uint32_t *) (dst + dst_stride * y);
+ for (int x = 0; x < w; x++) {
+ uint32_t pixel = dstp[x], a = pixel >> 24;
+ uint8_t r = pixel >> 16;
+ uint8_t g = pixel >> 8;
+ uint8_t b = pixel;
+ dstp[x] = a << 24 |
+ (r * a / 255) << 16 | (g * a / 255) << 8 | (b * a / 255);
+ }
+ }
+ }
+
+ // TODO(p): Attach any ICC color profile and Exif data.
+ cairo_surface_mark_dirty(surface);
+
+fail_process:
+ heif_image_release(image);
+fail_decode:
+ heif_decoding_options_free(opts);
+fail:
+ heif_image_handle_release(handle);
+ return surface;
+}
+
+static cairo_surface_t *
+open_libheif(const gchar *data, gsize len, GError **error)
+{
+ // libheif will throw C++ exceptions on allocation failures.
+ // The library is generally awful through and through.
+ struct heif_context *ctx = heif_context_alloc();
+ cairo_surface_t *result = NULL, *result_tail = NULL;
+
+ struct heif_error err;
+ err = heif_context_read_from_memory_without_copy(ctx, data, len, NULL);
+ if (err.code != heif_error_Ok) {
+ set_error(error, err.message);
+ goto fail_read;
+ }
+
+ // TODO(p): Only fail if there is absolutely nothing to extract,
+ // see open_libtiff() below.
+ int n = heif_context_get_number_of_top_level_images(ctx);
+ heif_item_id *ids = g_malloc0_n(n, sizeof *ids);
+ n = heif_context_get_list_of_top_level_image_IDs(ctx, ids, n);
+
+ for (int i = 0; i < n; i++) {
+ cairo_surface_t *surface = load_libheif_image(ctx, ids[i], error);
+ if (!surface) {
+ if (result) {
+ cairo_surface_destroy(result);
+ result = NULL;
+ }
+ goto fail_decode;
+ }
+
+ if (result) {
+ cairo_surface_set_user_data(result_tail,
+ &fastiv_io_key_page_next, surface,
+ (cairo_destroy_func_t) cairo_surface_destroy);
+ cairo_surface_set_user_data(surface,
+ &fastiv_io_key_page_previous, result_tail, NULL);
+ result_tail = surface;
+ } else {
+ result = result_tail = surface;
+ }
+ }
+
+fail_decode:
+ g_free(ids);
+fail_read:
+ heif_context_free(ctx);
+ return result;
+}
+
+#endif // HAVE_LIBHEIF --------------------------------------------------------
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
struct fastiv_io_tiff {
@@ -1452,6 +1600,14 @@ fastiv_io_open_from_data(const char *data, size_t len, const gchar *path,
g_clear_error(error);
}
#endif // HAVE_XCURSOR --------------------------------------------------------
+#ifdef HAVE_LIBHEIF //---------------------------------------------------------
+ if ((surface = open_libheif(data, len, error)))
+ break;
+ if (error) {
+ g_debug("%s", (*error)->message);
+ g_clear_error(error);
+ }
+#endif // HAVE_LIBHEIF --------------------------------------------------------
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
// This needs to be positioned after LibRaw.
if ((surface = open_libtiff(data, len, path, error)))
@@ -1463,8 +1619,9 @@ fastiv_io_open_from_data(const char *data, size_t len, const gchar *path,
#endif // HAVE_LIBTIFF --------------------------------------------------------
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
// This is only used as a last resort, the rest above is special-cased.
- if ((surface = open_gdkpixbuf(data, len, error)) ||
- (error && (*error)->code != GDK_PIXBUF_ERROR_UNKNOWN_TYPE))
+ if ((surface = open_gdkpixbuf(data, len, error)))
+ break;
+ if (error && (*error)->code != GDK_PIXBUF_ERROR_UNKNOWN_TYPE)
break;
if (error) {