/* Eye Of Mate - Thumbnailing functions * * Copyright (C) 2000-2008 The Free Software Foundation * * Author: Lucas Rocha * * Based on eel code (eel/eel-graphic-effects.c) by: * - Andy Hertzfeld * * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif /* We must define MATE_DESKTOP_USE_UNSTABLE_API to be able to use MateDesktopThumbnail */ #ifndef MATE_DESKTOP_USE_UNSTABLE_API #define MATE_DESKTOP_USE_UNSTABLE_API #endif #include #include "eom-thumbnail.h" #include "eom-list-store.h" #include "eom-debug.h" #define EOM_THUMB_ERROR eom_thumb_error_quark () static MateDesktopThumbnailFactory *factory = NULL; static GdkPixbuf *frame = NULL; typedef enum { EOM_THUMB_ERROR_VFS, EOM_THUMB_ERROR_GENERIC, EOM_THUMB_ERROR_UNKNOWN } EomThumbError; typedef struct { char *uri_str; char *thumb_path; time_t mtime; char *mime_type; gboolean thumb_exists; gboolean failed_thumb_exists; gboolean can_read; } EomThumbData; static GQuark eom_thumb_error_quark (void) { static GQuark q = 0; if (q == 0) q = g_quark_from_static_string ("eom-thumb-error-quark"); return q; } static void set_vfs_error (GError **error, GError *ioerror) { g_set_error (error, EOM_THUMB_ERROR, EOM_THUMB_ERROR_VFS, "%s", ioerror ? ioerror->message : "VFS error making a thumbnail"); } static void set_thumb_error (GError **error, int error_id, const char *string) { g_set_error (error, EOM_THUMB_ERROR, error_id, "%s", string); } static GdkPixbuf* get_valid_thumbnail (EomThumbData *data, GError **error) { GdkPixbuf *thumb = NULL; g_return_val_if_fail (data != NULL, NULL); /* does a thumbnail under the path exists? */ if (data->thumb_exists) { thumb = gdk_pixbuf_new_from_file (data->thumb_path, error); /* is this thumbnail file up to date? */ if (thumb != NULL && !mate_desktop_thumbnail_is_valid (thumb, data->uri_str, data->mtime)) { g_object_unref (thumb); thumb = NULL; } } return thumb; } static GdkPixbuf * create_thumbnail_from_pixbuf (EomThumbData *data, GdkPixbuf *pixbuf, GError **error) { GdkPixbuf *thumb; gint width, height; gfloat perc; g_assert (factory != NULL); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); perc = CLAMP (128.0/(MAX (width, height)), 0, 1); thumb = mate_desktop_thumbnail_scale_down_pixbuf (pixbuf, width*perc, height*perc); return thumb; } static void eom_thumb_data_free (EomThumbData *data) { if (data == NULL) return; g_free (data->thumb_path); g_free (data->mime_type); g_free (data->uri_str); g_slice_free (EomThumbData, data); } static EomThumbData* eom_thumb_data_new (GFile *file, GError **error) { EomThumbData *data; GFileInfo *file_info; GError *ioerror = NULL; g_return_val_if_fail (file != NULL, NULL); g_return_val_if_fail (error != NULL && *error == NULL, NULL); data = g_slice_new0 (EomThumbData); data->uri_str = g_file_get_uri (file); data->thumb_path = mate_desktop_thumbnail_path_for_uri (data->uri_str, MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL); file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," G_FILE_ATTRIBUTE_ACCESS_CAN_READ, 0, NULL, &ioerror); if (file_info == NULL) { set_vfs_error (error, ioerror); g_clear_error (&ioerror); } if (*error == NULL) { /* if available, copy data */ data->mtime = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); data->mime_type = g_strdup (g_file_info_get_content_type (file_info)); data->thumb_exists = (g_file_info_get_attribute_byte_string (file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH) != NULL); data->failed_thumb_exists = g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); data->can_read = TRUE; if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) { data->can_read = g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ); } } else { eom_thumb_data_free (data); data = NULL; g_clear_error (&ioerror); } g_object_unref (file_info); return data; } static void draw_frame_row (GdkPixbuf *frame_image, gint target_width, gint source_width, gint source_v_position, gint dest_v_position, GdkPixbuf *result_pixbuf, gint left_offset, gint height) { gint remaining_width, h_offset, slab_width; remaining_width = target_width; h_offset = 0; while (remaining_width > 0) { slab_width = remaining_width > source_width ? source_width : remaining_width; gdk_pixbuf_copy_area (frame_image, left_offset, source_v_position, slab_width, height, result_pixbuf, left_offset + h_offset, dest_v_position); remaining_width -= slab_width; h_offset += slab_width; } } static void draw_frame_column (GdkPixbuf *frame_image, gint target_height, gint source_height, gint source_h_position, gint dest_h_position, GdkPixbuf *result_pixbuf, gint top_offset, gint width) { gint remaining_height, v_offset, slab_height; remaining_height = target_height; v_offset = 0; while (remaining_height > 0) { slab_height = remaining_height > source_height ? source_height : remaining_height; gdk_pixbuf_copy_area (frame_image, source_h_position, top_offset, width, slab_height, result_pixbuf, dest_h_position, top_offset + v_offset); remaining_height -= slab_height; v_offset += slab_height; } } static GdkPixbuf * eom_thumbnail_stretch_frame_image (GdkPixbuf *frame_image, gint left_offset, gint top_offset, gint right_offset, gint bottom_offset, gint dest_width, gint dest_height, gboolean fill_flag) { GdkPixbuf *result_pixbuf; gint frame_width, frame_height; gint target_width, target_frame_width; gint target_height, target_frame_height; frame_width = gdk_pixbuf_get_width (frame_image); frame_height = gdk_pixbuf_get_height (frame_image); if (fill_flag) { result_pixbuf = gdk_pixbuf_scale_simple (frame_image, dest_width, dest_height, GDK_INTERP_NEAREST); } else { result_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, dest_width, dest_height); /* Clear pixbuf to fully opaque white */ gdk_pixbuf_fill (result_pixbuf, 0xffffffff); } target_width = dest_width - left_offset - right_offset; target_frame_width = frame_width - left_offset - right_offset; target_height = dest_height - top_offset - bottom_offset; target_frame_height = frame_height - top_offset - bottom_offset; /* Draw the left top corner and top row */ gdk_pixbuf_copy_area (frame_image, 0, 0, left_offset, top_offset, result_pixbuf, 0, 0); draw_frame_row (frame_image, target_width, target_frame_width, 0, 0, result_pixbuf, left_offset, top_offset); /* Draw the right top corner and left column */ gdk_pixbuf_copy_area (frame_image, frame_width - right_offset, 0, right_offset, top_offset, result_pixbuf, dest_width - right_offset, 0); draw_frame_column (frame_image, target_height, target_frame_height, 0, 0, result_pixbuf, top_offset, left_offset); /* Draw the bottom right corner and bottom row */ gdk_pixbuf_copy_area (frame_image, frame_width - right_offset, frame_height - bottom_offset, right_offset, bottom_offset, result_pixbuf, dest_width - right_offset, dest_height - bottom_offset); draw_frame_row (frame_image, target_width, target_frame_width, frame_height - bottom_offset, dest_height - bottom_offset, result_pixbuf, left_offset, bottom_offset); /* Draw the bottom left corner and the right column */ gdk_pixbuf_copy_area (frame_image, 0, frame_height - bottom_offset, left_offset, bottom_offset, result_pixbuf, 0, dest_height - bottom_offset); draw_frame_column (frame_image, target_height, target_frame_height, frame_width - right_offset, dest_width - right_offset, result_pixbuf, top_offset, right_offset); return result_pixbuf; } GdkPixbuf * eom_thumbnail_add_frame (GdkPixbuf *thumbnail) { GdkPixbuf *result_pixbuf; gint source_width, source_height; gint dest_width, dest_height; source_width = gdk_pixbuf_get_width (thumbnail); source_height = gdk_pixbuf_get_height (thumbnail); dest_width = source_width + 9; dest_height = source_height + 9; result_pixbuf = eom_thumbnail_stretch_frame_image (frame, 3, 3, 6, 6, dest_width, dest_height, FALSE); gdk_pixbuf_copy_area (thumbnail, 0, 0, source_width, source_height, result_pixbuf, 3, 3); return result_pixbuf; } GdkPixbuf * eom_thumbnail_fit_to_size (GdkPixbuf *thumbnail, gint dimension) { gint width, height; width = gdk_pixbuf_get_width (thumbnail); height = gdk_pixbuf_get_height (thumbnail); if (width > dimension || height > dimension) { GdkPixbuf *result_pixbuf; gfloat factor; if (width > height) { factor = (gfloat) dimension / (gfloat) width; } else { factor = (gfloat) dimension / (gfloat) height; } width = MAX (width * factor, 1); height = MAX (height * factor, 1); result_pixbuf = mate_desktop_thumbnail_scale_down_pixbuf (thumbnail, width, height); return result_pixbuf; } return gdk_pixbuf_copy (thumbnail); } GdkPixbuf* eom_thumbnail_load (EomImage *image, GError **error) { GdkPixbuf *thumb = NULL; GFile *file; EomThumbData *data; GdkPixbuf *pixbuf = NULL; g_return_val_if_fail (image != NULL, NULL); g_return_val_if_fail (error != NULL && *error == NULL, NULL); file = eom_image_get_file (image); data = eom_thumb_data_new (file, error); g_object_unref (file); if (data == NULL) return NULL; if (!data->can_read || (data->failed_thumb_exists && mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory, data->uri_str, data->mtime))) { eom_debug_message (DEBUG_THUMBNAIL, "%s: bad permissions or valid failed thumbnail present",data->uri_str); set_thumb_error (error, EOM_THUMB_ERROR_GENERIC, "Thumbnail creation failed"); return NULL; } /* check if there is already a valid cached thumbnail */ thumb = get_valid_thumbnail (data, error); if (thumb != NULL) { eom_debug_message (DEBUG_THUMBNAIL, "%s: loaded from cache",data->uri_str); } else if (mate_desktop_thumbnail_factory_can_thumbnail (factory, data->uri_str, data->mime_type, data->mtime)) { /* Only use the image pixbuf when it is up to date. */ if (!eom_image_is_file_changed (image)) pixbuf = eom_image_get_pixbuf (image); if (pixbuf != NULL) { /* generate a thumbnail from the in-memory image, if we have already loaded the image */ eom_debug_message (DEBUG_THUMBNAIL, "%s: creating from pixbuf",data->uri_str); thumb = create_thumbnail_from_pixbuf (data, pixbuf, error); g_object_unref (pixbuf); } else { /* generate a thumbnail from the file */ eom_debug_message (DEBUG_THUMBNAIL, "%s: creating from file",data->uri_str); thumb = mate_desktop_thumbnail_factory_generate_thumbnail (factory, data->uri_str, data->mime_type); } if (thumb != NULL) { /* Save the new thumbnail */ mate_desktop_thumbnail_factory_save_thumbnail (factory, thumb, data->uri_str, data->mtime); eom_debug_message (DEBUG_THUMBNAIL, "%s: normal thumbnail saved",data->uri_str); } else { /* Save a failed thumbnail, to stop further thumbnail attempts */ mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, data->uri_str, data->mtime); eom_debug_message (DEBUG_THUMBNAIL, "%s: failed thumbnail saved",data->uri_str); set_thumb_error (error, EOM_THUMB_ERROR_GENERIC, "Thumbnail creation failed"); } } eom_thumb_data_free (data); return thumb; } void eom_thumbnail_init (void) { if (factory == NULL) { factory = mate_desktop_thumbnail_factory_new (MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL); } if (frame == NULL) { frame = gdk_pixbuf_new_from_file (EOM_DATA_DIR "/pixmaps/thumbnail-frame.png", NULL); } }