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
|
'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,
|
It still has some road to go, but it's already become quite usable,
|
||||||
and it has received basic polishing.
|
and it has received basic polishing.
|
||||||
@ -24,8 +24,9 @@ Building and Running
|
|||||||
--------------------
|
--------------------
|
||||||
Build dependencies: Meson, pkg-config +
|
Build dependencies: Meson, pkg-config +
|
||||||
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
||||||
spng>=0.7.0, libturbojpeg, LibRaw (optional), librsvg-2.0 (optional),
|
spng>=0.7.0, libturbojpeg +
|
||||||
xcursor (optional), libtiff (optional), gdk-pixbuf-2.0 (optional)
|
Optional dependencies: LibRaw, librsvg-2.0, xcursor, libheif, libtiff,
|
||||||
|
gdk-pixbuf-2.0
|
||||||
|
|
||||||
$ git clone --recursive https://git.janouch.name/p/fastiv.git
|
$ git clone --recursive https://git.janouch.name/p/fastiv.git
|
||||||
$ meson builddir
|
$ meson builddir
|
||||||
|
192
fastiv-io.c
192
fastiv-io.c
@ -33,6 +33,9 @@
|
|||||||
#ifdef HAVE_XCURSOR
|
#ifdef HAVE_XCURSOR
|
||||||
#include <X11/Xcursor/Xcursor.h>
|
#include <X11/Xcursor/Xcursor.h>
|
||||||
#endif // HAVE_XCURSOR
|
#endif // HAVE_XCURSOR
|
||||||
|
#ifdef HAVE_LIBHEIF
|
||||||
|
#include <libheif/heif.h>
|
||||||
|
#endif // HAVE_LIBHEIF
|
||||||
#ifdef HAVE_LIBTIFF
|
#ifdef HAVE_LIBTIFF
|
||||||
#include <tiff.h>
|
#include <tiff.h>
|
||||||
#include <tiffio.h>
|
#include <tiffio.h>
|
||||||
@ -79,6 +82,11 @@ const char *fastiv_io_supported_media_types[] = {
|
|||||||
#ifdef HAVE_XCURSOR
|
#ifdef HAVE_XCURSOR
|
||||||
"image/x-xcursor",
|
"image/x-xcursor",
|
||||||
#endif // HAVE_XCURSOR
|
#endif // HAVE_XCURSOR
|
||||||
|
#ifdef HAVE_LIBHEIF
|
||||||
|
"image/heic",
|
||||||
|
"image/heif",
|
||||||
|
"image/avif",
|
||||||
|
#endif // HAVE_LIBHEIF
|
||||||
#ifdef HAVE_LIBTIFF
|
#ifdef HAVE_LIBTIFF
|
||||||
"image/tiff",
|
"image/tiff",
|
||||||
#endif // HAVE_LIBTIFF
|
#endif // HAVE_LIBTIFF
|
||||||
@ -1078,6 +1086,177 @@ open_xcursor(const gchar *data, gsize len, GError **error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_XCURSOR --------------------------------------------------------
|
#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 //---------------------------------------------------------
|
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
|
||||||
|
|
||||||
struct fastiv_io_tiff {
|
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);
|
g_clear_error(error);
|
||||||
}
|
}
|
||||||
#endif // HAVE_XCURSOR --------------------------------------------------------
|
#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 //---------------------------------------------------------
|
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
|
||||||
// This needs to be positioned after LibRaw.
|
// This needs to be positioned after LibRaw.
|
||||||
if ((surface = open_libtiff(data, len, path, error)))
|
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 --------------------------------------------------------
|
#endif // HAVE_LIBTIFF --------------------------------------------------------
|
||||||
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
|
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
|
||||||
// This is only used as a last resort, the rest above is special-cased.
|
// This is only used as a last resort, the rest above is special-cased.
|
||||||
if ((surface = open_gdkpixbuf(data, len, error)) ||
|
if ((surface = open_gdkpixbuf(data, len, error)))
|
||||||
(error && (*error)->code != GDK_PIXBUF_ERROR_UNKNOWN_TYPE))
|
break;
|
||||||
|
if (error && (*error)->code != GDK_PIXBUF_ERROR_UNKNOWN_TYPE)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -17,6 +17,7 @@ endif
|
|||||||
libraw = dependency('libraw', required : get_option('libraw'))
|
libraw = dependency('libraw', required : get_option('libraw'))
|
||||||
librsvg = dependency('librsvg-2.0', required : get_option('librsvg'))
|
librsvg = dependency('librsvg-2.0', required : get_option('librsvg'))
|
||||||
xcursor = dependency('xcursor', required : get_option('xcursor'))
|
xcursor = dependency('xcursor', required : get_option('xcursor'))
|
||||||
|
libheif = dependency('libheif', required : get_option('libheif'))
|
||||||
libtiff = dependency('libtiff-4', required : get_option('libtiff'))
|
libtiff = dependency('libtiff-4', required : get_option('libtiff'))
|
||||||
gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))
|
gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@ -28,6 +29,7 @@ dependencies = [
|
|||||||
libraw,
|
libraw,
|
||||||
librsvg,
|
librsvg,
|
||||||
xcursor,
|
xcursor,
|
||||||
|
libheif,
|
||||||
libtiff,
|
libtiff,
|
||||||
gdkpixbuf,
|
gdkpixbuf,
|
||||||
meson.get_compiler('c').find_library('m', required : false),
|
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_LIBRAW', libraw.found())
|
||||||
conf.set('HAVE_LIBRSVG', librsvg.found())
|
conf.set('HAVE_LIBRSVG', librsvg.found())
|
||||||
conf.set('HAVE_XCURSOR', xcursor.found())
|
conf.set('HAVE_XCURSOR', xcursor.found())
|
||||||
|
conf.set('HAVE_LIBHEIF', libheif.found())
|
||||||
conf.set('HAVE_LIBTIFF', libtiff.found())
|
conf.set('HAVE_LIBTIFF', libtiff.found())
|
||||||
conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found())
|
conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found())
|
||||||
configure_file(
|
configure_file(
|
||||||
|
@ -4,6 +4,8 @@ option('librsvg', type : 'feature', value : 'auto',
|
|||||||
description : 'Build with SVG support, requires librsvg')
|
description : 'Build with SVG support, requires librsvg')
|
||||||
option('xcursor', type : 'feature', value : 'auto',
|
option('xcursor', type : 'feature', value : 'auto',
|
||||||
description : 'Build with Xcursor support, requires libXcursor')
|
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',
|
option('libtiff', type : 'feature', value : 'auto',
|
||||||
description : 'Build with TIFF support, requires libtiff')
|
description : 'Build with TIFF support, requires libtiff')
|
||||||
option('gdk-pixbuf', type : 'feature', value : 'auto',
|
option('gdk-pixbuf', type : 'feature', value : 'auto',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user