Krolyxon
Commit 1560d2b

Branches

Refs →

nsxivthumbs.c

507 lines · 11.3 KB · C

1/* Copyright 2011 Bert Muennich
2 *
3 * This file is part of sxiv.
4 *
5 * sxiv is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published
7 * by the Free Software Foundation; either version 2 of the License,
8 * or (at your option) any later version.
9 *
10 * sxiv is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with sxiv. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#define _POSIX_C_SOURCE 200112L
20#define _THUMBS_CONFIG
21
22#include <stdlib.h>
23#include <string.h>
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <unistd.h>
27#include <utime.h>
28
29#include "exif.h"
30#include "thumbs.h"
31#include "util.h"
32#include "config.h"
33
34static const int thumb_dim = THUMB_SIZE + 10;
35
36static char *cache_dir = NULL;
37
38bool tns_cache_enabled(void)
39{
40 struct stat stats;
41
42 return cache_dir != NULL && stat(cache_dir, &stats) == 0 &&
43 S_ISDIR(stats.st_mode) && access(cache_dir, W_OK) == 0;
44}
45
46char* tns_cache_filepath(const char *filepath)
47{
48 size_t len;
49 char *cfile = NULL;
50
51 if (cache_dir == NULL || filepath == NULL || *filepath != '/')
52 return NULL;
53
54 if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) {
55 /* don't cache images inside the cache directory! */
56 len = strlen(cache_dir) + strlen(filepath) + 6;
57 cfile = (char*) s_malloc(len);
58 snprintf(cfile, len, "%s/%s.png", cache_dir, filepath + 1);
59 }
60 return cfile;
61}
62
63Imlib_Image tns_cache_load(const char *filepath)
64{
65 char *cfile;
66 struct stat cstats, fstats;
67 Imlib_Image im = NULL;
68
69 if (filepath == NULL)
70 return NULL;
71 if (stat(filepath, &fstats) < 0)
72 return NULL;
73
74 if ((cfile = tns_cache_filepath(filepath)) != NULL) {
75 if (stat(cfile, &cstats) == 0 && cstats.st_mtime == fstats.st_mtime)
76 im = imlib_load_image(cfile);
77 free(cfile);
78 }
79 return im;
80}
81
82void tns_cache_write(thumb_t *t, bool force)
83{
84 char *cfile, *dirend;
85 struct stat cstats, fstats;
86 struct utimbuf times;
87 Imlib_Load_Error err = 0;
88
89 if (t == NULL || t->im == NULL)
90 return;
91 if (t->file == NULL || t->file->name == NULL || t->file->path == NULL)
92 return;
93 if (stat(t->file->path, &fstats) < 0)
94 return;
95
96 if ((cfile = tns_cache_filepath(t->file->path)) != NULL) {
97 if (force || stat(cfile, &cstats) < 0 ||
98 cstats.st_mtime != fstats.st_mtime)
99 {
100 if ((dirend = strrchr(cfile, '/')) != NULL) {
101 *dirend = '\0';
102 err = r_mkdir(cfile);
103 *dirend = '/';
104 }
105
106 if (err == 0) {
107 imlib_context_set_image(t->im);
108 imlib_image_set_format("png");
109 imlib_save_image_with_error_return(cfile, &err);
110 }
111
112 if (err == 0) {
113 times.actime = fstats.st_atime;
114 times.modtime = fstats.st_mtime;
115 utime(cfile, &times);
116 } else {
117 warn("could not cache thumbnail: %s", t->file->name);
118 }
119 }
120 free(cfile);
121 }
122}
123
124void tns_clean_cache(tns_t *tns)
125{
126 int dirlen;
127 bool delete;
128 char *cfile, *filename, *tpos;
129 r_dir_t dir;
130
131 if (cache_dir == NULL)
132 return;
133
134 if (r_opendir(&dir, cache_dir) < 0) {
135 warn("could not open thumbnail cache directory: %s", cache_dir);
136 return;
137 }
138
139 dirlen = strlen(cache_dir);
140
141 while ((cfile = r_readdir(&dir)) != NULL) {
142 filename = cfile + dirlen;
143 delete = false;
144
145 if ((tpos = strrchr(filename, '.')) != NULL) {
146 *tpos = '\0';
147 if (access(filename, F_OK) < 0)
148 delete = true;
149 *tpos = '.';
150 }
151 if (delete) {
152 if (unlink(cfile) < 0)
153 warn("could not delete cache file: %s", cfile);
154 }
155 free(cfile);
156 }
157 r_closedir(&dir);
158}
159
160
161void tns_init(tns_t *tns, int cnt, win_t *win)
162{
163 int len;
164 const char *homedir, *dsuffix = "";
165
166 if (tns == NULL)
167 return;
168
169 if (cnt > 0) {
170 tns->thumbs = (thumb_t*) s_malloc(cnt * sizeof(thumb_t));
171 memset(tns->thumbs, 0, cnt * sizeof(thumb_t));
172 } else {
173 tns->thumbs = NULL;
174 }
175
176 tns->cap = cnt;
177 tns->cnt = tns->first = tns->sel = 0;
178 tns->win = win;
179 tns->dirty = false;
180
181 if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') {
182 homedir = getenv("HOME");
183 dsuffix = "/.cache";
184 }
185 if (homedir != NULL) {
186 if (cache_dir != NULL)
187 free(cache_dir);
188 len = strlen(homedir) + strlen(dsuffix) + 6;
189 cache_dir = (char*) s_malloc(len);
190 snprintf(cache_dir, len, "%s%s/sxiv", homedir, dsuffix);
191 } else {
192 warn("could not locate thumbnail cache directory");
193 }
194}
195
196void tns_free(tns_t *tns)
197{
198 int i;
199
200 if (tns == NULL)
201 return;
202
203 if (tns->thumbs != NULL) {
204 for (i = 0; i < tns->cnt; i++) {
205 if (tns->thumbs[i].im != NULL) {
206 imlib_context_set_image(tns->thumbs[i].im);
207 imlib_free_image();
208 }
209 }
210 free(tns->thumbs);
211 tns->thumbs = NULL;
212 }
213
214 if (cache_dir != NULL) {
215 free(cache_dir);
216 cache_dir = NULL;
217 }
218}
219
220bool tns_load(tns_t *tns, int n, const fileinfo_t *file,
221 bool force, bool silent)
222{
223 int w, h;
224 bool use_cache, cache_hit = false;
225 float z, zw, zh;
226 thumb_t *t;
227 Imlib_Image im;
228 const char *fmt;
229
230 if (tns == NULL || tns->thumbs == NULL)
231 return false;
232 if (file == NULL || file->name == NULL || file->path == NULL)
233 return false;
234 if (n < 0 || n >= tns->cap)
235 return false;
236
237 t = &tns->thumbs[n];
238 t->file = file;
239
240 if (t->im != NULL) {
241 imlib_context_set_image(t->im);
242 imlib_free_image();
243 }
244
245 if ((use_cache = tns_cache_enabled())) {
246 if (!force && (im = tns_cache_load(file->path)) != NULL)
247 cache_hit = true;
248 }
249
250 if (!cache_hit) {
251 if (access(file->path, R_OK) < 0 ||
252 (im = imlib_load_image(file->path)) == NULL)
253 {
254 if (!silent)
255 warn("could not open image: %s", file->name);
256 return false;
257 }
258 }
259
260 imlib_context_set_image(im);
261 imlib_context_set_anti_alias(1);
262
263 if ((fmt = imlib_image_format()) == NULL) {
264 if (!silent)
265 warn("could not open image: %s", file->name);
266 imlib_free_image_and_decache();
267 return false;
268 }
269 if (STREQ(fmt, "jpeg"))
270 exif_auto_orientate(file);
271
272 w = imlib_image_get_width();
273 h = imlib_image_get_height();
274 zw = (float) THUMB_SIZE / (float) w;
275 zh = (float) THUMB_SIZE / (float) h;
276 z = MIN(zw, zh);
277 z = MIN(z, 1.0);
278 t->w = z * w;
279 t->h = z * h;
280
281 t->im = imlib_create_cropped_scaled_image(0, 0, w, h, t->w, t->h);
282 if (t->im == NULL)
283 die("could not allocate memory");
284
285 imlib_free_image_and_decache();
286
287 if (use_cache && !cache_hit)
288 tns_cache_write(t, true);
289
290 tns->dirty = true;
291 return true;
292}
293
294void tns_check_view(tns_t *tns, bool scrolled)
295{
296 int r;
297
298 if (tns == NULL)
299 return;
300
301 tns->first -= tns->first % tns->cols;
302 r = tns->sel % tns->cols;
303
304 if (scrolled) {
305 /* move selection into visible area */
306 if (tns->sel >= tns->first + tns->cols * tns->rows)
307 tns->sel = tns->first + r + tns->cols * (tns->rows - 1);
308 else if (tns->sel < tns->first)
309 tns->sel = tns->first + r;
310 } else {
311 /* scroll to selection */
312 if (tns->first + tns->cols * tns->rows <= tns->sel) {
313 tns->first = tns->sel - r - tns->cols * (tns->rows - 1);
314 tns->dirty = true;
315 } else if (tns->first > tns->sel) {
316 tns->first = tns->sel - r;
317 tns->dirty = true;
318 }
319 }
320}
321
322void tns_render(tns_t *tns)
323{
324 thumb_t *t;
325 win_t *win;
326 int i, cnt, r, x, y;
327
328 if (tns == NULL || tns->thumbs == NULL || tns->win == NULL)
329 return;
330 if (!tns->dirty)
331 return;
332
333 win = tns->win;
334 win_clear(win);
335 imlib_context_set_drawable(win->pm);
336
337 tns->cols = MAX(1, win->w / thumb_dim);
338 tns->rows = MAX(1, win->h / thumb_dim);
339
340 if (tns->cnt < tns->cols * tns->rows) {
341 tns->first = 0;
342 cnt = tns->cnt;
343 } else {
344 tns_check_view(tns, false);
345 cnt = tns->cols * tns->rows;
346 if ((r = tns->first + cnt - tns->cnt) >= tns->cols)
347 tns->first -= r - r % tns->cols;
348 if (r > 0)
349 cnt -= r % tns->cols;
350 }
351
352 r = cnt % tns->cols ? 1 : 0;
353 tns->x = x = (win->w - MIN(cnt, tns->cols) * thumb_dim) / 2 + 5;
354 tns->y = y = (win->h - (cnt / tns->cols + r) * thumb_dim) / 2 + 5;
355
356 for (i = 0; i < cnt; i++) {
357 t = &tns->thumbs[tns->first + i];
358 t->x = x + (THUMB_SIZE - t->w) / 2;
359 t->y = y + (THUMB_SIZE - t->h) / 2;
360 imlib_context_set_image(t->im);
361 imlib_render_image_part_on_drawable_at_size(0, 0, t->w, t->h,
362 t->x, t->y, t->w, t->h);
363 if (t->file->marked)
364 tns_mark(tns, tns->first + i, true);
365 if ((i + 1) % tns->cols == 0) {
366 x = tns->x;
367 y += thumb_dim;
368 } else {
369 x += thumb_dim;
370 }
371 }
372 tns->dirty = false;
373 tns_highlight(tns, tns->sel, true);
374}
375
376void tns_mark(tns_t *tns, int n, bool mark)
377{
378 if (tns == NULL || tns->thumbs == NULL || tns->win == NULL)
379 return;
380
381 if (n >= 0 && n < tns->cnt) {
382 unsigned long col;
383 thumb_t *t = &tns->thumbs[n];
384 win_t *win = tns->win;
385 int x = t->x, y = t->y, w = t->w, h = t->h;
386
387 if (mark || n == tns->sel)
388 col = win->selcol;
389 else if (win->fullscreen)
390 col = win->fscol;
391 else
392 col = win->bgcol;
393
394 win_draw_rect(win, win->pm, x - 4, y - 4, 8, 2, true, 0, col);
395 win_draw_rect(win, win->pm, x - 4, y - 4, 2, 8, true, 0, col);
396 win_draw_rect(win, win->pm, x + w - 4, y - 4, 8, 2, true, 0, col);
397 win_draw_rect(win, win->pm, x + w + 2, y - 4, 2, 8, true, 0, col);
398 win_draw_rect(win, win->pm, x - 4, y + h + 2, 8, 2, true, 0, col);
399 win_draw_rect(win, win->pm, x - 4, y + h - 4, 2, 8, true, 0, col);
400 win_draw_rect(win, win->pm, x + w - 4, y + h + 2, 8, 2, true, 0, col);
401 win_draw_rect(win, win->pm, x + w + 2, y + h - 4, 2, 8, true, 0, col);
402 }
403}
404
405void tns_highlight(tns_t *tns, int n, bool hl)
406{
407 if (tns == NULL || tns->thumbs == NULL || tns->win == NULL)
408 return;
409
410 if (n >= 0 && n < tns->cnt) {
411 unsigned long col;
412 thumb_t *t = &tns->thumbs[n];
413 win_t *win = tns->win;
414
415 if (hl)
416 col = win->selcol;
417 else if (win->fullscreen)
418 col = win->fscol;
419 else
420 col = win->bgcol;
421
422 win_draw_rect(win, win->pm, t->x - 3, t->y - 3, t->w + 6, t->h + 6,
423 false, 2, col);
424
425 if (!hl && t->file->marked)
426 tns_mark(tns, n, true);
427 }
428}
429
430bool tns_move_selection(tns_t *tns, direction_t dir, int cnt)
431{
432 int old, max;
433
434 if (tns == NULL || tns->thumbs == NULL)
435 return false;
436
437 old = tns->sel;
438 cnt = cnt > 1 ? cnt : 1;
439
440 switch (dir) {
441 case DIR_UP:
442 tns->sel = MAX(tns->sel - cnt * tns->cols, tns->sel % tns->cols);
443 break;
444 case DIR_DOWN:
445 max = tns->cols * ((tns->cnt - 1) / tns->cols) +
446 MIN((tns->cnt - 1) % tns->cols, tns->sel % tns->cols);
447 tns->sel = MIN(tns->sel + cnt * tns->cols, max);
448 break;
449 case DIR_LEFT:
450 tns->sel = MAX(tns->sel - cnt, 0);
451 break;
452 case DIR_RIGHT:
453 tns->sel = MIN(tns->sel + cnt, tns->cnt - 1);
454 break;
455 }
456
457 if (tns->sel != old) {
458 tns_highlight(tns, old, false);
459 tns_check_view(tns, false);
460 if (!tns->dirty)
461 tns_highlight(tns, tns->sel, true);
462 }
463 return tns->sel != old;
464}
465
466bool tns_scroll(tns_t *tns, direction_t dir, bool screen)
467{
468 int d, max, old;
469
470 if (tns == NULL)
471 return false;
472
473 old = tns->first;
474 d = tns->cols * (screen ? tns->rows : 1);
475
476 if (dir == DIR_DOWN) {
477 max = tns->cnt - tns->cols * tns->rows;
478 if (tns->cnt % tns->cols != 0)
479 max += tns->cols - tns->cnt % tns->cols;
480 tns->first = MIN(tns->first + d, max);
481 } else if (dir == DIR_UP) {
482 tns->first = MAX(tns->first - d, 0);
483 }
484
485 if (tns->first != old) {
486 tns_check_view(tns, true);
487 tns->dirty = true;
488 }
489 return tns->first != old;
490}
491
492int tns_translate(tns_t *tns, int x, int y)
493{
494 int n;
495
496 if (tns == NULL || tns->thumbs == NULL)
497 return -1;
498 if (x < tns->x || y < tns->y)
499 return -1;
500
501 n = tns->first + (y - tns->y) / thumb_dim * tns->cols +
502 (x - tns->x) / thumb_dim;
503 if (n >= tns->cnt)
504 n = -1;
505
506 return n;
507}