From ec75c814d6f79ec16426ee40aacbf7d0aded8e41 Mon Sep 17 00:00:00 2001 From: Vladimir Nadvornik Date: Sat, 26 Mar 2011 22:03:30 +0100 Subject: [PATCH] added custom jpeg loader --- configure.in | 22 ++- src/Makefile.am | 4 +- src/image-load.c | 34 ++++- src/image-load.h | 32 +++-- src/image_load_gdk.c | 32 +++-- src/image_load_jpeg.c | 310 ++++++++++++++++++++++++++++++++++++++++++ src/image_load_jpeg.h | 8 ++ 7 files changed, 415 insertions(+), 27 deletions(-) create mode 100644 src/image_load_jpeg.c create mode 100644 src/image_load_jpeg.h diff --git a/configure.in b/configure.in index ac8415e7..b390258a 100644 --- a/configure.in +++ b/configure.in @@ -291,6 +291,26 @@ AM_CONDITIONAL(HAVE_LCMS, [test "x$HAVE_LCMS" = xyes]) AC_SUBST(LCMS_CFLAGS) AC_SUBST(LCMS_LIBS) +# libjpeg support +# ---------------------------------------------------------------------- + +AC_ARG_ENABLE([jpeg], + AC_HELP_STRING([--disable-jpeg], [disable direct jpeg support]), + [libjpeg=$enableval], [libjpeg=auto]) + +if test "x${libjpeg}" != "xno"; then + AC_CHECK_LIB(jpeg, jpeg_destroy_decompress, + HAVE_JPEG=yes + JPEG_LIBS=-ljpeg, + HAVE_JPEG=no) +else + HAVE_JPEG=disabled +fi + +AM_CONDITIONAL(HAVE_JPEG, [test "x$HAVE_JPEG" = xyes]) +AC_SUBST(JPEG_CFLAGS) +AC_SUBST(JPEG_LIBS) + # Exiv2 support # ---------------------------------------------------------------------- @@ -449,7 +469,7 @@ Flags: Gtk: $GTK_CFLAGS Glib: $GLIB_CFLAGS Thread: $GTHREAD_LIBS - Others: $LCMS_LIBS $EXIV2_LIBS $LIBCHAMPLAIN_LIBS $LIBCHAMPLAIN_GTK_LIBS + Others: $JPEG_LIBS $LCMS_LIBS $EXIV2_LIBS $LIBCHAMPLAIN_LIBS $LIBCHAMPLAIN_GTK_LIBS Localization: NLS support: $USE_NLS diff --git a/src/Makefile.am b/src/Makefile.am index f794c92a..05c55cfb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -162,6 +162,8 @@ geeqie_SOURCES = \ image-load.h \ image_load_gdk.c\ image_load_gdk.h\ + image_load_jpeg.c\ + image_load_jpeg.h\ image-overlay.c \ image-overlay.h \ img-view.c \ @@ -247,7 +249,7 @@ geeqie_SOURCES = \ window.c \ window.h -geeqie_LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(INTLLIBS) $(LCMS_LIBS) $(EXIV2_LIBS) $(LIBCHAMPLAIN_LIBS) $(LIBCHAMPLAIN_GTK_LIBS) +geeqie_LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(INTLLIBS) $(JPEG_LIBS) $(LCMS_LIBS) $(EXIV2_LIBS) $(LIBCHAMPLAIN_LIBS) $(LIBCHAMPLAIN_GTK_LIBS) EXTRA_DIST = \ $(extra_SLIK) diff --git a/src/image-load.c b/src/image-load.c index 7677fc87..c28df2e2 100644 --- a/src/image-load.c +++ b/src/image-load.c @@ -14,6 +14,7 @@ #include "main.h" #include "image-load.h" #include "image_load_gdk.h" +#include "image_load_jpeg.h" #include "exif.h" #include "filedata.h" @@ -316,7 +317,7 @@ static void image_loader_emit_area_ready(ImageLoader *il, guint x, guint y, guin /* the following functions may be executed in separate thread */ /* this function expects that il->data_mutex is locked by caller */ -static void image_loader_queue_delayed_erea_ready(ImageLoader *il, guint x, guint y, guint w, guint h) +static void image_loader_queue_delayed_area_ready(ImageLoader *il, guint x, guint y, guint w, guint h) { ImageLoaderAreaParam *par = g_new0(ImageLoaderAreaParam, 1); par->il = il; @@ -393,9 +394,12 @@ static void image_loader_area_updated_cb(gpointer loader, g_mutex_lock(il->data_mutex); if (il->delay_area_ready) - image_loader_queue_delayed_erea_ready(il, x, y, w, h); + image_loader_queue_delayed_area_ready(il, x, y, w, h); else image_loader_emit_area_ready(il, x, y, w, h); + + if (il->stopping) il->backend.abort(il->loader); + g_mutex_unlock(il->data_mutex); } @@ -498,7 +502,7 @@ static void image_loader_stop_loader(ImageLoader *il) /* some loaders do not have a pixbuf till close, order is important here */ il->backend.close(il->loader, il->error ? NULL : &il->error); /* we are interested in the first error only */ image_loader_sync_pixbuf(il); - g_object_unref(G_OBJECT(il->loader)); + il->backend.free(il->loader); il->loader = NULL; } g_mutex_lock(il->data_mutex); @@ -509,8 +513,15 @@ static void image_loader_stop_loader(ImageLoader *il) static void image_loader_setup_loader(ImageLoader *il) { g_mutex_lock(il->data_mutex); - image_loader_backend_set_default(&il->backend); - il->loader = il->backend.loader_new(G_CALLBACK(image_loader_area_updated_cb), G_CALLBACK(image_loader_size_cb), G_CALLBACK(image_loader_area_prepared_cb), il); + if (il->bytes_total >= 2 && il->mapped_file[0] == 0xff && il->mapped_file[1] == 0xd8) + { + DEBUG_1("Using custom jpeg loader"); + image_loader_backend_set_jpeg(&il->backend); + } + else + image_loader_backend_set_default(&il->backend); + + il->loader = il->backend.loader_new(image_loader_area_updated_cb, image_loader_size_cb, image_loader_area_prepared_cb, il); g_mutex_unlock(il->data_mutex); } @@ -578,8 +589,17 @@ static gboolean image_loader_begin(ImageLoader *il) if (b < 1) return FALSE; image_loader_setup_loader(il); - - if (!il->backend.write(il->loader, il->mapped_file + il->bytes_read, b, &il->error)) + + g_assert(il->bytes_read == 0); + if (il->backend.load) { + b = il->bytes_total; + if (!il->backend.load(il->loader, il->mapped_file, b, &il->error)) + { + image_loader_stop_loader(il); + return FALSE; + } + } + else if (!il->backend.write(il->loader, il->mapped_file, b, &il->error)) { image_loader_stop_loader(il); return FALSE; diff --git a/src/image-load.h b/src/image-load.h index c6ffbbd9..90e1260e 100644 --- a/src/image-load.h +++ b/src/image-load.h @@ -16,18 +16,34 @@ #define TYPE_IMAGE_LOADER (image_loader_get_type()) +typedef void (*ImageLoaderBackendCbAreaPrepared)(gpointer loader, gpointer data); +typedef void (*ImageLoaderBackendCbSize)(gpointer loader, gint width, gint height, gpointer data); +typedef void (*ImageLoaderBackendCbAreaUpdated)(gpointer loader, guint x, guint y, guint w, guint h, gpointer data); + +typedef gpointer (*ImageLoaderBackendFuncLoaderNew)(ImageLoaderBackendCbAreaUpdated, ImageLoaderBackendCbSize, ImageLoaderBackendCbAreaPrepared, gpointer data); +typedef void (*ImageLoaderBackendFuncSetSize)(gpointer loader, int width, int height); +typedef gboolean (*ImageLoaderBackendFuncLoad)(gpointer loader, const guchar *buf, gsize count, GError **error); /* optional, load whole image at once */ +typedef gboolean (*ImageLoaderBackendFuncWrite)(gpointer loader, const guchar *buf, gsize count, GError **error); +typedef GdkPixbuf* (*ImageLoaderBackendFuncGetPixbuf)(gpointer loader); +typedef gboolean (*ImageLoaderBackendFuncClose)(gpointer loader, GError **error); +typedef void (*ImageLoaderBackendFuncAbort)(gpointer loader); +typedef void (*ImageLoaderBackendFuncFree)(gpointer loader); +typedef gchar* (*ImageLoaderBackendFuncGetFormatName)(gpointer loader); +typedef gchar** (*ImageLoaderBackendFuncGetFormatMimeTypes)(gpointer loader); typedef struct _ImageLoaderBackend ImageLoaderBackend; struct _ImageLoaderBackend { - gpointer (*loader_new)(GCallback area_updated_cb, GCallback size_cb, GCallback area_prepared_cb, gpointer data); - void (*set_size)(gpointer loader, int width, int height); - gboolean (*write)(gpointer loader, const guchar *buf, gsize count, GError **error); - GdkPixbuf* (*get_pixbuf)(gpointer loader); - gboolean (*close)(gpointer loader, GError **error); - - gchar* (*get_format_name)(gpointer loader); - gchar** (*get_format_mime_types)(gpointer loader); + ImageLoaderBackendFuncLoaderNew loader_new; + ImageLoaderBackendFuncSetSize set_size; + ImageLoaderBackendFuncLoad load; + ImageLoaderBackendFuncWrite write; + ImageLoaderBackendFuncGetPixbuf get_pixbuf; + ImageLoaderBackendFuncClose close; + ImageLoaderBackendFuncAbort abort; + ImageLoaderBackendFuncFree free; + ImageLoaderBackendFuncGetFormatName get_format_name; + ImageLoaderBackendFuncGetFormatMimeTypes get_format_mime_types; }; diff --git a/src/image_load_gdk.c b/src/image_load_gdk.c index 722d70c5..77cb9279 100644 --- a/src/image_load_gdk.c +++ b/src/image_load_gdk.c @@ -4,32 +4,44 @@ #include "image_load_gdk.h" -static gchar* image_loader_gdk_get_format_name(GObject *loader) +static gchar* image_loader_gdk_get_format_name(gpointer loader) { return gdk_pixbuf_format_get_name(gdk_pixbuf_loader_get_format(GDK_PIXBUF_LOADER(loader))); } -static gchar** image_loader_gdk_get_format_mime_types(GObject *loader) +static gchar** image_loader_gdk_get_format_mime_types(gpointer loader) { return gdk_pixbuf_format_get_mime_types(gdk_pixbuf_loader_get_format(GDK_PIXBUF_LOADER(loader))); } -static gpointer image_loader_gdk_new(GCallback area_updated_cb, GCallback size_cb, GCallback area_prepared_cb, gpointer data) +static gpointer image_loader_gdk_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data) { GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); - g_signal_connect(G_OBJECT(loader), "area_updated", area_updated_cb, data); - g_signal_connect(G_OBJECT(loader), "size_prepared", size_cb, data); - g_signal_connect(G_OBJECT(loader), "area_prepared", area_prepared_cb, data); + g_signal_connect(G_OBJECT(loader), "area_updated", G_CALLBACK(area_updated_cb), data); + g_signal_connect(G_OBJECT(loader), "size_prepared", G_CALLBACK(size_cb), data); + g_signal_connect(G_OBJECT(loader), "area_prepared", G_CALLBACK(area_prepared_cb), data); return (gpointer) loader; } +static void image_loader_gdk_abort(gpointer loader) +{ +} + +static void image_loader_gdk_free(gpointer loader) +{ + g_object_unref(G_OBJECT(loader)); +} + void image_loader_backend_set_default(ImageLoaderBackend *funcs) { funcs->loader_new = image_loader_gdk_new; - funcs->set_size = gdk_pixbuf_loader_set_size; - funcs->write = gdk_pixbuf_loader_write; - funcs->get_pixbuf = gdk_pixbuf_loader_get_pixbuf; - funcs->close = gdk_pixbuf_loader_close; + funcs->set_size = (ImageLoaderBackendFuncSetSize) gdk_pixbuf_loader_set_size; + funcs->load = NULL; + funcs->write = (ImageLoaderBackendFuncWrite) gdk_pixbuf_loader_write; + funcs->get_pixbuf = (ImageLoaderBackendFuncGetPixbuf) gdk_pixbuf_loader_get_pixbuf; + funcs->close = (ImageLoaderBackendFuncClose) gdk_pixbuf_loader_close; + funcs->abort = image_loader_gdk_abort; + funcs->free = image_loader_gdk_free; funcs->get_format_name = image_loader_gdk_get_format_name; funcs->get_format_mime_types = image_loader_gdk_get_format_mime_types; diff --git a/src/image_load_jpeg.c b/src/image_load_jpeg.c new file mode 100644 index 00000000..7fb60901 --- /dev/null +++ b/src/image_load_jpeg.c @@ -0,0 +1,310 @@ + +#include "main.h" +#include "image-load.h" +#include "image_load_jpeg.h" + +#include +#include +#include + +typedef struct _ImageLoaderJpeg ImageLoaderJpeg; +struct _ImageLoaderJpeg { + ImageLoaderBackendCbAreaUpdated area_updated_cb; + ImageLoaderBackendCbSize size_cb; + ImageLoaderBackendCbAreaPrepared area_prepared_cb; + + gpointer data; + + GdkPixbuf *pixbuf; + guint requested_width; + guint requested_height; + + gboolean abort; + +}; + +/* error handler data */ +struct error_handler_data { + struct jpeg_error_mgr pub; + sigjmp_buf setjmp_buffer; + GError **error; +}; + +/* explode gray image data from jpeg library into rgb components in pixbuf */ +static void +explode_gray_into_buf (struct jpeg_decompress_struct *cinfo, + guchar **lines) +{ + gint i, j; + guint w; + + g_return_if_fail (cinfo != NULL); + g_return_if_fail (cinfo->output_components == 1); + g_return_if_fail (cinfo->out_color_space == JCS_GRAYSCALE); + + /* Expand grey->colour. Expand from the end of the + * memory down, so we can use the same buffer. + */ + w = cinfo->output_width; + for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) { + guchar *from, *to; + + from = lines[i] + w - 1; + to = lines[i] + (w - 1) * 3; + for (j = w - 1; j >= 0; j--) { + to[0] = from[0]; + to[1] = from[0]; + to[2] = from[0]; + to -= 3; + from--; + } + } +} + + +static void +convert_cmyk_to_rgb (struct jpeg_decompress_struct *cinfo, + guchar **lines) +{ + gint i, j; + + g_return_if_fail (cinfo != NULL); + g_return_if_fail (cinfo->output_components == 4); + g_return_if_fail (cinfo->out_color_space == JCS_CMYK); + + for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) { + guchar *p; + + p = lines[i]; + for (j = 0; j < cinfo->output_width; j++) { + int c, m, y, k; + c = p[0]; + m = p[1]; + y = p[2]; + k = p[3]; + if (cinfo->saw_Adobe_marker) { + p[0] = k*c / 255; + p[1] = k*m / 255; + p[2] = k*y / 255; + } + else { + p[0] = (255 - k)*(255 - c) / 255; + p[1] = (255 - k)*(255 - m) / 255; + p[2] = (255 - k)*(255 - y) / 255; + } + p[3] = 255; + p += 4; + } + } +} + + +static gpointer image_loader_jpeg_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data) +{ + ImageLoaderJpeg *loader = g_new0(ImageLoaderJpeg, 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 +fatal_error_handler (j_common_ptr cinfo) +{ + struct error_handler_data *errmgr; + char buffer[JMSG_LENGTH_MAX]; + + errmgr = (struct error_handler_data *) cinfo->err; + + /* Create the message */ + (* cinfo->err->format_message) (cinfo, buffer); + + /* broken check for *error == NULL for robustness against + * crappy JPEG library + */ + if (errmgr->error && *errmgr->error == NULL) { + g_set_error (errmgr->error, + GDK_PIXBUF_ERROR, + cinfo->err->msg_code == JERR_OUT_OF_MEMORY + ? GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY + : GDK_PIXBUF_ERROR_CORRUPT_IMAGE, + _("Error interpreting JPEG image file (%s)"), + buffer); + } + + siglongjmp (errmgr->setjmp_buffer, 1); + + g_assert_not_reached (); +} + +static void +output_message_handler (j_common_ptr cinfo) +{ + /* This method keeps libjpeg from dumping crap to stderr */ + + /* do nothing */ +} + +static gboolean image_loader_jpeg_load (gpointer loader, const guchar *buf, gsize count, GError **error) +{ + ImageLoaderJpeg *lj = (ImageLoaderJpeg *) loader; + struct jpeg_decompress_struct cinfo; + guchar *dptr; + guint rowstride; + + struct error_handler_data jerr; +// stdio_src_ptr src; + + /* setup error handler */ + cinfo.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = fatal_error_handler; + jerr.pub.output_message = output_message_handler; + + jerr.error = error; + + + if (setjmp(jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_decompress(&cinfo); + return FALSE; + } + + jpeg_create_decompress(&cinfo); + + jpeg_mem_src(&cinfo, (unsigned char *)buf, count); + + + jpeg_read_header(&cinfo, TRUE); + + lj->requested_width = cinfo.image_width; + lj->requested_height = cinfo.image_height; + lj->size_cb(loader, cinfo.image_width, cinfo.image_height, lj->data); + + cinfo.scale_num = 1; + for (cinfo.scale_denom = 2; cinfo.scale_denom <= 8; cinfo.scale_denom *= 2) { + jpeg_calc_output_dimensions(&cinfo); + if (cinfo.output_width < lj->requested_width || cinfo.output_height < lj->requested_height) { + cinfo.scale_denom /= 2; + break; + } + } + jpeg_calc_output_dimensions(&cinfo); + + jpeg_start_decompress(&cinfo); + + lj->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + cinfo.out_color_components == 4 ? TRUE : FALSE, + 8, cinfo.output_width, cinfo.output_height); + + lj->area_prepared_cb(loader, lj->data); + rowstride = gdk_pixbuf_get_rowstride(lj->pixbuf); + dptr = gdk_pixbuf_get_pixels(lj->pixbuf); + + if (!lj->pixbuf) + { + jpeg_destroy_decompress (&cinfo); + return 0; + } + + while (cinfo.output_scanline < cinfo.output_height && !lj->abort) + { + guchar *lines[4]; + guchar **lptr; + gint i; + guint scanline = cinfo.output_scanline; + + lptr = lines; + for (i = 0; i < cinfo.rec_outbuf_height; i++) + { + *lptr++ = dptr; + dptr += rowstride; + } + + jpeg_read_scanlines (&cinfo, lines, cinfo.rec_outbuf_height); + + switch (cinfo.out_color_space) + { + case JCS_GRAYSCALE: + explode_gray_into_buf (&cinfo, lines); + break; + case JCS_RGB: + /* do nothing */ + break; + case JCS_CMYK: + convert_cmyk_to_rgb (&cinfo, lines); + break; + default: + break; + } + lj->area_updated_cb(loader, 0, scanline, cinfo.output_width, cinfo.rec_outbuf_height, lj->data); + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + return TRUE; +} + +static void image_loader_jpeg_set_size(gpointer loader, int width, int height) +{ + ImageLoaderJpeg *lj = (ImageLoaderJpeg *) loader; + lj->requested_width = width; + lj->requested_height = height; +} + +static GdkPixbuf* image_loader_jpeg_get_pixbuf(gpointer loader) +{ + ImageLoaderJpeg *lj = (ImageLoaderJpeg *) loader; + return lj->pixbuf; +} + +static gchar* image_loader_jpeg_get_format_name(gpointer loader) +{ + return g_strdup("jpeg"); +} +static gchar** image_loader_jpeg_get_format_mime_types(gpointer loader) +{ + static gchar *mime[] = {"image/jpeg", NULL}; + return g_strdupv(mime); +} + +static gboolean image_loader_jpeg_close(gpointer loader, GError **error) +{ + return TRUE; +} + +static void image_loader_jpeg_abort(gpointer loader) +{ + ImageLoaderJpeg *lj = (ImageLoaderJpeg *) loader; + lj->abort = TRUE; +} + +static void image_loader_jpeg_free(gpointer loader) +{ + ImageLoaderJpeg *lj = (ImageLoaderJpeg *) loader; + if (lj->pixbuf) g_object_unref(lj->pixbuf); + g_free(lj); +} + + +void image_loader_backend_set_jpeg(ImageLoaderBackend *funcs) +{ + funcs->loader_new = image_loader_jpeg_new; + funcs->set_size = image_loader_jpeg_set_size; + funcs->load = image_loader_jpeg_load; + funcs->write = NULL; + funcs->get_pixbuf = image_loader_jpeg_get_pixbuf; + funcs->close = image_loader_jpeg_close; + funcs->abort = image_loader_jpeg_abort; + funcs->free = image_loader_jpeg_free; + + funcs->get_format_name = image_loader_jpeg_get_format_name; + funcs->get_format_mime_types = image_loader_jpeg_get_format_mime_types; +} + + + diff --git a/src/image_load_jpeg.h b/src/image_load_jpeg.h new file mode 100644 index 00000000..86bf7c6f --- /dev/null +++ b/src/image_load_jpeg.h @@ -0,0 +1,8 @@ + +#ifndef IMAGE_LOAD_JPEG_H +#define IMAGE_LOAD_JPEG_H + +void image_loader_backend_set_jpeg(ImageLoaderBackend *funcs); + +#endif + -- 2.20.1