Support using libtiff directly

Multiple directories are read as multiple pages.

The error handling is mildly questionable, as is libtiff.
This commit is contained in:
Přemysl Eric Janouch 2021-11-26 21:31:13 +01:00
parent f1742ec7da
commit 666bfc0759
Signed by: p
GPG Key ID: A0420B94F92B9493
4 changed files with 234 additions and 2 deletions

View File

@ -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, SVG and X11 cursors, or whatever gdk-pixbuf loads. RAW, 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.
@ -25,7 +25,7 @@ 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, LibRaw (optional), librsvg-2.0 (optional),
xcursor (optional), gdk-pixbuf-2.0 (optional) xcursor (optional), libtiff (optional), gdk-pixbuf-2.0 (optional)
$ git clone --recursive https://git.janouch.name/p/fastiv.git $ git clone --recursive https://git.janouch.name/p/fastiv.git
$ meson builddir $ meson builddir

View File

@ -33,6 +33,10 @@
#ifdef HAVE_XCURSOR #ifdef HAVE_XCURSOR
#include <X11/Xcursor/Xcursor.h> #include <X11/Xcursor/Xcursor.h>
#endif // HAVE_XCURSOR #endif // HAVE_XCURSOR
#ifdef HAVE_LIBTIFF
#include <tiff.h>
#include <tiffio.h>
#endif // HAVE_LIBTIFF
#ifdef HAVE_GDKPIXBUF #ifdef HAVE_GDKPIXBUF
#include <gdk/gdk.h> #include <gdk/gdk.h>
#include <gdk-pixbuf/gdk-pixbuf.h> #include <gdk-pixbuf/gdk-pixbuf.h>
@ -75,6 +79,9 @@ 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_LIBTIFF
"image/tiff",
#endif // HAVE_LIBTIFF
NULL NULL
}; };
@ -1010,6 +1017,217 @@ open_xcursor(const gchar *data, gsize len, GError **error)
} }
#endif // HAVE_XCURSOR -------------------------------------------------------- #endif // HAVE_XCURSOR --------------------------------------------------------
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
struct fastiv_io_tiff {
unsigned char *data;
gchar *error;
// No, libtiff, the offset is not supposed to be unsigned (also see:
// man 0p sys_types.h), but at least it's fewer cases for us to care about.
toff_t position, len;
};
static tsize_t
fastiv_io_tiff_read(thandle_t h, tdata_t buf, tsize_t len)
{
struct fastiv_io_tiff *io = h;
if (len < 0) {
// What the FUCK! This argument is not supposed to be signed!
// How many mistakes can you make in such a basic API?
errno = EOWNERDEAD;
return -1;
}
if (io->position > io->len) {
errno = EOVERFLOW;
return -1;
}
toff_t n = MIN(io->len - io->position, (toff_t) len);
if (n > TIFF_TMSIZE_T_MAX) {
errno = EIO;
return -1;
}
memcpy(buf, io->data + io->position, n);
io->position += n;
return n;
}
static tsize_t
fastiv_io_tiff_write(G_GNUC_UNUSED thandle_t h,
G_GNUC_UNUSED tdata_t buf, G_GNUC_UNUSED tsize_t len)
{
errno = EBADF;
return -1;
}
static toff_t
fastiv_io_tiff_seek(thandle_t h, toff_t offset, int whence)
{
struct fastiv_io_tiff *io = h;
switch (whence) {
case SEEK_SET:
io->position = offset;
break;
case SEEK_CUR:
io->position += offset;
break;
case SEEK_END:
io->position = io->len + offset;
break;
default:
errno = EINVAL;
return -1;
}
return io->position;
}
static int
fastiv_io_tiff_close(G_GNUC_UNUSED thandle_t h)
{
return 0;
}
static toff_t
fastiv_io_tiff_size(thandle_t h)
{
return ((struct fastiv_io_tiff *) h)->len;
}
static void
fastiv_io_tiff_error(thandle_t h,
const char *module, const char *format, va_list ap)
{
struct fastiv_io_tiff *io = h;
gchar *message = g_strdup_vprintf(format, ap);
if (io->error)
// I'm not sure if two errors can ever come in a succession,
// but make sure to log them in any case.
g_warning("tiff: %s: %s", module, message);
else
io->error = g_strconcat(module, ": ", message, NULL);
g_free(message);
}
static void
fastiv_io_tiff_warning(G_GNUC_UNUSED thandle_t h,
const char *module, const char *format, va_list ap)
{
gchar *message = g_strdup_vprintf(format, ap);
g_debug("tiff: %s: %s", module, message);
g_free(message);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static cairo_surface_t *
load_libtiff_directory(TIFF *tiff, GError **error)
{
char emsg[1024] = "";
if (!TIFFRGBAImageOK(tiff, emsg)) {
set_error(error, emsg);
return NULL;
}
// TODO(p): Are there cases where we might not want to "stop on error"?
TIFFRGBAImage image;
if (!TIFFRGBAImageBegin(&image, tiff, 1 /* stop on error */, emsg)) {
set_error(error, emsg);
return NULL;
}
if (image.width > G_MAXINT || image.height >= G_MAXINT ||
G_MAXUINT32 / image.width < image.height) {
set_error(error, "image dimensions too large");
TIFFRGBAImageEnd(&image);
return NULL;
}
cairo_surface_t *surface = cairo_image_surface_create(
CAIRO_FORMAT_ARGB32, image.width, image.height);
uint32_t *raster = (uint32_t *) cairo_image_surface_get_data(surface);
image.req_orientation = ORIENTATION_LEFTTOP;
if (TIFFRGBAImageGet(&image, raster, image.width, image.height)) {
// Needs to be converted from ABGR to ARGB for Cairo,
// and then premultiplied.
for (uint32_t i = image.width * image.height; i--; ) {
uint32_t pixel = raster[i],
a = TIFFGetA(pixel),
b = TIFFGetB(pixel) * a / 255,
g = TIFFGetG(pixel) * a / 255,
r = TIFFGetR(pixel) * a / 255;
raster[i] = a << 24 | r << 16 | g << 8 | b;
}
cairo_surface_mark_dirty(surface);
} else {
g_clear_pointer(&surface, cairo_surface_destroy);
}
TIFFRGBAImageEnd(&image);
return surface;
}
static cairo_surface_t *
open_libtiff(const gchar *data, gsize len, const gchar *path, GError **error)
{
// Both kinds of handlers are called, redirect everything.
TIFFErrorHandler eh = TIFFSetErrorHandler(NULL);
TIFFErrorHandler wh = TIFFSetWarningHandler(NULL);
TIFFErrorHandlerExt ehe = TIFFSetErrorHandlerExt(fastiv_io_tiff_error);
TIFFErrorHandlerExt whe = TIFFSetWarningHandlerExt(fastiv_io_tiff_warning);
struct fastiv_io_tiff h = {
.data = (unsigned char *) data,
.position = 0,
.len = len,
};
cairo_surface_t *result = NULL, *result_tail = NULL;
TIFF *tiff = TIFFClientOpen(path, "rm" /* Avoid mmap. */, &h,
fastiv_io_tiff_read, fastiv_io_tiff_write, fastiv_io_tiff_seek,
fastiv_io_tiff_close, fastiv_io_tiff_size, NULL, NULL);
if (!tiff)
goto fail;
do {
// We inform about unsupported directories, but do not fail on them.
GError *err = NULL;
cairo_surface_t *surface = load_libtiff_directory(tiff, &err);
if (err) {
g_warning("%s: %s", path, err->message);
g_error_free(err);
}
if (!surface)
continue;
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;
}
} while (TIFFReadDirectory(tiff));
TIFFClose(tiff);
fail:
if (h.error) {
g_clear_pointer(&result, cairo_surface_destroy);
set_error(error, h.error);
g_free(h.error);
} else if (!result) {
set_error(error, "empty or unsupported image");
}
TIFFSetErrorHandlerExt(ehe);
TIFFSetWarningHandlerExt(whe);
TIFFSetErrorHandler(eh);
TIFFSetWarningHandler(wh);
return result;
}
#endif // HAVE_LIBTIFF --------------------------------------------------------
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------ #ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
static cairo_surface_t * static cairo_surface_t *
@ -1149,6 +1367,15 @@ 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_LIBTIFF //---------------------------------------------------------
// This needs to be positioned after LibRaw.
if ((surface = open_libtiff(data, len, path, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
g_clear_error(error);
}
#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)) ||

View File

@ -10,6 +10,7 @@ add_project_arguments(
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'))
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 = [
dependency('gtk+-3.0'), dependency('gtk+-3.0'),
@ -20,6 +21,7 @@ dependencies = [
libraw, libraw,
librsvg, librsvg,
xcursor, xcursor,
libtiff,
gdkpixbuf, gdkpixbuf,
meson.get_compiler('c').find_library('m', required : false), meson.get_compiler('c').find_library('m', required : false),
] ]
@ -30,6 +32,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_LIBTIFF', libtiff.found())
conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found()) conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found())
configure_file( configure_file(
output : 'config.h', output : 'config.h',

View File

@ -4,5 +4,7 @@ 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('libtiff', type : 'feature', value : 'auto',
description : 'Build with TIFF support, requires libtiff')
option('gdk-pixbuf', type : 'feature', value : 'auto', option('gdk-pixbuf', type : 'feature', value : 'auto',
description : 'Build with a fallback to the gdk-pixbuf library') description : 'Build with a fallback to the gdk-pixbuf library')