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