Compare commits
2 Commits
5f4090aaee
...
7e92011ab2
Author | SHA1 | Date | |
---|---|---|---|
7e92011ab2 | |||
ac70c7724b |
@ -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
|
||||
|
192
fastiv-io.c
192
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,177 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heif_item_id exif_id = 0;
|
||||
if (heif_image_handle_get_list_of_metadata_block_IDs(
|
||||
handle, "Exif", &exif_id, 1)) {
|
||||
size_t exif_len = heif_image_handle_get_metadata_size(handle, exif_id);
|
||||
void *exif = g_malloc0(exif_len);
|
||||
err = heif_image_handle_get_metadata(handle, exif_id, exif);
|
||||
if (err.code) {
|
||||
g_warning("%s", err.message);
|
||||
g_free(exif);
|
||||
} else {
|
||||
cairo_surface_set_user_data(surface, &fastiv_io_key_exif,
|
||||
g_bytes_new_take(exif, exif_len),
|
||||
(cairo_destroy_func_t) g_bytes_unref);
|
||||
}
|
||||
}
|
||||
|
||||
// https://loc.gov/preservation/digital/formats/fdd/fdd000526.shtml#factors
|
||||
if (heif_image_handle_get_color_profile_type(handle) ==
|
||||
heif_color_profile_type_prof) {
|
||||
size_t icc_len = heif_image_handle_get_raw_color_profile_size(handle);
|
||||
void *icc = g_malloc0(icc_len);
|
||||
err = heif_image_handle_get_raw_color_profile(handle, icc);
|
||||
if (err.code) {
|
||||
g_warning("%s", err.message);
|
||||
g_free(icc);
|
||||
} else {
|
||||
cairo_surface_set_user_data(surface, &fastiv_io_key_icc,
|
||||
g_bytes_new_take(icc, icc_len),
|
||||
(cairo_destroy_func_t) g_bytes_unref);
|
||||
}
|
||||
}
|
||||
|
||||
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 +1631,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 +1650,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) {
|
||||
|
@ -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(
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user