doxygen: prevent GPL license block from being parsed as doxygen comment.
[blender.git] / source / blender / imbuf / intern / thumbs.c
1 /*
2  * $Id$ 
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version. 
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * The Original Code is Copyright (C) 2007 Blender Foundation
21  * All rights reserved.
22  *
23  * The Original Code is: all of this file.
24  *
25  * Contributor(s): Andrea Weikert.
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30 #include <stdio.h>
31
32 #include "BKE_utildefines.h"
33 #include "BLI_blenlib.h"
34 #include "MEM_guardedalloc.h"
35
36 #include "IMB_imbuf_types.h"
37 #include "IMB_imbuf.h"
38 #include "IMB_thumbs.h"
39 #include "IMB_metadata.h"
40
41 #include "md5.h"
42
43 #include <ctype.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <time.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <stdio.h>
50
51 #ifdef WIN32
52 #include <windows.h> /* need to include windows.h so _WIN32_IE is defined  */
53 #ifndef _WIN32_IE
54 #define _WIN32_IE 0x0400 /* minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already */
55 #endif
56 #include <shlobj.h> /* for SHGetSpecialFolderPath, has to be done before BLI_winstuff because 'near' is disabled through BLI_windstuff */
57 #include <process.h> /* getpid */
58 #include <direct.h> /* chdir */
59 #include "BLI_winstuff.h"
60 #else
61 #include <unistd.h>
62 #endif
63
64 #define URI_MAX FILE_MAX*3 + 8
65
66 static int get_thumb_dir( char* dir , ThumbSize size)
67 {
68 #ifdef WIN32
69         /* yes, applications shouldn't store data there, but so does GIMP :)*/
70         SHGetSpecialFolderPath(0, dir, CSIDL_PROFILE, 0);
71 #else
72         char* home = getenv("HOME");
73         if (!home) return 0;
74         BLI_strncpy(dir, home, FILE_MAX);
75 #endif
76         switch(size) {
77                 case THB_NORMAL:
78                         strcat(dir, "/.thumbnails/normal/");
79                         break;
80                 case THB_LARGE:
81                         strcat(dir, "/.thumbnails/large/");
82                         break;
83                 case THB_FAIL:
84                         strcat(dir, "/.thumbnails/fail/blender/");
85                         break;
86                 default:
87                         return 0; /* unknown size */
88         }
89         return 1;
90 }
91
92 /** ----- begin of adapted code from glib ---
93  * The following code is adapted from function g_escape_uri_string from the gnome glib
94  * Source: http://svn.gnome.org/viewcvs/glib/trunk/glib/gconvert.c?view=markup
95  * released under the Gnu General Public License.
96  */
97 typedef enum {
98   UNSAFE_ALL        = 0x1,  /* Escape all unsafe characters   */
99   UNSAFE_ALLOW_PLUS = 0x2,  /* Allows '+'  */
100   UNSAFE_PATH       = 0x8,  /* Allows '/', '&', '=', ':', '@', '+', '$' and ',' */
101   UNSAFE_HOST       = 0x10, /* Allows '/' and ':' and '@' */
102   UNSAFE_SLASHES    = 0x20  /* Allows all characters except for '/' and '%' */
103 } UnsafeCharacterSet;
104
105 static const unsigned char acceptable[96] = {
106   /* A table of the ASCII chars from space (32) to DEL (127) */
107   /*      !    "    #    $    %    &    '    (    )    *    +    ,    -    .    / */ 
108   0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C,
109   /* 0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ? */
110   0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20,
111   /* @    A    B    C    D    E    F    G    H    I    J    K    L    M    N    O */
112   0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
113   /* P    Q    R    S    T    U    V    W    X    Y    Z    [    \    ]    ^    _ */
114   0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F,
115   /* `    a    b    c    d    e    f    g    h    i    j    k    l    m    n    o */
116   0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
117   /* p    q    r    s    t    u    v    w    x    y    z    {    |    }    ~  DEL */
118   0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20
119 };
120
121 static const char hex[17] = "0123456789abcdef";
122
123 /* Note: This escape function works on file: URIs, but if you want to
124  * escape something else, please read RFC-2396 */
125 static void escape_uri_string (const char *string, char* escaped_string, int len,UnsafeCharacterSet mask)
126 {
127 #define ACCEPTABLE(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask))
128
129         const char *p;
130         char *q;
131         int c;
132         UnsafeCharacterSet use_mask;
133         use_mask = mask;
134
135         for (q = escaped_string, p = string; (*p != '\0') && len; p++) {
136                 c = (unsigned char) *p;
137                 len--;
138
139                 if (!ACCEPTABLE (c)) {
140                         *q++ = '%'; /* means hex coming */
141                         *q++ = hex[c >> 4];
142                         *q++ = hex[c & 15];
143                 } else {
144                         *q++ = *p;
145                 }
146         }
147   
148         *q = '\0';
149 }
150
151 static void to_hex_char(char* hexbytes, const unsigned char* bytes, int len)
152 {
153         const unsigned char *p;
154         char *q;
155
156         for (q = hexbytes, p = bytes; len; p++) {
157                 const unsigned char c = (unsigned char) *p;
158                 len--;
159                 *q++ = hex[c >> 4];
160                 *q++ = hex[c & 15];
161         }
162 }
163
164 /** ----- end of adapted code from glib --- */
165
166 static int uri_from_filename( const char *path, char *uri )
167 {
168         char orig_uri[URI_MAX]; 
169         const char* dirstart = path;
170         
171 #ifdef WIN32
172         {
173                 char vol[3];
174
175                 BLI_strncpy(orig_uri, "file:///", FILE_MAX);
176                 if (strlen(path) < 2 && path[1] != ':') {
177                         /* not a correct absolute path */
178                         return 0;
179                 }
180                 /* on windows, using always uppercase drive/volume letter in uri */
181                 vol[0] = (unsigned char)toupper(path[0]);
182                 vol[1] = ':';
183                 vol[2] = '\0';
184                 strcat(orig_uri, vol);
185                 dirstart += 2;
186         }
187 #else
188         BLI_strncpy(orig_uri, "file://", FILE_MAX);
189 #endif
190         strcat(orig_uri, dirstart);
191         BLI_char_switch(orig_uri, '\\', '/');
192         
193 #ifdef WITH_ICONV
194         {
195                 char uri_utf8[FILE_MAX*3+8];
196                 escape_uri_string(orig_uri, uri_utf8, FILE_MAX*3+8, UNSAFE_PATH);
197                 BLI_string_to_utf8(uri_utf8, uri, NULL);
198         }
199 #else 
200         escape_uri_string(orig_uri, uri, FILE_MAX*3+8, UNSAFE_PATH);
201 #endif
202         return 1;
203 }
204
205 static void thumbname_from_uri(const char* uri, char* thumb, const int thumb_len)
206 {
207         char hexdigest[33];
208         unsigned char digest[16];
209
210         md5_buffer( uri, strlen(uri), digest);
211         hexdigest[0] = '\0';
212         to_hex_char(hexdigest, digest, 16);
213         hexdigest[32] = '\0';
214         BLI_snprintf(thumb, thumb_len, "%s.png", hexdigest);
215 }
216
217 static int thumbpath_from_uri(const char* uri, char* path, const int path_len, ThumbSize size)
218 {
219         char tmppath[FILE_MAX];
220         int rv = 0;
221
222         if (get_thumb_dir(tmppath, size)) {
223                 char thumb[40];
224                 thumbname_from_uri(uri, thumb, sizeof(thumb));
225                 BLI_snprintf(path, path_len, "%s%s", tmppath, thumb);
226                 rv = 1;
227         }
228         return rv;
229 }
230
231 void IMB_thumb_makedirs(void)
232 {
233         char tpath[FILE_MAX];
234         if (get_thumb_dir(tpath, THB_NORMAL)) {
235                 BLI_recurdir_fileops(tpath);
236         }
237         if (get_thumb_dir(tpath, THB_FAIL)) {
238                 BLI_recurdir_fileops(tpath);
239         }
240 }
241
242 /* create thumbnail for file and returns new imbuf for thumbnail */
243 ImBuf* IMB_thumb_create(const char* path, ThumbSize size, ThumbSource source, ImBuf *img)
244 {
245         char uri[URI_MAX]= "";
246         char desc[URI_MAX+22];
247         char tpath[FILE_MAX];
248         char tdir[FILE_MAX];
249         char temp[FILE_MAX];
250         char mtime[40]= "0"; /* incase we can't stat the file */
251         char cwidth[40]= "0"; /* incase images have no data */
252         char cheight[40]= "0";
253         char thumb[40];
254         short tsize = 128;
255         short ex, ey;
256         float scaledx, scaledy; 
257         struct stat info;
258
259         switch(size) {
260                 case THB_NORMAL:
261                         tsize = 128;
262                         break;
263                 case THB_LARGE:
264                         tsize = 256;
265                         break;
266                 case THB_FAIL:
267                         tsize = 1;
268                         break;
269                 default:
270                         return 0; /* unknown size */
271         }
272
273         uri_from_filename(path, uri);
274         thumbname_from_uri(uri, thumb, sizeof(thumb));
275         if (get_thumb_dir(tdir, size)) {
276                 BLI_snprintf(tpath, FILE_MAX, "%s%s", tdir, thumb);
277                 thumb[8] = '\0'; /* shorten for tempname, not needed anymore */
278                 BLI_snprintf(temp, FILE_MAX, "%sblender_%d_%s.png", tdir, abs(getpid()), thumb);
279                 if (strncmp(path, tdir, strlen(tdir)) == 0) {
280                         return NULL;
281                 }
282                 if (size == THB_FAIL) {
283                         img = IMB_allocImBuf(1,1,32, IB_rect | IB_metadata);
284                         if (!img) return 0;
285                 } else {
286                         if (THB_SOURCE_IMAGE == source || THB_SOURCE_BLEND == source) {
287                                 
288                                 /* only load if we didnt give an image */
289                                 if(img==NULL) {
290                                         if(THB_SOURCE_BLEND == source) {
291                                                 img = IMB_loadblend_thumb(path);
292                                         }
293                                         else {
294                                                 img = IMB_loadiffname(path, IB_rect | IB_metadata);
295                                         }
296                                 }
297
298                                 if (img != NULL) {
299                                         stat(path, &info);
300                                         BLI_snprintf(mtime, sizeof(mtime), "%ld", info.st_mtime);
301                                         BLI_snprintf(cwidth, sizeof(cwidth), "%d", img->x);
302                                         BLI_snprintf(cheight, sizeof(cheight), "%d", img->y);
303                                 }
304                         } else if (THB_SOURCE_MOVIE == source) {
305                                 struct anim * anim = NULL;
306                                 anim = IMB_open_anim(path, IB_rect | IB_metadata);
307                                 if (anim != NULL) {
308                                         img = IMB_anim_absolute(anim, 0);
309                                         if (img == NULL) {
310                                                 printf("not an anim; %s\n", path);
311                                         } else {
312                                                 IMB_freeImBuf(img);
313                                                 img = IMB_anim_previewframe(anim);                                              
314                                         }
315                                         IMB_free_anim(anim);
316                                 }
317                                 stat(path, &info);
318                                 BLI_snprintf(mtime, sizeof(mtime), "%ld", info.st_mtime);
319                         }
320                         if (!img) return 0;             
321
322                         if (img->x > img->y) {
323                                 scaledx = (float)tsize;
324                                 scaledy =  ( (float)img->y/(float)img->x )*tsize;
325                         }
326                         else {
327                                 scaledy = (float)tsize;
328                                 scaledx =  ( (float)img->x/(float)img->y )*tsize;
329                         }
330                         ex = (short)scaledx;
331                         ey = (short)scaledy;
332                         
333                         IMB_scaleImBuf(img, ex, ey);
334                 }
335                 BLI_snprintf(desc, sizeof(desc), "Thumbnail for %s", uri);
336                 IMB_metadata_change_field(img, "Description", desc);
337                 IMB_metadata_change_field(img, "Software", "Blender");
338                 IMB_metadata_change_field(img, "Thumb::URI", uri);
339                 IMB_metadata_change_field(img, "Thumb::MTime", mtime);
340                 if (THB_SOURCE_IMAGE == source) {
341                         IMB_metadata_change_field(img, "Thumb::Image::Width", cwidth);
342                         IMB_metadata_change_field(img, "Thumb::Image::Height", cheight);
343                 }
344                 img->ftype = PNG;
345                 img->depth = 32;                
346                 if (IMB_saveiff(img, temp, IB_rect | IB_metadata)) {
347 #ifndef WIN32
348                         chmod(temp, S_IRUSR | S_IWUSR);
349 #endif  
350                         BLI_rename(temp, tpath);
351                 }
352
353                 return img;
354         }
355         return img;
356 }
357
358 /* read thumbnail for file and returns new imbuf for thumbnail */
359 ImBuf* IMB_thumb_read(const char* path, ThumbSize size)
360 {
361         char thumb[FILE_MAX];
362         char uri[FILE_MAX*3+8];
363         ImBuf *img = 0;
364
365         if (!uri_from_filename(path,uri)) {
366                 return NULL;
367         }
368         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {              
369                 img = IMB_loadiffname(thumb, IB_rect | IB_metadata);
370         }
371
372         return img;
373 }
374
375 /* delete all thumbs for the file */
376 void IMB_thumb_delete(const char* path, ThumbSize size)
377 {
378         char thumb[FILE_MAX];
379         char uri[FILE_MAX*3+8];
380
381         if (!uri_from_filename(path ,uri)) {
382                 return;
383         }
384         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
385                 if (strncmp(path, thumb, strlen(thumb)) == 0) {
386                         return;
387                 }
388                 if (BLI_exists(thumb)) {
389                         BLI_delete(thumb, 0, 0);
390                 }
391         }
392 }
393
394
395 /* create the thumb if necessary and manage failed and old thumbs */
396 ImBuf* IMB_thumb_manage(const char* path, ThumbSize size, ThumbSource source)
397 {
398         char thumb[FILE_MAX];
399         char uri[FILE_MAX*3+8];
400         struct stat st;
401         ImBuf* img = NULL;
402         
403         if (stat(path, &st)) {
404                 return NULL;
405         }       
406         if (!uri_from_filename(path,uri)) {
407                 return NULL;
408         }
409         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), THB_FAIL)) {
410                 /* failure thumb exists, don't try recreating */
411                 if (BLI_exists(thumb)) {
412                         return NULL;
413                 }
414         }
415
416         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
417                 if (strncmp(path, thumb, strlen(thumb)) == 0) {
418                         img = IMB_loadiffname(path, IB_rect);
419                 } else {
420                         img = IMB_loadiffname(thumb, IB_rect | IB_metadata);
421                         if (img) {
422                                 char mtime[40];
423                                 if (!IMB_metadata_get_field(img, "Thumb::MTime", mtime, 40)) {
424                                         /* illegal thumb, forget it! */
425                                         IMB_freeImBuf(img);
426                                         img = 0;
427                                 } else {
428                                         time_t t = atol(mtime);
429                                         if (st.st_mtime != t) {
430                                                 /* recreate all thumbs */
431                                                 IMB_freeImBuf(img);
432                                                 img = 0;
433                                                 IMB_thumb_delete(path, THB_NORMAL);
434                                                 IMB_thumb_delete(path, THB_LARGE);
435                                                 IMB_thumb_delete(path, THB_FAIL);
436                                                 img = IMB_thumb_create(path, size, source, NULL);
437                                                 if(!img){
438                                                         /* thumb creation failed, write fail thumb */
439                                                         img = IMB_thumb_create(path, THB_FAIL, source, NULL);
440                                                         if (img) {
441                                                                 /* we don't need failed thumb anymore */
442                                                                 IMB_freeImBuf(img);
443                                                                 img = 0;
444                                                         }
445                                                 }
446                                         }
447                                 }
448                         } else {
449                                 img = IMB_thumb_create(path, size, source, NULL);
450                                 if(!img){
451                                         /* thumb creation failed, write fail thumb */
452                                         img = IMB_thumb_create(path, THB_FAIL, source, NULL);
453                                         if (img) {
454                                                 /* we don't need failed thumb anymore */
455                                                 IMB_freeImBuf(img);
456                                                 img = 0;
457                                         }
458                                 }
459                         }
460                 }
461         }
462
463         return img;
464 }
465
466