From 42d345e789158b22e679f76ce6e29570e7e33a9e Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Tue, 13 Aug 2019 13:46:17 +0100 Subject: [PATCH] Include jpeg2000 loader Basic .jp2 loader --- README.md | 5 +- configure.ac | 26 +++ src/Makefile.am | 6 +- src/filefilter.c | 3 + src/image-load.c | 10 + src/image_load_j2k.c | 351 +++++++++++++++++++++++++++++++++++ src/image_load_j2k.h | 29 +++ src/misc.c | 27 +++ src/misc.h | 1 + web/geeqie-install-debian.sh | 4 +- 10 files changed, 459 insertions(+), 3 deletions(-) create mode 100644 src/image_load_j2k.c create mode 100644 src/image_load_j2k.h diff --git a/README.md b/README.md index 0b944c60..d4fd977a 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Geeqie is a graphics file viewer. Basic features: * output: single image, anaglyph, SBS, mirror, SBS half size (3DTV) * Viewing raster and vector images, in the following formats: -3FR, ANI, APM, ARW, BMP, CR2, CRW, CUR, DNG, ERF, GIF, ICNS, ICO, JPE/JPEG/JPG, JPS, KDC, MEF, MPO, MOS, MRW, NEF, ORF, PEF, PTX, PBM/PGM/PNM/PPM, PNG, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WMF, XBM, XPM, HEIF (primary image only), WEBP, DjVu, PSD. Animated GIFs are supported. +3FR, ANI, APM, ARW, BMP, CR2, CRW, CUR, DNG, ERF, GIF, ICNS, ICO, JPE/JPEG/JPG, JPS, KDC, MEF, MPO, MOS, MRW, NEF, ORF, PEF, PTX, PBM/PGM/PNM/PPM, PNG, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WMF, XBM, XPM, HEIF (primary image only), WEBP, DjVu, PSD, JP2. Animated GIFs are supported. * Preview and thumbnails of video clips can be displayed. Clips can be run via a defined external program. @@ -269,6 +269,9 @@ And either the ChangeLog file or [Geeqie ChangeLog](http://geeqie.org/cgi-bin/gi libdjvulibre For displaying DjVu images + libopenjp2 + For displaying JP2 images + ### Code hackers: If you plan on making any major changes to the code that will be offered for diff --git a/configure.ac b/configure.ac index b5c28dc9..0da02fda 100644 --- a/configure.ac +++ b/configure.ac @@ -633,6 +633,31 @@ AM_CONDITIONAL(HAVE_WEBP, [test "x$HAVE_WEBP" = xyes]) AC_SUBST(WEBP_CFLAGS) AC_SUBST(WEBP_LIBS) +# J2K support +# ---------------------------------------------------------------------- + +AC_ARG_ENABLE([j2k], + AC_HELP_STRING([--disable-j2k], [disable j2k support]), + [libopenjp2=$enableval], [libopenjp2=auto]) + +if test "x${libopenjp2}" != "xno"; then + PKG_CHECK_MODULES(J2K, libopenjp2 >= 2.3.0, + [ + HAVE_J2K=yes + AC_DEFINE(HAVE_J2K, 1, [define to enable j2k support]) + ], + [ + HAVE_J2K=no + AC_MSG_WARN([$J2K_PKG_ERRORS]) + ]) +else + HAVE_J2K=disabled +fi + +AM_CONDITIONAL(HAVE_J2K, [test "x$HAVE_J2K" = xyes]) +AC_SUBST(J2K_CFLAGS) +AC_SUBST(J2K_LIBS) + # DjVu support # ---------------------------------------------------------------------- @@ -781,6 +806,7 @@ Support: HEIF: $HAVE_HEIF WebP: $HAVE_WEBP DjVu: $HAVE_DJVU + J2K: $HAVE_J2K Documentation: Doxygen: $DX_DOXYGEN diff --git a/src/Makefile.am b/src/Makefile.am index 311637e1..a477118f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ AM_CFLAGS = \ $(PDF_CFLAGS) \ $(HEIF_CFLAGS) \ $(WEBP_CFLAGS) \ + $(J2K_CFLAGS) \ -I$(top_srcdir) \ -I$(top_builddir) @@ -31,6 +32,7 @@ AM_CXXFLAGS = \ $(PDF_CFLAGS) \ $(HEIF_CFLAGS) \ $(WEBP_CFLAGS) \ + $(J2K_CFLAGS) \ -I$(top_srcdir) \ -I$(top_builddir) @@ -199,6 +201,8 @@ geeqie_SOURCES = \ image_load_djvu.h\ image_load_psd.c\ image_load_psd.h\ + image_load_j2k.c\ + image_load_j2k.h\ image_load_ffmpegthumbnailer.c\ image_load_ffmpegthumbnailer.h\ image-overlay.c \ @@ -288,7 +292,7 @@ geeqie_SOURCES = \ zonedetect.c \ zonedetect.h -geeqie_LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(INTLLIBS) $(JPEG_LIBS) $(TIFF_LIBS) $(LCMS_LIBS) $(EXIV2_LIBS) $(LIBCHAMPLAIN_LIBS) $(LIBCHAMPLAIN_GTK_LIBS) $(LUA_LIBS) $(CLUTTER_LIBS) $(CLUTTER_GTK_LIBS) $(FFMPEGTHUMBNAILER_LIBS) $(PDF_LIBS) $(HEIF_LIBS) $(WEBP_LIBS) $(DJVU_LIBS) +geeqie_LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(INTLLIBS) $(JPEG_LIBS) $(TIFF_LIBS) $(LCMS_LIBS) $(EXIV2_LIBS) $(LIBCHAMPLAIN_LIBS) $(LIBCHAMPLAIN_GTK_LIBS) $(LUA_LIBS) $(CLUTTER_LIBS) $(CLUTTER_GTK_LIBS) $(FFMPEGTHUMBNAILER_LIBS) $(PDF_LIBS) $(HEIF_LIBS) $(WEBP_LIBS) $(DJVU_LIBS) $(J2K_LIBS) EXTRA_DIST = \ $(extra_SLIK) diff --git a/src/filefilter.c b/src/filefilter.c index 3db08d33..f80d4a9b 100644 --- a/src/filefilter.c +++ b/src/filefilter.c @@ -302,6 +302,9 @@ void filter_add_defaults(void) #endif #ifdef HAVE_DJVU filter_add_if_missing("djvu", "DjVu Format", ".djvu;.djv", FORMAT_CLASS_DOCUMENT, FALSE, FALSE, TRUE); +#endif +#ifdef HAVE_J2K + filter_add_if_missing("jp2", "JPEG 2000", ".jp2", FORMAT_CLASS_IMAGE, FALSE, FALSE, TRUE); #endif filter_add_if_missing("psd", "Adobe Photoshop Document", ".psd", FORMAT_CLASS_IMAGE, FALSE, FALSE, TRUE); } diff --git a/src/image-load.c b/src/image-load.c index 16c40daa..2504ff8a 100644 --- a/src/image-load.c +++ b/src/image-load.c @@ -32,6 +32,7 @@ #include "image_load_ffmpegthumbnailer.h" #include "image_load_collection.h" #include "image_load_webp.h" +#include "image_load_j2k.h" #include "exif.h" #include "filedata.h" @@ -696,6 +697,15 @@ static void image_loader_setup_loader(ImageLoader *il) image_loader_backend_set_psd(&il->backend); } else +#ifdef HAVE_J2K + if (il->bytes_total >= 12 && + (memcmp(il->mapped_file, "\0\0\0\x0CjP\x20\x20\x0D\x0A\x87\x0A", 12) == 0)) + { + DEBUG_1("Using custom j2k loader"); + image_loader_backend_set_j2k(&il->backend); + } + else +#endif if (il->fd->format_class == FORMAT_CLASS_COLLECTION) { DEBUG_1("Using custom collection loader"); diff --git a/src/image_load_j2k.c b/src/image_load_j2k.c new file mode 100644 index 00000000..c7ea0cc0 --- /dev/null +++ b/src/image_load_j2k.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 20019 - The Geeqie Team + * + * Author: Colin Clark + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "main.h" + +#include "image-load.h" +#include "image_load_j2k.h" + +#include "misc.h" + +#include +#ifdef HAVE_J2K +#include "openjpeg.h" + +typedef struct _ImageLoaderJ2K ImageLoaderJ2K; +struct _ImageLoaderJ2K { + ImageLoaderBackendCbAreaUpdated area_updated_cb; + ImageLoaderBackendCbSize size_cb; + ImageLoaderBackendCbAreaPrepared area_prepared_cb; + gpointer data; + GdkPixbuf *pixbuf; + guint requested_width; + guint requested_height; + gboolean abort; +}; + +static void free_buffer(guchar *pixels, gpointer data) +{ + g_free (pixels); +} + +typedef struct opj_buffer_info { + OPJ_BYTE* buf; + OPJ_BYTE* cur; + OPJ_SIZE_T len; +} opj_buffer_info_t; + +static OPJ_SIZE_T opj_read_from_buffer (void* pdst, OPJ_SIZE_T len, opj_buffer_info_t* psrc) +{ + OPJ_SIZE_T n = psrc->buf + psrc->len - psrc->cur; + + if (n) { + if (n > len) + n = len; + + memcpy (pdst, psrc->cur, n); + psrc->cur += n; + } + else + n = (OPJ_SIZE_T)-1; + + return n; +} + +static OPJ_SIZE_T opj_write_to_buffer (void* p_buffer, OPJ_SIZE_T p_nb_bytes, + opj_buffer_info_t* p_source_buffer) +{ + void* pbuf = p_source_buffer->buf; + void* pcur = p_source_buffer->cur; + + OPJ_SIZE_T len = p_source_buffer->len; + + if (0 == len) + len = 1; + + OPJ_SIZE_T dist = pcur - pbuf, n = len - dist; + g_assert (dist <= len); + + while (n < p_nb_bytes) { + len *= 2; + n = len - dist; + } + + if (len != p_source_buffer->len) { + pbuf = malloc (len); + + if (0 == pbuf) + return (OPJ_SIZE_T)-1; + + if (p_source_buffer->buf) { + memcpy (pbuf, p_source_buffer->buf, dist); + free (p_source_buffer->buf); + } + + p_source_buffer->buf = pbuf; + p_source_buffer->cur = pbuf + dist; + p_source_buffer->len = len; + } + + memcpy (p_source_buffer->cur, p_buffer, p_nb_bytes); + p_source_buffer->cur += p_nb_bytes; + + return p_nb_bytes; +} + +static OPJ_SIZE_T opj_skip_from_buffer (OPJ_SIZE_T len, opj_buffer_info_t* psrc) +{ + OPJ_SIZE_T n = psrc->buf + psrc->len - psrc->cur; + + if (n) { + if (n > len) + n = len; + + psrc->cur += len; + } + else + n = (OPJ_SIZE_T)-1; + + return n; +} + +static OPJ_BOOL opj_seek_from_buffer (OPJ_OFF_T len, opj_buffer_info_t* psrc) +{ + OPJ_SIZE_T n = psrc->len; + + if (n > len) + n = len; + + psrc->cur = psrc->buf + n; + + return OPJ_TRUE; +} + +opj_stream_t* OPJ_CALLCONV opj_stream_create_buffer_stream (opj_buffer_info_t* psrc, OPJ_BOOL input) +{ + if (!psrc) + return 0; + + opj_stream_t* ps = opj_stream_default_create (input); + + if (0 == ps) + return 0; + + opj_stream_set_user_data (ps, psrc, 0); + opj_stream_set_user_data_length (ps, psrc->len); + + if (input) + opj_stream_set_read_function ( + ps, (opj_stream_read_fn)opj_read_from_buffer); + else + opj_stream_set_write_function( + ps,(opj_stream_write_fn) opj_write_to_buffer); + + opj_stream_set_skip_function ( + ps, (opj_stream_skip_fn)opj_skip_from_buffer); + + opj_stream_set_seek_function ( + ps, (opj_stream_seek_fn)opj_seek_from_buffer); + + return ps; +} + +static gboolean image_loader_j2k_load(gpointer loader, const guchar *buf, gsize count, GError **error) +{ + ImageLoaderJ2K *ld = (ImageLoaderJ2K *) loader; + ImageLoader *il = ld->data; + opj_stream_t *stream; + opj_codec_t *codec; + opj_dparameters_t parameters; + opj_image_t *image; + gint width; + gint height; + gint num_components; + gint i, j, k; + guchar *pixels; + gint bytes_per_pixel; + opj_buffer_info_t *decode_buffer; + guchar *buf_copy; + + stream = NULL; + codec = NULL; + image = NULL; + + buf_copy = (guchar *) g_malloc(count); + memcpy(buf_copy, buf, count); + + decode_buffer = g_new0(opj_buffer_info_t, 1); + decode_buffer->buf = buf_copy; + decode_buffer->len = count; + decode_buffer->cur = buf_copy; + + stream = opj_stream_create_buffer_stream(decode_buffer, OPJ_TRUE); + + if (!stream) + { + log_printf(_("Could not open file for reading")); + return FALSE; + } + + if (memcmp(buf_copy + 20, "jp2", 3) == 0) + { + codec = opj_create_decompress(OPJ_CODEC_JP2); + } + else + { + log_printf(_("Unknown jpeg2000 decoder type")); + return FALSE; + } + + opj_set_default_decoder_parameters(¶meters); + if (opj_setup_decoder (codec, ¶meters) != OPJ_TRUE) + { + log_printf(_("Couldn't set parameters on decoder for file.")); + return FALSE; + } + + opj_codec_set_threads(codec, get_cpu_cores()); + + if (opj_read_header(stream, codec, &image) != OPJ_TRUE) + { + log_printf(_("Couldn't read JP2 header from file")); + return FALSE; + } + + if (opj_decode(codec, stream, image) != OPJ_TRUE) + { + log_printf(_("Couldn't decode JP2 image in file")); + return FALSE; + } + + if (opj_end_decompress(codec, stream) != OPJ_TRUE) + { + log_printf(_("Couldn't decompress JP2 image in file")); + return FALSE; + } + + num_components = image->numcomps; + if (num_components != 3) + { + log_printf(_("JP2 image not rgb")); + return FALSE; + } + + width = image->comps[0].w; + height = image->comps[0].h; + + bytes_per_pixel = 3; + + pixels = g_new0(guchar, width * bytes_per_pixel * height); + for (i = 0; i < height; i++) + { + for (j = 0; j < num_components; j++) + { + for (k = 0; k < width; k++) + { + pixels[(k * bytes_per_pixel + j) + (i * width * bytes_per_pixel)] = image->comps[j].data[i * width + k]; + } + } + } + + ld->pixbuf = gdk_pixbuf_new_from_data(pixels, GDK_COLORSPACE_RGB, FALSE , 8, width, height, width * bytes_per_pixel, free_buffer, NULL); + + ld->area_updated_cb(loader, 0, 0, width, height, ld->data); + + g_free(decode_buffer); + g_free(buf_copy); + if (image) + opj_image_destroy (image); + if (codec) + opj_destroy_codec (codec); + if (stream) + opj_stream_destroy (stream); + + return TRUE; +} + +static gpointer image_loader_j2k_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data) +{ + ImageLoaderJ2K *loader = g_new0(ImageLoaderJ2K, 1); + loader->area_updated_cb = area_updated_cb; + loader->size_cb = size_cb; + loader->area_prepared_cb = area_prepared_cb; + loader->data = data; + return (gpointer) loader; +} + +static void image_loader_j2k_set_size(gpointer loader, int width, int height) +{ + ImageLoaderJ2K *ld = (ImageLoaderJ2K *) loader; + ld->requested_width = width; + ld->requested_height = height; +} + +static GdkPixbuf* image_loader_j2k_get_pixbuf(gpointer loader) +{ + ImageLoaderJ2K *ld = (ImageLoaderJ2K *) loader; + return ld->pixbuf; +} + +static gchar* image_loader_j2k_get_format_name(gpointer loader) +{ + return g_strdup("j2k"); +} + +static gchar** image_loader_j2k_get_format_mime_types(gpointer loader) +{ + static gchar *mime[] = {"image/jp2", NULL}; + return g_strdupv(mime); +} + +static gboolean image_loader_j2k_close(gpointer loader, GError **error) +{ + return TRUE; +} + +static void image_loader_j2k_abort(gpointer loader) +{ + ImageLoaderJ2K *ld = (ImageLoaderJ2K *) loader; + ld->abort = TRUE; +} + +static void image_loader_j2k_free(gpointer loader) +{ + ImageLoaderJ2K *ld = (ImageLoaderJ2K *) loader; + if (ld->pixbuf) g_object_unref(ld->pixbuf); + g_free(ld); +} + +void image_loader_backend_set_j2k(ImageLoaderBackend *funcs) +{ + funcs->loader_new = image_loader_j2k_new; + funcs->set_size = image_loader_j2k_set_size; + funcs->load = image_loader_j2k_load; + funcs->write = NULL; + funcs->get_pixbuf = image_loader_j2k_get_pixbuf; + funcs->close = image_loader_j2k_close; + funcs->abort = image_loader_j2k_abort; + funcs->free = image_loader_j2k_free; + funcs->get_format_name = image_loader_j2k_get_format_name; + funcs->get_format_mime_types = image_loader_j2k_get_format_mime_types; +} + +#endif +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/image_load_j2k.h b/src/image_load_j2k.h new file mode 100644 index 00000000..a02fa343 --- /dev/null +++ b/src/image_load_j2k.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 20019 - The Geeqie Team + * + * Author: Colin Clark + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef IMAGE_LOAD_J2K_H +#define IMAGE_LOAD_J2K_H + +#ifdef HAVE_J2K +void image_loader_backend_set_j2k(ImageLoaderBackend *funcs); +#endif + +#endif +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/misc.c b/src/misc.c index 85c87658..104d5de4 100644 --- a/src/misc.c +++ b/src/misc.c @@ -376,4 +376,31 @@ gchar *get_symbolic_link(const gchar *path_utf8) return ret; } +gint get_cpu_cores(void) +{ + FILE *cpuinfo = fopen("/proc/cpuinfo", "rb"); + char *arg = 0; + size_t size = 0; + int cores = 1; + gchar *siblings_line; + gchar *siblings_str; + + while(getline(&arg, &size, cpuinfo) != -1) + { + siblings_line = g_strrstr(arg, "siblings"); + if (siblings_line) + { + siblings_str = g_strrstr(siblings_line, ":"); + if (siblings_str) + { + cores = g_ascii_strtoll(siblings_str + 1, NULL, 0); + } + } + } + free(arg); + fclose(cpuinfo); + + return cores; +} + /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/misc.h b/src/misc.h index 1071062f..f602312a 100644 --- a/src/misc.h +++ b/src/misc.h @@ -31,5 +31,6 @@ gint date_get_first_day_of_week(); gchar *date_get_abbreviated_day_name(gint day); gchar *convert_rating_to_stars(gint rating); gchar *get_symbolic_link(const gchar *path_utf8); +gint get_cpu_cores(void); #endif /* MISC_H */ /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/web/geeqie-install-debian.sh b/web/geeqie-install-debian.sh index 91b9ae0e..29267926 100755 --- a/web/geeqie-install-debian.sh +++ b/web/geeqie-install-debian.sh @@ -1,5 +1,5 @@ #!/bin/bash -version="2019-08-11" +version="2019-08-13" description=$' Geeqie is an image viewer. This script will download, compile, and install Geeqie on Debian-based systems. @@ -66,6 +66,8 @@ optional_array=( "libwebp-dev" "libdjvulibre (for DjVu images)" "libdjvulibre-dev" +"libopenjp2 (for JP2 images)" +"libopenjp2-7-dev" ) # Optional for GTK3 only -- 2.20.1