1 /* Eye Of Mate - Thumbnailing functions
2  *
3  * Copyright (C) 2000-2008 The Free Software Foundation
4  *
5  * Author: Lucas Rocha <lucasr@gnome.org>
6  *
7  * Based on eel code (eel/eel-graphic-effects.c) by:
8  * 	- Andy Hertzfeld <andy@eazel.com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 /* We must define MATE_DESKTOP_USE_UNSTABLE_API to be able
30    to use MateDesktopThumbnail */
31 #ifndef MATE_DESKTOP_USE_UNSTABLE_API
32 #define MATE_DESKTOP_USE_UNSTABLE_API
33 #endif
34 #include <libmate-desktop/mate-desktop-thumbnail.h>
35 
36 #include "eom-thumbnail.h"
37 #include "eom-list-store.h"
38 #include "eom-debug.h"
39 
40 #define EOM_THUMB_ERROR eom_thumb_error_quark ()
41 
42 static MateDesktopThumbnailFactory *factory = NULL;
43 static GdkPixbuf *frame = NULL;
44 
45 typedef enum {
46 	EOM_THUMB_ERROR_VFS,
47 	EOM_THUMB_ERROR_GENERIC,
48 	EOM_THUMB_ERROR_UNKNOWN
49 } EomThumbError;
50 
51 typedef struct {
52 	char    *uri_str;
53 	char    *thumb_path;
54 	time_t   mtime;
55 	char    *mime_type;
56 	gboolean thumb_exists;
57 	gboolean failed_thumb_exists;
58 	gboolean can_read;
59 } EomThumbData;
60 
61 static GQuark
eom_thumb_error_quark(void)62 eom_thumb_error_quark (void)
63 {
64 	static GQuark q = 0;
65 	if (q == 0)
66 		q = g_quark_from_static_string ("eom-thumb-error-quark");
67 
68 	return q;
69 }
70 
71 static void
set_vfs_error(GError ** error,GError * ioerror)72 set_vfs_error (GError **error, GError *ioerror)
73 {
74 	g_set_error (error,
75 		     EOM_THUMB_ERROR,
76 		     EOM_THUMB_ERROR_VFS,
77 		     "%s", ioerror ? ioerror->message : "VFS error making a thumbnail");
78 }
79 
80 static void
set_thumb_error(GError ** error,int error_id,const char * string)81 set_thumb_error (GError **error, int error_id, const char *string)
82 {
83 	g_set_error (error,
84 		     EOM_THUMB_ERROR,
85 		     error_id,
86 		     "%s", string);
87 }
88 
89 static GdkPixbuf*
get_valid_thumbnail(EomThumbData * data,GError ** error)90 get_valid_thumbnail (EomThumbData *data, GError **error)
91 {
92 	GdkPixbuf *thumb = NULL;
93 
94 	g_return_val_if_fail (data != NULL, NULL);
95 
96 	/* does a thumbnail under the path exists? */
97 	if (data->thumb_exists) {
98 		thumb = gdk_pixbuf_new_from_file (data->thumb_path, error);
99 
100 		/* is this thumbnail file up to date? */
101 		if (thumb != NULL && !mate_desktop_thumbnail_is_valid (thumb, data->uri_str, data->mtime)) {
102 			g_object_unref (thumb);
103 			thumb = NULL;
104 		}
105 	}
106 
107 	return thumb;
108 }
109 
110 static GdkPixbuf *
create_thumbnail_from_pixbuf(EomThumbData * data,GdkPixbuf * pixbuf,GError ** error)111 create_thumbnail_from_pixbuf (EomThumbData *data,
112 			      GdkPixbuf *pixbuf,
113 			      GError **error)
114 {
115 	GdkPixbuf *thumb;
116 	gint width, height;
117 	gfloat perc;
118 
119 	g_assert (factory != NULL);
120 
121 	width = gdk_pixbuf_get_width (pixbuf);
122 	height = gdk_pixbuf_get_height (pixbuf);
123 
124 	perc = CLAMP (128.0/(MAX (width, height)), 0, 1);
125 
126 	thumb = gdk_pixbuf_scale_simple (pixbuf,
127 	                                 width*perc,
128 	                                 height*perc,
129 	                                 GDK_INTERP_HYPER);
130 
131 	return thumb;
132 }
133 
134 static void
eom_thumb_data_free(EomThumbData * data)135 eom_thumb_data_free (EomThumbData *data)
136 {
137 	if (data == NULL)
138 		return;
139 
140 	g_free (data->thumb_path);
141 	g_free (data->mime_type);
142 	g_free (data->uri_str);
143 
144 	g_slice_free (EomThumbData, data);
145 }
146 
147 static EomThumbData*
eom_thumb_data_new(GFile * file,GError ** error)148 eom_thumb_data_new (GFile *file, GError **error)
149 {
150 	EomThumbData *data;
151 	GFileInfo *file_info;
152 	GError *ioerror = NULL;
153 
154 	g_return_val_if_fail (file != NULL, NULL);
155 	g_return_val_if_fail (error != NULL && *error == NULL, NULL);
156 
157 	data = g_slice_new0 (EomThumbData);
158 
159 	data->uri_str    = g_file_get_uri (file);
160 	data->thumb_path = mate_desktop_thumbnail_path_for_uri (data->uri_str, MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
161 
162 	file_info = g_file_query_info (file,
163 				       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
164 				       G_FILE_ATTRIBUTE_TIME_MODIFIED ","
165 				       G_FILE_ATTRIBUTE_THUMBNAIL_PATH ","
166 				       G_FILE_ATTRIBUTE_THUMBNAILING_FAILED ","
167 				       G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
168 				       0, NULL, &ioerror);
169 	if (file_info == NULL)
170 	{
171 		set_vfs_error (error, ioerror);
172 		g_clear_error (&ioerror);
173 	}
174 
175 	if (*error == NULL) {
176 		/* if available, copy data */
177 		data->mtime = g_file_info_get_attribute_uint64 (file_info,
178 								G_FILE_ATTRIBUTE_TIME_MODIFIED);
179 		data->mime_type = g_strdup (g_file_info_get_content_type (file_info));
180 
181 		data->thumb_exists = (g_file_info_get_attribute_byte_string (file_info,
182 					                                     G_FILE_ATTRIBUTE_THUMBNAIL_PATH) != NULL);
183 		data->failed_thumb_exists = g_file_info_get_attribute_boolean (file_info,
184 									       G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
185 		data->can_read = TRUE;
186 		if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
187 			data->can_read = g_file_info_get_attribute_boolean (file_info,
188 									    G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
189 		}
190 	}
191 	else {
192 		eom_thumb_data_free (data);
193 		data = NULL;
194 		g_clear_error (&ioerror);
195 	}
196 
197 	g_object_unref (file_info);
198 
199 	return data;
200 }
201 
202 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)203 draw_frame_row (GdkPixbuf *frame_image,
204 		gint target_width,
205 		gint source_width,
206 		gint source_v_position,
207 		gint dest_v_position,
208 		GdkPixbuf *result_pixbuf,
209 		gint left_offset,
210 		gint height)
211 {
212 	gint remaining_width, h_offset, slab_width;
213 
214 	remaining_width = target_width;
215 	h_offset = 0;
216 
217 	while (remaining_width > 0) {
218 		slab_width = remaining_width > source_width ?
219 			     source_width : remaining_width;
220 
221 		gdk_pixbuf_copy_area (frame_image,
222 				      left_offset,
223 				      source_v_position,
224 				      slab_width,
225 				      height,
226 				      result_pixbuf,
227 				      left_offset + h_offset,
228 				      dest_v_position);
229 
230 		remaining_width -= slab_width;
231 		h_offset += slab_width;
232 	}
233 }
234 
235 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)236 draw_frame_column (GdkPixbuf *frame_image,
237 		   gint target_height,
238 		   gint source_height,
239 		   gint source_h_position,
240 		   gint dest_h_position,
241 		   GdkPixbuf *result_pixbuf,
242 		   gint top_offset,
243 		   gint width)
244 {
245 	gint remaining_height, v_offset, slab_height;
246 
247 	remaining_height = target_height;
248 	v_offset = 0;
249 
250 	while (remaining_height > 0) {
251 		slab_height = remaining_height > source_height ?
252 			      source_height : remaining_height;
253 
254 		gdk_pixbuf_copy_area (frame_image,
255 				      source_h_position,
256 				      top_offset,
257 				      width,
258 				      slab_height,
259 				      result_pixbuf,
260 				      dest_h_position,
261 				      top_offset + v_offset);
262 
263 		remaining_height -= slab_height;
264 		v_offset += slab_height;
265 	}
266 }
267 
268 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)269 eom_thumbnail_stretch_frame_image (GdkPixbuf *frame_image,
270 				   gint left_offset,
271 				   gint top_offset,
272 				   gint right_offset,
273 				   gint bottom_offset,
274                                    gint dest_width,
275 				   gint dest_height,
276 				   gboolean fill_flag)
277 {
278         GdkPixbuf *result_pixbuf;
279         gint frame_width, frame_height;
280         gint target_width, target_frame_width;
281         gint target_height, target_frame_height;
282 
283         frame_width  = gdk_pixbuf_get_width  (frame_image);
284         frame_height = gdk_pixbuf_get_height (frame_image);
285 
286         if (fill_flag) {
287 		result_pixbuf = gdk_pixbuf_scale_simple (frame_image,
288 							 dest_width,
289 							 dest_height,
290 							 GDK_INTERP_NEAREST);
291         } else {
292                 result_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
293 						TRUE,
294 						8,
295 						dest_width,
296 						dest_height);
297 
298 		/* Clear pixbuf to fully opaque white */
299 		gdk_pixbuf_fill (result_pixbuf, 0xffffffff);
300         }
301 
302         target_width  = dest_width - left_offset - right_offset;
303         target_frame_width = frame_width - left_offset - right_offset;
304 
305         target_height  = dest_height - top_offset - bottom_offset;
306         target_frame_height = frame_height - top_offset - bottom_offset;
307 
308         /* Draw the left top corner  and top row */
309         gdk_pixbuf_copy_area (frame_image,
310 			      0, 0,
311 			      left_offset,
312 			      top_offset,
313 			      result_pixbuf,
314 			      0, 0);
315 
316         draw_frame_row (frame_image,
317 			target_width,
318 			target_frame_width,
319 			0, 0,
320 			result_pixbuf,
321 			left_offset,
322 			top_offset);
323 
324         /* Draw the right top corner and left column */
325         gdk_pixbuf_copy_area (frame_image,
326 			      frame_width - right_offset,
327 			      0,
328 			      right_offset,
329 			      top_offset,
330 			      result_pixbuf,
331 			      dest_width - right_offset,
332 			      0);
333 
334         draw_frame_column (frame_image,
335 			   target_height,
336 			   target_frame_height,
337 			   0, 0,
338 			   result_pixbuf,
339 			   top_offset,
340 			   left_offset);
341 
342         /* Draw the bottom right corner and bottom row */
343         gdk_pixbuf_copy_area (frame_image,
344 			      frame_width - right_offset,
345 			      frame_height - bottom_offset,
346 			      right_offset,
347 			      bottom_offset,
348 			      result_pixbuf,
349 			      dest_width - right_offset,
350 			      dest_height - bottom_offset);
351 
352         draw_frame_row (frame_image,
353 			target_width,
354 			target_frame_width,
355 			frame_height - bottom_offset,
356 			dest_height - bottom_offset,
357 			result_pixbuf,
358 			left_offset, bottom_offset);
359 
360         /* Draw the bottom left corner and the right column */
361         gdk_pixbuf_copy_area (frame_image,
362 			      0,
363 			      frame_height - bottom_offset,
364 			      left_offset,
365 			      bottom_offset,
366 			      result_pixbuf,
367 			      0,
368 			      dest_height - bottom_offset);
369 
370         draw_frame_column (frame_image,
371 			   target_height,
372 			   target_frame_height,
373 			   frame_width - right_offset,
374 			   dest_width - right_offset,
375 			   result_pixbuf, top_offset,
376 			   right_offset);
377 
378         return result_pixbuf;
379 }
380 
381 /**
382  * eom_thumbnail_add_frame:
383  * @thumbnail: a #GdkPixbuf
384  *
385  * Adds a frame to @thumbnail
386  *
387  * Returns: (transfer full): a new #GdkPixbuf, storing @thumbnail nicely framed.
388  **/
389 GdkPixbuf *
eom_thumbnail_add_frame(GdkPixbuf * thumbnail)390 eom_thumbnail_add_frame (GdkPixbuf *thumbnail)
391 {
392 	GdkPixbuf *result_pixbuf;
393 	gint source_width, source_height;
394 	gint dest_width, dest_height;
395 
396 	source_width  = gdk_pixbuf_get_width  (thumbnail);
397 	source_height = gdk_pixbuf_get_height (thumbnail);
398 
399 	dest_width  = source_width  + 9;
400 	dest_height = source_height + 9;
401 
402 	result_pixbuf = eom_thumbnail_stretch_frame_image (frame,
403 							   3, 3, 6, 6,
404 	                                	           dest_width,
405 							   dest_height,
406 							   FALSE);
407 
408 	gdk_pixbuf_copy_area (thumbnail,
409 			      0, 0,
410 			      source_width,
411 			      source_height,
412 			      result_pixbuf,
413 			      3, 3);
414 
415 	return result_pixbuf;
416 }
417 
418 /**
419  * eom_thumbnail_fit_to_size:
420  * @thumbnail: a #GdkPixbuf
421  * @dimension: the maximum width or height desired
422  *
423  * Ensures a pixbuf fits a given @dimension
424  *
425  * Returns: (transfer full): a new #GdkPixbuf
426  **/
427 GdkPixbuf *
eom_thumbnail_fit_to_size(GdkPixbuf * thumbnail,gint dimension)428 eom_thumbnail_fit_to_size (GdkPixbuf *thumbnail, gint dimension)
429 {
430 	gint width, height;
431 
432 	width = gdk_pixbuf_get_width (thumbnail);
433 	height = gdk_pixbuf_get_height (thumbnail);
434 
435 	if (width > dimension || height > dimension) {
436 		GdkPixbuf *result_pixbuf;
437 		gfloat factor;
438 
439 		if (width > height) {
440 			factor = (gfloat) dimension / (gfloat) width;
441 		} else {
442 			factor = (gfloat) dimension / (gfloat) height;
443 		}
444 
445 		width  = MAX (width  * factor, 1);
446 		height = MAX (height * factor, 1);
447 
448 		result_pixbuf = gdk_pixbuf_scale_simple (thumbnail, width, height, GDK_INTERP_HYPER);
449 
450 		return result_pixbuf;
451 	}
452 	return gdk_pixbuf_copy (thumbnail);
453 }
454 
455 /**
456  * eom_thumbnail_load:
457  * @image: a #EomImage
458  * @error: location to store the error ocurring or %NULL to ignore
459  *
460  * Loads the thumbnail for @image. In case of error, %NULL is returned
461  * and @error is set.
462  *
463  * Returns: (transfer full): a new #GdkPixbuf with the thumbnail for
464  * @image or %NULL in case of error.
465  **/
466 GdkPixbuf*
eom_thumbnail_load(EomImage * image,GError ** error)467 eom_thumbnail_load (EomImage *image, GError **error)
468 {
469 	GdkPixbuf *thumb = NULL;
470 	GFile *file;
471 	EomThumbData *data;
472 	GdkPixbuf *pixbuf = NULL;
473 
474 	g_return_val_if_fail (image != NULL, NULL);
475 	g_return_val_if_fail (error != NULL && *error == NULL, NULL);
476 
477 	file = eom_image_get_file (image);
478 	data = eom_thumb_data_new (file, error);
479 	g_object_unref (file);
480 
481 	if (data == NULL)
482 		return NULL;
483 
484 	if (!data->can_read ||
485 	    (data->failed_thumb_exists && mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory, data->uri_str, data->mtime))) {
486 		eom_debug_message (DEBUG_THUMBNAIL, "%s: bad permissions or valid failed thumbnail present",data->uri_str);
487 		set_thumb_error (error, EOM_THUMB_ERROR_GENERIC, "Thumbnail creation failed");
488 		return NULL;
489 	}
490 
491 	/* check if there is already a valid cached thumbnail */
492 	thumb = get_valid_thumbnail (data, error);
493 
494 	if (thumb != NULL) {
495 		eom_debug_message (DEBUG_THUMBNAIL, "%s: loaded from cache",data->uri_str);
496 	} else if (mate_desktop_thumbnail_factory_can_thumbnail (factory, data->uri_str, data->mime_type, data->mtime)) {
497 		/* Only use the image pixbuf when it is up to date. */
498 		if (!eom_image_is_file_changed (image))
499 			pixbuf = eom_image_get_pixbuf (image);
500 
501 		if (pixbuf != NULL) {
502 			/* generate a thumbnail from the in-memory image,
503 			   if we have already loaded the image */
504 			eom_debug_message (DEBUG_THUMBNAIL, "%s: creating from pixbuf",data->uri_str);
505 			thumb = create_thumbnail_from_pixbuf (data, pixbuf, error);
506 			g_object_unref (pixbuf);
507 		} else {
508 			/* generate a thumbnail from the file */
509 			eom_debug_message (DEBUG_THUMBNAIL, "%s: creating from file",data->uri_str);
510 			thumb = mate_desktop_thumbnail_factory_generate_thumbnail (factory, data->uri_str, data->mime_type);
511 		}
512 
513 		if (thumb != NULL) {
514 			/* Save the new thumbnail */
515 			mate_desktop_thumbnail_factory_save_thumbnail (factory, thumb, data->uri_str, data->mtime);
516 			eom_debug_message (DEBUG_THUMBNAIL, "%s: normal thumbnail saved",data->uri_str);
517 		} else {
518 			/* Save a failed thumbnail, to stop further thumbnail attempts */
519 			mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, data->uri_str, data->mtime);
520 			eom_debug_message (DEBUG_THUMBNAIL, "%s: failed thumbnail saved",data->uri_str);
521 			set_thumb_error (error, EOM_THUMB_ERROR_GENERIC, "Thumbnail creation failed");
522 		}
523 	}
524 
525 	eom_thumb_data_free (data);
526 
527 	return thumb;
528 }
529 
530 void
eom_thumbnail_init(void)531 eom_thumbnail_init (void)
532 {
533 	if (factory == NULL) {
534 		factory = mate_desktop_thumbnail_factory_new (MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
535 	}
536 
537 	if (frame == NULL) {
538 		frame = gdk_pixbuf_new_from_resource (
539 	                    "/org/mate/eom/ui/pixmaps/thumbnail-frame.png",
540 	                    NULL);
541 	}
542 }
543