summaryrefslogtreecommitdiff
path: root/thumbnailer/gnome-thumbnailer-skeleton.c
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/gnome-thumbnailer-skeleton.c
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/gnome-thumbnailer-skeleton.c')
-rw-r--r--thumbnailer/gnome-thumbnailer-skeleton.c324
1 files changed, 324 insertions, 0 deletions
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;
+}