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:
parent
f1742ec7da
commit
666bfc0759
|
@ -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
|
||||||
|
|
227
fastiv-io.c
227
fastiv-io.c
|
@ -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)) ||
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue