diff options
| -rw-r--r-- | README.adoc | 7 | ||||
| -rw-r--r-- | fastiv-io.c | 161 | ||||
| -rw-r--r-- | meson.build | 3 | ||||
| -rw-r--r-- | meson_options.txt | 2 | 
4 files changed, 168 insertions, 5 deletions
diff --git a/README.adoc b/README.adoc index 69649e9..33691d8 100644 --- a/README.adoc +++ b/README.adoc @@ -2,7 +2,7 @@ fastiv  ======  'fastiv' is a fast image viewer, supporting BMP, PNG, GIF, JPEG, and optionally -raw photos, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf loads. +raw photos, HEIC, AVIF, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf loads.  It still has some road to go, but it's already become quite usable,  and it has received basic polishing. @@ -24,8 +24,9 @@ Building and Running  --------------------  Build dependencies: Meson, pkg-config +  Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info, -spng>=0.7.0, libturbojpeg, LibRaw (optional), librsvg-2.0 (optional), -xcursor (optional), libtiff (optional), gdk-pixbuf-2.0 (optional) +spng>=0.7.0, libturbojpeg + +Optional dependencies: LibRaw, librsvg-2.0, xcursor, libheif, libtiff, +gdk-pixbuf-2.0   $ git clone --recursive https://git.janouch.name/p/fastiv.git   $ meson builddir 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) { diff --git a/meson.build b/meson.build index 34508c6..bbd0cc5 100644 --- a/meson.build +++ b/meson.build @@ -17,6 +17,7 @@ endif  libraw = dependency('libraw', required : get_option('libraw'))  librsvg = dependency('librsvg-2.0', required : get_option('librsvg'))  xcursor = dependency('xcursor', required : get_option('xcursor')) +libheif = dependency('libheif', required : get_option('libheif'))  libtiff = dependency('libtiff-4', required : get_option('libtiff'))  gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))  dependencies = [ @@ -28,6 +29,7 @@ dependencies = [  	libraw,  	librsvg,  	xcursor, +	libheif,  	libtiff,  	gdkpixbuf,  	meson.get_compiler('c').find_library('m', required : false), @@ -39,6 +41,7 @@ conf.set_quoted('PROJECT_VERSION', meson.project_version())  conf.set('HAVE_LIBRAW', libraw.found())  conf.set('HAVE_LIBRSVG', librsvg.found())  conf.set('HAVE_XCURSOR', xcursor.found()) +conf.set('HAVE_LIBHEIF', libheif.found())  conf.set('HAVE_LIBTIFF', libtiff.found())  conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found())  configure_file( diff --git a/meson_options.txt b/meson_options.txt index b360330..f428622 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,8 @@ option('librsvg', type : 'feature', value : 'auto',  	description : 'Build with SVG support, requires librsvg')  option('xcursor', type : 'feature', value : 'auto',  	description : 'Build with Xcursor support, requires libXcursor') +option('libheif', type : 'feature', value : 'auto', +	description : 'Build with HEIF/AVIF support, requires libheif')  option('libtiff', type : 'feature', value : 'auto',  	description : 'Build with TIFF support, requires libtiff')  option('gdk-pixbuf', type : 'feature', value : 'auto',  | 
