diff --git a/README.adoc b/README.adoc index fe2183b..1f7df00 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,8 @@ fastiv ====== -'fastiv' is a fast image viewer, supporting BMP, PNG, GIF and JPEG. +'fastiv' is a fast image viewer, supporting BMP, PNG, GIF, JPEG, and optionally +RAW pictures. It is meant to be a viable replacement for Eye of GNOME, which is slow and likes to break on huge pictures. Memory efficiency is specifically a non-goal. @@ -14,7 +15,7 @@ a package with the latest development version from Archlinux's AUR. Building and Running -------------------- Build dependencies: Meson, pkg-config + -Runtime dependencies: gtk+-3.0, libturbojpeg +Runtime dependencies: gtk+-3.0, libturbojpeg, LibRaw (optional) $ git clone --recursive https://git.janouch.name/p/fastiv.git $ meson builddir diff --git a/fastiv-view.c b/fastiv-view.c index d8d71ea..a692285 100644 --- a/fastiv-view.c +++ b/fastiv-view.c @@ -30,8 +30,13 @@ #include "wuffs-mirror-release-c/release/c/wuffs-v0.3.c" #include +#include "config.h" #include "fastiv-view.h" +#ifdef HAVE_LIBRAW +#include +#endif // HAVE_LIBRAW + struct _FastivView { GtkWidget parent_instance; cairo_surface_t *surface; @@ -330,12 +335,120 @@ open_libjpeg_turbo(const gchar *data, gsize len, GError **error) return surface; } +#ifdef HAVE_LIBRAW // --------------------------------------------------------- + +static cairo_surface_t * +open_libraw(const gchar *data, gsize len, GError **error) +{ + // https://github.com/LibRaw/LibRaw/issues/418 + libraw_data_t *iprc = libraw_init(LIBRAW_OPIONS_NO_MEMERR_CALLBACK + | LIBRAW_OPIONS_NO_DATAERR_CALLBACK); + if (!iprc) { + set_error(error, "failed to obtain a LibRaw handle"); + return NULL; + } + +#if 0 + // TODO(p): Consider setting this--the image is still likely to be + // rendered suboptimally, so why not make it faster. + iprc->params.half_size = 1; +#endif + + // TODO(p): Check if we need to set anything for autorotation (sizes.flip). + iprc->params.use_camera_wb = 1; + iprc->params.output_color = 1; // sRGB, TODO(p): Is this used? + iprc->params.output_bps = 8; // This should be the default value. + + int err = 0; + if ((err = libraw_open_buffer(iprc, (void *) data, len))) { + set_error(error, libraw_strerror(err)); + libraw_close(iprc); + return NULL; + } + + // TODO(p): Do we need to check iprc->idata.raw_count? Maybe for TIFFs? + if ((err = libraw_unpack(iprc))) { + set_error(error, libraw_strerror(err)); + libraw_close(iprc); + return NULL; + } + +#if 0 + // TODO(p): I'm not sure when this is necessary or useful yet. + if ((err = libraw_adjust_sizes_info_only(iprc))) { + set_error(error, libraw_strerror(err)); + libraw_close(iprc); + return NULL; + } +#endif + + // TODO(p): Documentation says I should look at the code and do it myself. + if ((err = libraw_dcraw_process(iprc))) { + set_error(error, libraw_strerror(err)); + libraw_close(iprc); + return NULL; + } + + // FIXME: This is shittily written to iterate over the range of + // idata.colors, and will be naturally slow. + libraw_processed_image_t *image = libraw_dcraw_make_mem_image(iprc, &err); + if (!image) { + set_error(error, libraw_strerror(err)); + libraw_close(iprc); + return NULL; + } + + // This should have been transformed, and kept, respectively. + if (image->colors != 3 || image->bits != 8) { + set_error(error, "unexpected number of colours, or bit depth"); + libraw_dcraw_clear_mem(image); + libraw_close(iprc); + return NULL; + } + + int width = image->width, height = image->height; + cairo_surface_t *surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + if (!surface) { + set_error(error, "failed to allocate an image surface"); + libraw_dcraw_clear_mem(image); + libraw_close(iprc); + return NULL; + } + + // Starting to modify pixel data directly. Probably an unnecessary call. + cairo_surface_flush(surface); + + uint32_t *pixels = (uint32_t *) cairo_image_surface_get_data(surface); + unsigned char *p = image->data; + for (ushort y = 0; y < image->height; y++) { + for (ushort x = 0; x < image->width; x++) { + *pixels++ = 0xff000000 | (uint32_t) p[0] << 16 + | (uint32_t) p[1] << 8 | (uint32_t) p[2]; + p += 3; + } + } + + // Pixel data has been written, need to let Cairo know. + cairo_surface_mark_dirty(surface); + + libraw_dcraw_clear_mem(image); + libraw_close(iprc); + return surface; +} + +#endif // HAVE_LIBRAW --------------------------------------------------------- + // TODO(p): Progressive picture loading, or at least async/cancellable. gboolean fastiv_view_open(FastivView *self, const gchar *path, GError **error) { // TODO(p): Don't always load everything into memory, test type first, // for which we only need the first 16 bytes right now. + // Though LibRaw poses an issue--we may want to try to map RAW formats + // to FourCC values--many of them are compliant TIFF files. + // We might want to employ a more generic way of magic identification, + // and with some luck, it could even be integrated into Wuffs. gchar *data = NULL; gsize len = 0; if (!g_file_get_contents(path, &data, &len, error)) @@ -366,6 +479,15 @@ fastiv_view_open(FastivView *self, const gchar *path, GError **error) surface = open_libjpeg_turbo(data, len, error); break; default: +#ifdef HAVE_LIBRAW // --------------------------------------------------------- + if ((surface = open_libraw(data, len, error))) + break; + + // TODO(p): We should try to pass actual processing errors through, + // notably only continue with LIBRAW_FILE_UNSUPPORTED. + g_clear_error(error); +#endif // HAVE_LIBRAW --------------------------------------------------------- + // TODO(p): Integrate gdk-pixbuf as a fallback (optional dependency). set_error(error, "unsupported file type"); } diff --git a/meson.build b/meson.build index 1295f1d..b730544 100644 --- a/meson.build +++ b/meson.build @@ -1,18 +1,22 @@ project('fastiv', 'c', default_options : ['c_std=gnu99'], version : '0.1.0') +# TODO(p): consider whether libraw_r would be appropriate +libraw = dependency('libraw', required : get_option('libraw')) +dependencies = [ + dependency('gtk+-3.0'), + dependency('libturbojpeg'), + libraw, +] + conf = configuration_data() conf.set_quoted('PROJECT_NAME', meson.project_name()) conf.set_quoted('PROJECT_VERSION', meson.project_version()) +conf.set('HAVE_LIBRAW', libraw.found()) configure_file( output : 'config.h', configuration : conf, ) -dependencies = [ - dependency('gtk+-3.0'), - dependency('libturbojpeg'), -] - # TODO(p): we need to create and install a .desktop file executable('fastiv', 'fastiv.c', 'fastiv-view.c', install : true, diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..0d56b56 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,2 @@ +option('libraw', type : 'feature', value : 'auto', + description : 'Build with RAW support, requires LibRaw')