dotfiles

personal configuration files and scripts
git clone https://tongong.net/git/dotfiles.git
Log | Files | Refs | README

thumbs.c (13918B)


      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 #include "sxiv.h"
     20 #define _THUMBS_CONFIG
     21 #include "config.h"
     22 
     23 #include <errno.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <sys/types.h>
     27 #include <sys/stat.h>
     28 #include <unistd.h>
     29 #include <utime.h>
     30 
     31 #if HAVE_LIBEXIF
     32 #include <libexif/exif-data.h>
     33 void exif_auto_orientate(const fileinfo_t*);
     34 #endif
     35 Imlib_Image img_open(const fileinfo_t*);
     36 
     37 static char *cache_dir;
     38 
     39 char* tns_cache_filepath(const char *filepath)
     40 {
     41 	size_t len;
     42 	char *cfile = NULL;
     43 
     44 	if (*filepath != '/')
     45 		return NULL;
     46 	
     47 	if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) {
     48 		/* don't cache images inside the cache directory! */
     49 		len = strlen(cache_dir) + strlen(filepath) + 2;
     50 		cfile = (char*) emalloc(len);
     51 		snprintf(cfile, len, "%s/%s", cache_dir, filepath + 1);
     52 	}
     53 	return cfile;
     54 }
     55 
     56 Imlib_Image tns_cache_load(const char *filepath, bool *outdated)
     57 {
     58 	char *cfile;
     59 	struct stat cstats, fstats;
     60 	Imlib_Image im = NULL;
     61 
     62 	if (stat(filepath, &fstats) < 0)
     63 		return NULL;
     64 
     65 	if ((cfile = tns_cache_filepath(filepath)) != NULL) {
     66 		if (stat(cfile, &cstats) == 0) {
     67 			if (cstats.st_mtime == fstats.st_mtime)
     68 				im = imlib_load_image(cfile);
     69 			else
     70 				*outdated = true;
     71 		}
     72 		free(cfile);
     73 	}
     74 	return im;
     75 }
     76 
     77 void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
     78 {
     79 	char *cfile, *dirend;
     80 	struct stat cstats, fstats;
     81 	struct utimbuf times;
     82 	Imlib_Load_Error err;
     83 
     84 	if (options->private_mode)
     85 		return;
     86 
     87 	if (stat(filepath, &fstats) < 0)
     88 		return;
     89 
     90 	if ((cfile = tns_cache_filepath(filepath)) != NULL) {
     91 		if (force || stat(cfile, &cstats) < 0 ||
     92 		    cstats.st_mtime != fstats.st_mtime)
     93 		{
     94 			if ((dirend = strrchr(cfile, '/')) != NULL) {
     95 				*dirend = '\0';
     96 				if (r_mkdir(cfile) == -1) {
     97 					error(0, errno, "%s", cfile);
     98 					goto end;
     99 				}
    100 				*dirend = '/';
    101 			}
    102 			imlib_context_set_image(im);
    103 			if (imlib_image_has_alpha()) {
    104 				imlib_image_set_format("png");
    105 			} else {
    106 				imlib_image_set_format("jpg");
    107 				imlib_image_attach_data_value("quality", NULL, 90, NULL);
    108 			}
    109 			imlib_save_image_with_error_return(cfile, &err);
    110 			if (err)
    111 				goto end;
    112 			times.actime = fstats.st_atime;
    113 			times.modtime = fstats.st_mtime;
    114 			utime(cfile, &times);
    115 		}
    116 end:
    117 		free(cfile);
    118 	}
    119 }
    120 
    121 void tns_clean_cache(tns_t *tns)
    122 {
    123 	int dirlen;
    124 	char *cfile, *filename;
    125 	r_dir_t dir;
    126 
    127 	if (r_opendir(&dir, cache_dir, true) < 0) {
    128 		error(0, errno, "%s", cache_dir);
    129 		return;
    130 	}
    131 
    132 	dirlen = strlen(cache_dir);
    133 
    134 	while ((cfile = r_readdir(&dir, false)) != NULL) {
    135 		filename = cfile + dirlen;
    136 		if (access(filename, F_OK) < 0) {
    137 			if (unlink(cfile) < 0)
    138 				error(0, errno, "%s", cfile);
    139 		}
    140 		free(cfile);
    141 	}
    142 	r_closedir(&dir);
    143 }
    144 
    145 
    146 void tns_init(tns_t *tns, fileinfo_t *files, const int *cnt, int *sel,
    147               win_t *win)
    148 {
    149 	int len;
    150 	const char *homedir, *dsuffix = "";
    151 
    152 	if (cnt != NULL && *cnt > 0) {
    153 		tns->thumbs = (thumb_t*) emalloc(*cnt * sizeof(thumb_t));
    154 		memset(tns->thumbs, 0, *cnt * sizeof(thumb_t));
    155 	} else {
    156 		tns->thumbs = NULL;
    157 	}
    158 	tns->files = files;
    159 	tns->cnt = cnt;
    160 	tns->initnext = tns->loadnext = 0;
    161 	tns->first = tns->end = tns->r_first = tns->r_end = 0;
    162 	tns->sel = sel;
    163 	tns->win = win;
    164 	tns->dirty = false;
    165 
    166 	tns->zl = THUMB_SIZE;
    167 	tns_zoom(tns, 0);
    168 
    169 	if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') {
    170 		homedir = getenv("HOME");
    171 		dsuffix = "/.cache";
    172 	}
    173 	if (homedir != NULL) {
    174 		free(cache_dir);
    175 		len = strlen(homedir) + strlen(dsuffix) + 6;
    176 		cache_dir = (char*) emalloc(len);
    177 		snprintf(cache_dir, len, "%s%s/sxiv", homedir, dsuffix);
    178 	} else {
    179 		error(0, 0, "Cache directory not found");
    180 	}
    181 }
    182 
    183 CLEANUP void tns_free(tns_t *tns)
    184 {
    185 	int i;
    186 
    187 	if (tns->thumbs != NULL) {
    188 		for (i = 0; i < *tns->cnt; i++) {
    189 			if (tns->thumbs[i].im != NULL) {
    190 				imlib_context_set_image(tns->thumbs[i].im);
    191 				imlib_free_image();
    192 			}
    193 		}
    194 		free(tns->thumbs);
    195 		tns->thumbs = NULL;
    196 	}
    197 
    198 	free(cache_dir);
    199 	cache_dir = NULL;
    200 }
    201 
    202 Imlib_Image tns_scale_down(Imlib_Image im, int dim)
    203 {
    204 	int w, h;
    205 	float z, zw, zh;
    206 
    207 	imlib_context_set_image(im);
    208 	w = imlib_image_get_width();
    209 	h = imlib_image_get_height();
    210 	zw = (float) dim / (float) w;
    211 	zh = (float) dim / (float) h;
    212 	z = MIN(zw, zh);
    213 	z = MIN(z, 1.0);
    214 
    215 	if (z < 1.0) {
    216 		imlib_context_set_anti_alias(1);
    217 		im = imlib_create_cropped_scaled_image(0, 0, w, h,
    218 		                                       MAX(z * w, 1), MAX(z * h, 1));
    219 		if (im == NULL)
    220 			error(EXIT_FAILURE, ENOMEM, NULL);
    221 		imlib_free_image_and_decache();
    222 	}
    223 	return im;
    224 }
    225 
    226 bool tns_load(tns_t *tns, int n, bool force, bool cache_only)
    227 {
    228 	int maxwh = thumb_sizes[ARRLEN(thumb_sizes)-1];
    229 	bool cache_hit = false;
    230 	char *cfile;
    231 	thumb_t *t;
    232 	fileinfo_t *file;
    233 	Imlib_Image im = NULL;
    234 
    235 	if (n < 0 || n >= *tns->cnt)
    236 		return false;
    237 	file = &tns->files[n];
    238 	if (file->name == NULL || file->path == NULL)
    239 		return false;
    240 
    241 	t = &tns->thumbs[n];
    242 
    243 	if (t->im != NULL) {
    244 		imlib_context_set_image(t->im);
    245 		imlib_free_image();
    246 		t->im = NULL;
    247 	}
    248 
    249 	if (!force) {
    250 		if ((im = tns_cache_load(file->path, &force)) != NULL) {
    251 			imlib_context_set_image(im);
    252 			if (imlib_image_get_width() < maxwh &&
    253 			    imlib_image_get_height() < maxwh)
    254 			{
    255 				if ((cfile = tns_cache_filepath(file->path)) != NULL) {
    256 					unlink(cfile);
    257 					free(cfile);
    258 				}
    259 				imlib_free_image_and_decache();
    260 				im = NULL;
    261 			} else {
    262 				cache_hit = true;
    263 			}
    264 #if HAVE_LIBEXIF
    265 		} else if (!force && !options->private_mode) {
    266 			int pw = 0, ph = 0, w, h, x = 0, y = 0;
    267 			bool err;
    268 			float zw, zh;
    269 			ExifData *ed;
    270 			ExifEntry *entry;
    271 			ExifContent *ifd;
    272 			ExifByteOrder byte_order;
    273 			int tmpfd;
    274 			char tmppath[] = "/tmp/sxiv-XXXXXX";
    275 			Imlib_Image tmpim;
    276 
    277 			if ((ed = exif_data_new_from_file(file->path)) != NULL) {
    278 				if (ed->data != NULL && ed->size > 0 &&
    279 				    (tmpfd = mkstemp(tmppath)) >= 0)
    280 				{
    281 					err = write(tmpfd, ed->data, ed->size) != ed->size;
    282 					close(tmpfd);
    283 
    284 					if (!err && (tmpim = imlib_load_image(tmppath)) != NULL) {
    285 						byte_order = exif_data_get_byte_order(ed);
    286 						ifd = ed->ifd[EXIF_IFD_EXIF];
    287 						entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_X_DIMENSION);
    288 						if (entry != NULL)
    289 							pw = exif_get_long(entry->data, byte_order);
    290 						entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_Y_DIMENSION);
    291 						if (entry != NULL)
    292 							ph = exif_get_long(entry->data, byte_order);
    293 
    294 						imlib_context_set_image(tmpim);
    295 						w = imlib_image_get_width();
    296 						h = imlib_image_get_height();
    297 
    298 						if (pw > w && ph > h && (pw - ph >= 0) == (w - h >= 0)) {
    299 							zw = (float) pw / (float) w;
    300 							zh = (float) ph / (float) h;
    301 							if (zw < zh) {
    302 								pw /= zh;
    303 								x = (w - pw) / 2;
    304 								w = pw;
    305 							} else if (zw > zh) {
    306 								ph /= zw;
    307 								y = (h - ph) / 2;
    308 								h = ph;
    309 							}
    310 						}
    311 						if (w >= maxwh || h >= maxwh) {
    312 							if ((im = imlib_create_cropped_image(x, y, w, h)) == NULL)
    313 								error(EXIT_FAILURE, ENOMEM, NULL);
    314 						}
    315 						imlib_free_image_and_decache();
    316 					}
    317 					unlink(tmppath);
    318 				}
    319 				exif_data_unref(ed);
    320 			}
    321 #endif
    322 		}
    323 	}
    324 
    325 	if (im == NULL) {
    326 		if ((im = img_open(file)) == NULL)
    327 			return false;
    328 	}
    329 	imlib_context_set_image(im);
    330 
    331 	if (!cache_hit) {
    332 #if HAVE_LIBEXIF
    333 		exif_auto_orientate(file);
    334 #endif
    335 		im = tns_scale_down(im, maxwh);
    336 		imlib_context_set_image(im);
    337 		if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh)
    338 			tns_cache_write(im, file->path, true);
    339 	}
    340 
    341 	if (cache_only) {
    342 		imlib_free_image_and_decache();
    343 	} else {
    344 		t->im = tns_scale_down(im, thumb_sizes[tns->zl]);
    345 		imlib_context_set_image(t->im);
    346 		t->w = imlib_image_get_width();
    347 		t->h = imlib_image_get_height();
    348 		tns->dirty = true;
    349 	}
    350 	file->flags |= FF_TN_INIT;
    351 
    352 	if (n == tns->initnext)
    353 		while (++tns->initnext < *tns->cnt && ((++file)->flags & FF_TN_INIT));
    354 	if (n == tns->loadnext && !cache_only)
    355 		while (++tns->loadnext < tns->end && (++t)->im != NULL);
    356 
    357 	return true;
    358 }
    359 
    360 void tns_unload(tns_t *tns, int n)
    361 {
    362 	thumb_t *t;
    363 
    364 	if (n < 0 || n >= *tns->cnt)
    365 		return;
    366 
    367 	t = &tns->thumbs[n];
    368 
    369 	if (t->im != NULL) {
    370 		imlib_context_set_image(t->im);
    371 		imlib_free_image();
    372 		t->im = NULL;
    373 	}
    374 }
    375 
    376 void tns_check_view(tns_t *tns, bool scrolled)
    377 {
    378 	int r;
    379 
    380 	if (tns == NULL)
    381 		return;
    382 
    383 	tns->first -= tns->first % tns->cols;
    384 	r = *tns->sel % tns->cols;
    385 
    386 	if (scrolled) {
    387 		/* move selection into visible area */
    388 		if (*tns->sel >= tns->first + tns->cols * tns->rows)
    389 			*tns->sel = tns->first + r + tns->cols * (tns->rows - 1);
    390 		else if (*tns->sel < tns->first)
    391 			*tns->sel = tns->first + r;
    392 	} else {
    393 		/* scroll to selection */
    394 		if (tns->first + tns->cols * tns->rows <= *tns->sel) {
    395 			tns->first = *tns->sel - r - tns->cols * (tns->rows - 1);
    396 			tns->dirty = true;
    397 		} else if (tns->first > *tns->sel) {
    398 			tns->first = *tns->sel - r;
    399 			tns->dirty = true;
    400 		}
    401 	}
    402 }
    403 
    404 void tns_render(tns_t *tns)
    405 {
    406 	thumb_t *t;
    407 	win_t *win;
    408 	int i, cnt, r, x, y;
    409 
    410 	if (!tns->dirty)
    411 		return;
    412 
    413 	win = tns->win;
    414 	win_clear(win);
    415 	imlib_context_set_drawable(win->buf.pm);
    416 
    417 	tns->cols = MAX(1, win->w / tns->dim);
    418 	tns->rows = MAX(1, win->h / tns->dim);
    419 
    420 	if (*tns->cnt < tns->cols * tns->rows) {
    421 		tns->first = 0;
    422 		cnt = *tns->cnt;
    423 	} else {
    424 		tns_check_view(tns, false);
    425 		cnt = tns->cols * tns->rows;
    426 		if ((r = tns->first + cnt - *tns->cnt) >= tns->cols)
    427 			tns->first -= r - r % tns->cols;
    428 		if (r > 0)
    429 			cnt -= r % tns->cols;
    430 	}
    431 	r = cnt % tns->cols ? 1 : 0;
    432 	tns->x = x = (win->w - MIN(cnt, tns->cols) * tns->dim) / 2 + tns->bw + 3;
    433 	tns->y = y = (win->h - (cnt / tns->cols + r) * tns->dim) / 2 + tns->bw + 3;
    434 	tns->loadnext = *tns->cnt;
    435 	tns->end = tns->first + cnt;
    436 
    437 	for (i = tns->r_first; i < tns->r_end; i++) {
    438 		if ((i < tns->first || i >= tns->end) && tns->thumbs[i].im != NULL)
    439 			tns_unload(tns, i);
    440 	}
    441 	tns->r_first = tns->first;
    442 	tns->r_end = tns->end;
    443 
    444 	for (i = tns->first; i < tns->end; i++) {
    445 		t = &tns->thumbs[i];
    446 		if (t->im != NULL) {
    447 			t->x = x + (thumb_sizes[tns->zl] - t->w) / 2;
    448 			t->y = y + (thumb_sizes[tns->zl] - t->h) / 2;
    449 			imlib_context_set_image(t->im);
    450 			imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h);
    451 			if (tns->files[i].flags & FF_MARK)
    452 				tns_mark(tns, i, true);
    453 		} else {
    454 			tns->loadnext = MIN(tns->loadnext, i);
    455 		}
    456 		if ((i + 1) % tns->cols == 0) {
    457 			x = tns->x;
    458 			y += tns->dim;
    459 		} else {
    460 			x += tns->dim;
    461 		}
    462 	}
    463 	tns->dirty = false;
    464 	tns_highlight(tns, *tns->sel, true);
    465 }
    466 
    467 void tns_mark(tns_t *tns, int n, bool mark)
    468 {
    469 	if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) {
    470 		win_t *win = tns->win;
    471 		thumb_t *t = &tns->thumbs[n];
    472 		unsigned long col = win->bg.pixel;
    473 		int x = t->x + t->w, y = t->y + t->h;
    474 
    475 		win_draw_rect(win, x - 1, y + 1, 1, tns->bw, true, 1, col);
    476 		win_draw_rect(win, x + 1, y - 1, tns->bw, 1, true, 1, col);
    477 
    478 		if (mark)
    479 			col = win->fg.pixel;
    480 
    481 		win_draw_rect(win, x, y, tns->bw + 2, tns->bw + 2, true, 1, col);
    482 
    483 		if (!mark && n == *tns->sel)
    484 			tns_highlight(tns, n, true);
    485 	}
    486 }
    487 
    488 void tns_highlight(tns_t *tns, int n, bool hl)
    489 {
    490 	if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) {
    491 		win_t *win = tns->win;
    492 		thumb_t *t = &tns->thumbs[n];
    493 		unsigned long col = hl ? win->fg.pixel : win->bg.pixel;
    494 		int oxy = (tns->bw + 1) / 2 + 1, owh = tns->bw + 2;
    495 
    496 		win_draw_rect(win, t->x - oxy, t->y - oxy, t->w + owh, t->h + owh,
    497 		              false, tns->bw, col);
    498 
    499 		if (tns->files[n].flags & FF_MARK)
    500 			tns_mark(tns, n, true);
    501 	}
    502 }
    503 
    504 bool tns_move_selection(tns_t *tns, direction_t dir, int cnt)
    505 {
    506 	int old, max;
    507 
    508 	old = *tns->sel;
    509 	cnt = cnt > 1 ? cnt : 1;
    510 
    511 	switch (dir) {
    512 		case DIR_UP:
    513 			*tns->sel = MAX(*tns->sel - cnt * tns->cols, *tns->sel % tns->cols);
    514 			break;
    515 		case DIR_DOWN:
    516 			max = tns->cols * ((*tns->cnt - 1) / tns->cols) +
    517 			      MIN((*tns->cnt - 1) % tns->cols, *tns->sel % tns->cols);
    518 			*tns->sel = MIN(*tns->sel + cnt * tns->cols, max);
    519 			break;
    520 		case DIR_LEFT:
    521 			*tns->sel = MAX(*tns->sel - cnt, 0);
    522 			break;
    523 		case DIR_RIGHT:
    524 			*tns->sel = MIN(*tns->sel + cnt, *tns->cnt - 1);
    525 			break;
    526 	}
    527 
    528 	if (*tns->sel != old) {
    529 		tns_highlight(tns, old, false);
    530 		tns_check_view(tns, false);
    531 		if (!tns->dirty)
    532 			tns_highlight(tns, *tns->sel, true);
    533 	}
    534 	return *tns->sel != old;
    535 }
    536 
    537 bool tns_scroll(tns_t *tns, direction_t dir, bool screen)
    538 {
    539 	int d, max, old;
    540 
    541 	old = tns->first;
    542 	d = tns->cols * (screen ? tns->rows : 1);
    543 
    544 	if (dir == DIR_DOWN) {
    545 		max = *tns->cnt - tns->cols * tns->rows;
    546 		if (*tns->cnt % tns->cols != 0)
    547 			max += tns->cols - *tns->cnt % tns->cols;
    548 		tns->first = MIN(tns->first + d, max);
    549 	} else if (dir == DIR_UP) {
    550 		tns->first = MAX(tns->first - d, 0);
    551 	}
    552 
    553 	if (tns->first != old) {
    554 		tns_check_view(tns, true);
    555 		tns->dirty = true;
    556 	}
    557 	return tns->first != old;
    558 }
    559 
    560 bool tns_zoom(tns_t *tns, int d)
    561 {
    562 	int i, oldzl;
    563 
    564 	oldzl = tns->zl;
    565 	tns->zl += -(d < 0) + (d > 0);
    566 	tns->zl = MAX(tns->zl, 0);
    567 	tns->zl = MIN(tns->zl, ARRLEN(thumb_sizes)-1);
    568 
    569 	tns->bw = ((thumb_sizes[tns->zl] - 1) >> 5) + 1;
    570 	tns->bw = MIN(tns->bw, 4);
    571 	tns->dim = thumb_sizes[tns->zl] + 2 * tns->bw + 6;
    572 
    573 	if (tns->zl != oldzl) {
    574 		for (i = 0; i < *tns->cnt; i++)
    575 			tns_unload(tns, i);
    576 		tns->dirty = true;
    577 	}
    578 	return tns->zl != oldzl;
    579 }
    580 
    581 int tns_translate(tns_t *tns, int x, int y)
    582 {
    583 	int n;
    584 
    585 	if (x < tns->x || y < tns->y)
    586 		return -1;
    587 
    588 	n = tns->first + (y - tns->y) / tns->dim * tns->cols +
    589 	    (x - tns->x) / tns->dim;
    590 	if (n >= *tns->cnt)
    591 		n = -1;
    592 
    593 	return n;
    594 }