[gdk-pixbuf] thumbnailer: Add an external thumbnailer for images



commit 06cf4c78067203b78acbfb29862350cdb8200b73
Author: Bastien Nocera <hadess hadess net>
Date:   Sat Jun 25 17:39:48 2016 +0200

    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

 Makefile.am                                       |    2 +-
 configure.ac                                      |    1 +
 thumbnailer/Makefile.am                           |   36 +++
 thumbnailer/gdk-pixbuf-print-mime-types.c         |   29 ++
 thumbnailer/gdk-pixbuf-thumbnailer.c              |  303 +++++++++++++++++++
 thumbnailer/gdk-pixbuf-thumbnailer.thumbnailer.in |    4 +
 thumbnailer/gnome-thumbnailer-skeleton.c          |  324 +++++++++++++++++++++
 thumbnailer/gnome-thumbnailer-skeleton.h          |   38 +++
 8 files changed, 736 insertions(+), 1 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index ed4a5c2..ab16470 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = gdk-pixbuf po docs tests contrib build
+SUBDIRS = gdk-pixbuf po docs thumbnailer tests contrib build
 
 EXTRA_DIST =                   \
        config.h.win32          \
diff --git a/configure.ac b/configure.ac
index d7ec2b8..1e0475e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1121,6 +1121,7 @@ docs/reference/gdk-pixbuf/Makefile
 docs/reference/gdk-pixbuf/version.xml
 po/Makefile.in
 tests/Makefile
+thumbnailer/Makefile
 contrib/Makefile
 contrib/gdk-pixbuf-xlib/Makefile
 contrib/gdk-pixbuf-xlib/gdk-pixbuf-xlib-2.0.pc
diff --git a/thumbnailer/Makefile.am b/thumbnailer/Makefile.am
new file mode 100644
index 0000000..d85af21
--- /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 0000000..0cf8393
--- /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 0000000..2c72072
--- /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 0000000..44f9726
--- /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 0000000..6224bec
--- /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 0000000..b389645
--- /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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]