summaryrefslogtreecommitdiff
path: root/thumbnailer
diff options
context:
space:
mode:
authorBastien Nocera <hadess@hadess.net>2016-06-25 17:39:48 +0200
committerBastien Nocera <hadess@hadess.net>2016-12-12 16:36:21 +0100
commit06cf4c78067203b78acbfb29862350cdb8200b73 (patch)
treeccc30b3bcbaf4cae4c8c4c8afb182a339853ff62 /thumbnailer
parent85b4f6c291c5b928b4b60a31dfac83ec2927f345 (diff)
downloadgdk-pixbuf-06cf4c78067203b78acbfb29862350cdb8200b73.tar.gz
thumbnailer: Add an external thumbnailer for images
So that broken images, or images that use too much RAM can get killed without prejudice. _gdk_pixbuf_new_from_uri_at_scale() and gnome_desktop_thumbnail_scale_down_pixbuf () are directly from gnome-desktop. https://bugzilla.gnome.org/show_bug.cgi?id=768062
Diffstat (limited to 'thumbnailer')
-rw-r--r--thumbnailer/Makefile.am36
-rw-r--r--thumbnailer/gdk-pixbuf-print-mime-types.c29
-rw-r--r--thumbnailer/gdk-pixbuf-thumbnailer.c303
-rw-r--r--thumbnailer/gdk-pixbuf-thumbnailer.thumbnailer.in4
-rw-r--r--thumbnailer/gnome-thumbnailer-skeleton.c324
-rw-r--r--thumbnailer/gnome-thumbnailer-skeleton.h38
6 files changed, 734 insertions, 0 deletions
diff --git a/thumbnailer/Makefile.am b/thumbnailer/Makefile.am
new file mode 100644
index 000000000..d85af210b
--- /dev/null
+++ b/thumbnailer/Makefile.am
@@ -0,0 +1,36 @@
+bin_PROGRAMS = gdk-pixbuf-thumbnailer
+noinst_PROGRAMS = gdk-pixbuf-print-mime-types
+
+gdk_pixbuf_thumbnailer_SOURCES = gdk-pixbuf-thumbnailer.c gnome-thumbnailer-skeleton.c gnome-thumbnailer-skeleton.h
+gdk_pixbuf_thumbnailer_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gdk-pixbuf \
+ $(GDK_PIXBUF_DEP_CFLAGS) \
+ -DTHUMBNAILER_RETURNS_PIXBUF \
+ -DTHUMBNAILER_USAGE="\"Thumbnail images\"" \
+ $(WARN_CFLAGS)
+gdk_pixbuf_thumbnailer_LDADD = \
+ $(GDK_PIXBUF_DEP_LIBS) \
+ $(top_builddir)/gdk-pixbuf/libgdk_pixbuf-$(GDK_PIXBUF_API_VERSION).la
+
+gdk_pixbuf_print_mime_types_SOURCES = gdk-pixbuf-print-mime-types.c
+gdk_pixbuf_print_mime_types_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gdk-pixbuf \
+ $(GDK_PIXBUF_DEP_CFLAGS) \
+ $(WARN_CFLAGS)
+gdk_pixbuf_print_mime_types_LDADD = \
+ $(GDK_PIXBUF_DEP_LIBS) \
+ $(top_builddir)/gdk-pixbuf/libgdk_pixbuf-$(GDK_PIXBUF_API_VERSION).la
+
+thumbnailerdir = $(datadir)/thumbnailers/
+thumbnailer_DATA = gdk-pixbuf-thumbnailer.thumbnailer
+gdk-pixbuf-thumbnailer.thumbnailer: gdk-pixbuf-thumbnailer.thumbnailer.in Makefile gdk-pixbuf-print-mime-types
+ $(AM_V_GEN) GDK_PIXBUF_MODULE_FILE=$(top_builddir)/gdk-pixbuf/loaders.cache \
+ GDK_PIXBUF_PIXDATA=$(top_builddir)/gdk-pixbuf/gdk-pixbuf-pixdata \
+ $(SED) -e "s|\@bindir\@|$(bindir)|" \
+ -e "s|\@mimetypes\@|`$(builddir)/gdk-pixbuf-print-mime-types`|" $< > $@
+
+EXTRA_DIST = gdk-pixbuf-thumbnailer.thumbnailer.in
+
+CLEANFILES = $(thumbnailer_DATA)
diff --git a/thumbnailer/gdk-pixbuf-print-mime-types.c b/thumbnailer/gdk-pixbuf-print-mime-types.c
new file mode 100644
index 000000000..0cf8393ce
--- /dev/null
+++ b/thumbnailer/gdk-pixbuf-print-mime-types.c
@@ -0,0 +1,29 @@
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+int main (int argc, char **argv)
+{
+ GSList *formats, *l;
+ GString *s;
+
+ formats = gdk_pixbuf_get_formats ();
+ s = g_string_new (NULL);
+ for (l = formats; l != NULL; l = l->next) {
+ GdkPixbufFormat *format = l->data;
+ char **mime_types;
+ guint i;
+
+ mime_types = gdk_pixbuf_format_get_mime_types (format);
+ for (i = 0; mime_types[i] != NULL; i++) {
+ g_string_append (s, mime_types[i]);
+ g_string_append (s, ";");
+ }
+
+ g_strfreev (mime_types);
+ }
+ g_slist_free (formats);
+
+ g_print ("%s", s->str);
+ g_string_free (s, TRUE);
+
+ return 0;
+}
diff --git a/thumbnailer/gdk-pixbuf-thumbnailer.c b/thumbnailer/gdk-pixbuf-thumbnailer.c
new file mode 100644
index 000000000..2c7207262
--- /dev/null
+++ b/thumbnailer/gdk-pixbuf-thumbnailer.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2016 Bastien Nocera <hadess@hadess.net>
+ *
+ * Authors: Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <string.h>
+#include <glib.h>
+
+#include "gnome-thumbnailer-skeleton.h"
+
+typedef struct {
+ gint width;
+ gint height;
+ gint input_width;
+ gint input_height;
+ gboolean preserve_aspect_ratio;
+} SizePrepareContext;
+
+#define LOAD_BUFFER_SIZE 4096
+
+static void
+size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer data)
+{
+ SizePrepareContext *info = data;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ info->input_width = width;
+ info->input_height = height;
+
+ if (width < info->width && height < info->height) return;
+
+ if (info->preserve_aspect_ratio &&
+ (info->width > 0 || info->height > 0)) {
+ if (info->width < 0)
+ {
+ width = width * (double)info->height/(double)height;
+ height = info->height;
+ }
+ else if (info->height < 0)
+ {
+ height = height * (double)info->width/(double)width;
+ width = info->width;
+ }
+ else if ((double)height * (double)info->width >
+ (double)width * (double)info->height) {
+ width = 0.5 + (double)width * (double)info->height / (double)height;
+ height = info->height;
+ } else {
+ height = 0.5 + (double)height * (double)info->width / (double)width;
+ width = info->width;
+ }
+ } else {
+ if (info->width > 0)
+ width = info->width;
+ if (info->height > 0)
+ height = info->height;
+ }
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+static GdkPixbufLoader *
+create_loader (GFile *file,
+ const guchar *data,
+ gsize size)
+{
+ GdkPixbufLoader *loader;
+ GError *error = NULL;
+ char *mime_type;
+ char *filename;
+
+ loader = NULL;
+
+ /* need to specify the type here because the gdk_pixbuf_loader_write
+ doesn't have access to the filename in order to correct detect
+ the image type. */
+ filename = g_file_get_basename (file);
+ mime_type = g_content_type_guess (filename, data, size, NULL);
+ g_free (filename);
+
+ if (mime_type != NULL) {
+ loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, &error);
+ }
+
+ if (loader == NULL) {
+ g_debug ("Unable to create loader for mime type %s: %s", mime_type, error->message);
+ g_clear_error (&error);
+ loader = gdk_pixbuf_loader_new ();
+ }
+ g_free (mime_type);
+
+ return loader;
+}
+
+static GdkPixbuf *
+_gdk_pixbuf_new_from_uri_at_scale (const char *uri,
+ gint width,
+ gint height,
+ gboolean preserve_aspect_ratio)
+{
+ gboolean result;
+ guchar buffer[LOAD_BUFFER_SIZE];
+ gssize bytes_read;
+ GdkPixbufLoader *loader = NULL;
+ GdkPixbuf *pixbuf;
+ GdkPixbufAnimation *animation;
+ GdkPixbufAnimationIter *iter;
+ gboolean has_frame;
+ SizePrepareContext info;
+ GFile *file;
+ GFileInfo *file_info;
+ GInputStream *input_stream;
+ GError *error = NULL;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ input_stream = NULL;
+
+ file = g_file_new_for_uri (uri);
+
+ /* First see if we can get an input stream via preview::icon */
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_PREVIEW_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, /* GCancellable */
+ NULL); /* return location for GError */
+ if (file_info != NULL) {
+ GObject *object;
+
+ object = g_file_info_get_attribute_object (file_info,
+ G_FILE_ATTRIBUTE_PREVIEW_ICON);
+ if (object != NULL && G_IS_LOADABLE_ICON (object)) {
+ input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
+ 0, /* size */
+ NULL, /* return location for type */
+ NULL, /* GCancellable */
+ NULL); /* return location for GError */
+ }
+ g_object_unref (file_info);
+ }
+
+ if (input_stream == NULL) {
+ input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
+ if (input_stream == NULL) {
+ g_warning ("Unable to create an input stream for %s: %s", uri, error->message);
+ g_clear_error (&error);
+ g_object_unref (file);
+ return NULL;
+ }
+ }
+
+ has_frame = FALSE;
+
+ result = FALSE;
+ while (!has_frame) {
+
+ bytes_read = g_input_stream_read (input_stream,
+ buffer,
+ sizeof (buffer),
+ NULL,
+ &error);
+ if (bytes_read == -1) {
+ g_warning ("Error reading from %s: %s", uri, error->message);
+ g_clear_error (&error);
+ break;
+ }
+ result = TRUE;
+ if (bytes_read == 0) {
+ break;
+ }
+
+ if (loader == NULL) {
+ loader = create_loader (file, buffer, bytes_read);
+ if (1 <= width || 1 <= height) {
+ info.width = width;
+ info.height = height;
+ info.input_width = info.input_height = 0;
+ info.preserve_aspect_ratio = preserve_aspect_ratio;
+ g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
+ }
+ g_assert (loader != NULL);
+ }
+
+ if (!gdk_pixbuf_loader_write (loader,
+ (unsigned char *)buffer,
+ bytes_read,
+ &error)) {
+ g_warning ("Error creating thumbnail for %s: %s", uri, error->message);
+ g_clear_error (&error);
+ result = FALSE;
+ break;
+ }
+
+ animation = gdk_pixbuf_loader_get_animation (loader);
+ if (animation) {
+ iter = gdk_pixbuf_animation_get_iter (animation, NULL);
+ if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
+ has_frame = TRUE;
+ }
+ g_object_unref (iter);
+ }
+ }
+
+ if (loader == NULL) {
+ /* This can happen if the above loop was exited due to the
+ * g_input_stream_read() call failing. */
+ result = FALSE;
+ } else if (gdk_pixbuf_loader_close (loader, &error) == FALSE &&
+ !g_error_matches (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INCOMPLETE_ANIMATION)) {
+ g_warning ("Error creating thumbnail for %s: %s", uri, error->message);
+ result = FALSE;
+ }
+ g_clear_error (&error);
+
+ if (!result) {
+ g_clear_object (&loader);
+ g_input_stream_close (input_stream, NULL, NULL);
+ g_object_unref (input_stream);
+ g_object_unref (file);
+ return NULL;
+ }
+
+ g_input_stream_close (input_stream, NULL, NULL);
+ g_object_unref (input_stream);
+ g_object_unref (file);
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ if (pixbuf != NULL) {
+ g_object_ref (G_OBJECT (pixbuf));
+ g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width",
+ GINT_TO_POINTER (info.input_width));
+ g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height",
+ GINT_TO_POINTER (info.input_height));
+ }
+ g_object_unref (G_OBJECT (loader));
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+file_to_pixbuf (const char *path,
+ guint destination_size,
+ GError **error)
+{
+ GdkPixbuf *pixbuf, *tmp_pixbuf;
+ GFile *file;
+ char *uri;
+ int original_width, original_height;
+
+ file = g_file_new_for_path (path);
+ uri = g_file_get_uri (file);
+ pixbuf = _gdk_pixbuf_new_from_uri_at_scale (uri, destination_size, destination_size, TRUE);
+ if (pixbuf == NULL) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Generic error");
+ return pixbuf;
+ }
+
+ tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ gdk_pixbuf_copy_options (pixbuf, tmp_pixbuf);
+ gdk_pixbuf_remove_option (tmp_pixbuf, "orientation");
+ g_object_unref (pixbuf);
+ pixbuf = tmp_pixbuf;
+
+ original_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
+ "gnome-original-width"));
+ original_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
+ "gnome-original-height"));
+
+ if (original_width > 0 && original_height > 0) {
+ char *tmp;
+
+ tmp = g_strdup_printf ("%d", original_width);
+ gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", tmp);
+ g_free (tmp);
+
+ tmp = g_strdup_printf ("%d", original_height);
+ gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", tmp);
+ g_free (tmp);
+ }
+
+ return pixbuf;
+}
diff --git a/thumbnailer/gdk-pixbuf-thumbnailer.thumbnailer.in b/thumbnailer/gdk-pixbuf-thumbnailer.thumbnailer.in
new file mode 100644
index 000000000..44f972602
--- /dev/null
+++ b/thumbnailer/gdk-pixbuf-thumbnailer.thumbnailer.in
@@ -0,0 +1,4 @@
+[Thumbnailer Entry]
+TryExec=@bindir@/gdk-pixbuf-thumbnailer
+Exec=@bindir@/gdk-pixbuf-thumbnailer -s %s %u %o
+MimeType=@mimetypes@
diff --git a/thumbnailer/gnome-thumbnailer-skeleton.c b/thumbnailer/gnome-thumbnailer-skeleton.c
new file mode 100644
index 000000000..6224bec28
--- /dev/null
+++ b/thumbnailer/gnome-thumbnailer-skeleton.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2013 Bastien Nocera <hadess@hadess.net>
+ *
+ * Authors: Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <math.h>
+#include <stdlib.h>
+
+#include "gnome-thumbnailer-skeleton.h"
+
+#ifndef THUMBNAILER_USAGE
+#error "THUMBNAILER_USAGE must be set"
+#endif
+
+static int output_size = 256;
+static gboolean g_fatal_warnings = FALSE;
+static char **filenames = NULL;
+
+/**
+ * gnome_desktop_thumbnail_scale_down_pixbuf:
+ * @pixbuf: a #GdkPixbuf
+ * @dest_width: the desired new width
+ * @dest_height: the desired new height
+ *
+ * Scales the pixbuf to the desired size. This function
+ * is a lot faster than gdk-pixbuf when scaling down by
+ * large amounts.
+ *
+ * Return value: (transfer full): a scaled pixbuf
+ *
+ * Since: 2.2
+ **/
+GdkPixbuf *
+gnome_desktop_thumbnail_scale_down_pixbuf (GdkPixbuf *pixbuf,
+ int dest_width,
+ int dest_height)
+{
+ int source_width, source_height;
+ int s_x1, s_y1, s_x2, s_y2;
+ int s_xfrac, s_yfrac;
+ int dx, dx_frac, dy, dy_frac;
+ div_t ddx, ddy;
+ int x, y;
+ int r, g, b, a;
+ int n_pixels;
+ gboolean has_alpha;
+ guchar *dest, *src, *xsrc, *src_pixels;
+ GdkPixbuf *dest_pixbuf;
+ int pixel_stride;
+ int source_rowstride, dest_rowstride;
+
+ if (dest_width == 0 || dest_height == 0) {
+ return NULL;
+ }
+
+ source_width = gdk_pixbuf_get_width (pixbuf);
+ source_height = gdk_pixbuf_get_height (pixbuf);
+
+ g_assert (source_width >= dest_width);
+ g_assert (source_height >= dest_height);
+
+ ddx = div (source_width, dest_width);
+ dx = ddx.quot;
+ dx_frac = ddx.rem;
+
+ ddy = div (source_height, dest_height);
+ dy = ddy.quot;
+ dy_frac = ddy.rem;
+
+ has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+ source_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ src_pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8,
+ dest_width, dest_height);
+ dest = gdk_pixbuf_get_pixels (dest_pixbuf);
+ dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf);
+
+ pixel_stride = (has_alpha)?4:3;
+
+ s_y1 = 0;
+ s_yfrac = -dest_height/2;
+ while (s_y1 < source_height) {
+ s_y2 = s_y1 + dy;
+ s_yfrac += dy_frac;
+ if (s_yfrac > 0) {
+ s_y2++;
+ s_yfrac -= dest_height;
+ }
+
+ s_x1 = 0;
+ s_xfrac = -dest_width/2;
+ while (s_x1 < source_width) {
+ s_x2 = s_x1 + dx;
+ s_xfrac += dx_frac;
+ if (s_xfrac > 0) {
+ s_x2++;
+ s_xfrac -= dest_width;
+ }
+
+ /* Average block of [x1,x2[ x [y1,y2[ and store in dest */
+ r = g = b = a = 0;
+ n_pixels = 0;
+
+ src = src_pixels + s_y1 * source_rowstride + s_x1 * pixel_stride;
+ for (y = s_y1; y < s_y2; y++) {
+ xsrc = src;
+ if (has_alpha) {
+ for (x = 0; x < s_x2-s_x1; x++) {
+ n_pixels++;
+
+ r += xsrc[3] * xsrc[0];
+ g += xsrc[3] * xsrc[1];
+ b += xsrc[3] * xsrc[2];
+ a += xsrc[3];
+ xsrc += 4;
+ }
+ } else {
+ for (x = 0; x < s_x2-s_x1; x++) {
+ n_pixels++;
+ r += *xsrc++;
+ g += *xsrc++;
+ b += *xsrc++;
+ }
+ }
+ src += source_rowstride;
+ }
+
+ if (has_alpha) {
+ if (a != 0) {
+ *dest++ = r / a;
+ *dest++ = g / a;
+ *dest++ = b / a;
+ *dest++ = a / n_pixels;
+ } else {
+ *dest++ = 0;
+ *dest++ = 0;
+ *dest++ = 0;
+ *dest++ = 0;
+ }
+ } else {
+ *dest++ = r / n_pixels;
+ *dest++ = g / n_pixels;
+ *dest++ = b / n_pixels;
+ }
+
+ s_x1 = s_x2;
+ }
+ s_y1 = s_y2;
+ dest += dest_rowstride - dest_width * pixel_stride;
+ }
+
+ return dest_pixbuf;
+}
+
+static char *
+get_target_uri (GFile *file)
+{
+ GFileInfo *info;
+ char *target;
+
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+ if (info == NULL)
+ return NULL;
+ target = g_strdup (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI));
+ g_object_unref (info);
+
+ return target;
+}
+
+static char *
+get_target_path (GFile *input)
+{
+ if (g_file_has_uri_scheme (input, "trash") != FALSE ||
+ g_file_has_uri_scheme (input, "recent") != FALSE) {
+ GFile *file;
+ char *input_uri;
+ char *input_path;
+
+ input_uri = get_target_uri (input);
+ file = g_file_new_for_uri (input_uri);
+ g_free (input_uri);
+ input_path = g_file_get_path (file);
+ g_object_unref (file);
+ return input_path;
+ }
+ return g_file_get_path (input);
+}
+
+static const GOptionEntry entries[] = {
+ { "size", 's', 0, G_OPTION_ARG_INT, &output_size, "Size of the thumbnail in pixels", NULL },
+ {"g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL},
+ { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, "[INPUT FILE] [OUTPUT FILE]" },
+ { NULL }
+};
+
+int main (int argc, char **argv)
+{
+ char *input_filename;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+ GOptionContext *context;
+ GFile *input;
+ const char *output;
+
+#ifdef THUMBNAILER_RETURNS_PIXBUF
+ int width, height;
+#elif THUMBNAILER_RETURNS_DATA
+ char *data = NULL;
+ gsize length;
+#endif
+
+ g_type_init ();
+
+ /* Options parsing */
+ context = g_option_context_new (THUMBNAILER_USAGE);
+ g_option_context_add_main_entries (context, entries, NULL);
+
+ if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) {
+ g_warning ("Couldn't parse command-line options: %s", error->message);
+ g_error_free (error);
+ return 1;
+ }
+
+ /* Set fatal warnings if required */
+ if (g_fatal_warnings) {
+ GLogLevelFlags fatal_mask;
+
+ fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
+ fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL;
+ g_log_set_always_fatal (fatal_mask);
+ }
+
+ if (filenames == NULL || g_strv_length (filenames) != 2) {
+ g_print ("Expects an input and an output file\n");
+ return 1;
+ }
+
+ input = g_file_new_for_commandline_arg (filenames[0]);
+ input_filename = get_target_path (input);
+ g_object_unref (input);
+
+ if (input_filename == NULL) {
+ g_warning ("Could not get file path for %s", filenames[0]);
+ return 1;
+ }
+
+ output = filenames[1];
+
+#ifdef THUMBNAILER_RETURNS_PIXBUF
+ pixbuf = file_to_pixbuf (input_filename, output_size, &error);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ /* Handle naive thumbnailers that don't resize */
+ if (output_size != 0 &&
+ (height > output_size || width > output_size)) {
+ GdkPixbuf *scaled;
+ double scale;
+
+ scale = (double)output_size / MAX (width, height);
+
+ scaled = gnome_desktop_thumbnail_scale_down_pixbuf (pixbuf,
+ floor (width * scale + 0.5),
+ floor (height * scale + 0.5));
+ gdk_pixbuf_copy_options (pixbuf, scaled);
+ g_object_unref (pixbuf);
+ pixbuf = scaled;
+ }
+#elif THUMBNAILER_RETURNS_DATA
+ data = file_to_data (input_filename, &length, &error);
+ if (data) {
+ GInputStream *mem_stream;
+
+ mem_stream = g_memory_input_stream_new_from_data (data, length, g_free);
+ pixbuf = gdk_pixbuf_new_from_stream_at_scale (mem_stream, output_size, -1, TRUE, NULL, &error);
+ g_object_unref (mem_stream);
+ } else {
+ pixbuf = NULL;
+ }
+#else
+#error "One of THUMBNAILER_RETURNS_PIXBUF or THUMBNAILER_RETURNS_DATA must be set"
+#endif
+ g_free (input_filename);
+
+ if (!pixbuf) {
+ g_warning ("Could not thumbnail '%s': %s", filenames[0], error->message);
+ g_error_free (error);
+ g_strfreev (filenames);
+ return 1;
+ }
+
+ if (gdk_pixbuf_save (pixbuf, output, "png", &error, NULL) == FALSE) {
+ g_warning ("Couldn't save the thumbnail '%s' for file '%s': %s", output, filenames[0], error->message);
+ g_error_free (error);
+ return 1;
+ }
+
+ g_object_unref (pixbuf);
+
+ return 0;
+}
diff --git a/thumbnailer/gnome-thumbnailer-skeleton.h b/thumbnailer/gnome-thumbnailer-skeleton.h
new file mode 100644
index 000000000..b38964545
--- /dev/null
+++ b/thumbnailer/gnome-thumbnailer-skeleton.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 Bastien Nocera <hadess@hadess.net>
+ *
+ * Authors: Bastien Nocera <hadess@hadess.net>
+ *
+ * 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 <gdk-pixbuf/gdk-pixbuf.h>
+
+#ifdef THUMBNAILER_RETURNS_PIXBUF
+#ifdef THUMBNAILER_RETURNS_DATA
+#error "Only one of THUMBNAILER_RETURNS_PIXBUF or THUMBNAILER_RETURNS_DATA must be set"
+#else
+GdkPixbuf * file_to_pixbuf (const char *path,
+ guint destination_size,
+ GError **error);
+#endif
+#elif THUMBNAILER_RETURNS_DATA
+char * file_to_data (const char *path,
+ gsize *ret_length,
+ GError **error);
+#else
+#error "One of THUMBNAILER_RETURNS_PIXBUF or THUMBNAILER_RETURNS_DATA must be set"
+#endif