Include jpeg2000 loader
authorColin Clark <colin.clark@cclark.uk>
Tue, 13 Aug 2019 12:46:17 +0000 (13:46 +0100)
committerColin Clark <colin.clark@cclark.uk>
Tue, 13 Aug 2019 12:46:17 +0000 (13:46 +0100)
Basic .jp2 loader

README.md
configure.ac
src/Makefile.am
src/filefilter.c
src/image-load.c
src/image_load_j2k.c [new file with mode: 0644]
src/image_load_j2k.h [new file with mode: 0644]
src/misc.c
src/misc.h
web/geeqie-install-debian.sh

index 0b944c6..d4fd977 100644 (file)
--- 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
index b5c28dc..0da02fd 100644 (file)
@@ -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
index 311637e..a477118 100644 (file)
@@ -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)
index 3db08d3..f80d4a9 100644 (file)
@@ -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);
 }
index 16c40da..2504ff8 100644 (file)
@@ -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 (file)
index 0000000..c7ea0cc
--- /dev/null
@@ -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 <sys/sysinfo.h>
+#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(&parameters);
+       if (opj_setup_decoder (codec, &parameters) != 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 (file)
index 0000000..a02fa34
--- /dev/null
@@ -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: */
index 85c8765..104d5de 100644 (file)
@@ -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: */
index 1071062..f602312 100644 (file)
@@ -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: */
index 91b9ae0..2926792 100755 (executable)
@@ -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