/*
* 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 size;
gint input_width;
gint input_height;
} SizePrepareContext;
#define LOAD_BUFFER_SIZE 65536
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->size && height < info->size) return;
if (info->size <= 0) return;
if (height > width) {
width = 0.5 + (double)width * (double)info->size / (double)height;
height = info->size;
} else {
height = 0.5 + (double)height * (double)info->size / (double)width;
width = info->size;
}
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 != NULL) ? error->message : "(null)");
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 size,
GError **error)
{
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;
GInputStream *input_stream;
g_return_val_if_fail (uri != NULL, NULL);
file = g_file_new_for_uri (uri);
input_stream = G_INPUT_STREAM (g_file_read (file, NULL, error));
if (input_stream == NULL) {
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) {
break;
}
result = TRUE;
if (bytes_read == 0) {
break;
}
if (loader == NULL) {
loader = create_loader (file, buffer, bytes_read);
if (1 <= size) {
info.size = size;
info.input_width = info.input_height = 0;
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)) {
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 (*error != NULL) {
gdk_pixbuf_loader_close (loader, NULL);
result = FALSE;
} else if (gdk_pixbuf_loader_close (loader, error) == FALSE) {
if (!g_error_matches (*error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INCOMPLETE_ANIMATION))
result = FALSE;
else
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, error);
if (pixbuf == NULL)
return NULL;
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;
}