From 7b91e10f225797cef26ce7094bf2ac6b94901b33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bert=20M=C3=BCnnich?= <ber.t@posteo.de>
Date: Sun, 28 Sep 2014 00:26:02 +0200
Subject: [PATCH] Added thumbnail zooming...

- Key mappings +/- are now general commands
- Use JPG as thumbnail cache file format instead of PNG
- Fixes issue #161
---
 README.md    |   4 +-
 commands.c   |  26 +++++++------
 commands.lst |   2 +-
 config.def.h |  12 +++---
 sxiv.1       |  12 +++---
 thumbs.c     | 104 +++++++++++++++++++++++++++++++++------------------
 thumbs.h     |   3 ++
 7 files changed, 100 insertions(+), 63 deletions(-)

diff --git a/README.md b/README.md
index 70c65f6..b282f33 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,8 @@ of small previews is displayed, making it easy to choose an image to open.
     r            Reload image
     D            Remove image from file list and go to next image
     Ctrl-h,j,k,l Scroll one window width/height left/down/up/right
+    +            Zoom in
+    -            Zoom out
     m            Mark/unmark current image
     M            Reverse all image marks
     Ctrl-m       Remove all image marks
@@ -123,8 +125,6 @@ of small previews is displayed, making it easy to choose an image to open.
     h,j,k,l      Scroll image 1/5 of window width/height or [count] pixels
                  left/down/up/right (also with arrow keys)
     H,J,K,L      Scroll to left/bottom/top/right image edge
-    +            Zoom in
-    -            Zoom out
     =            Set zoom level to 100%, or [count]%
     w            Set zoom level to 100%, but fit large images into window
     W            Fit image to window
diff --git a/commands.c b/commands.c
index 6761946..afe3e00 100644
--- a/commands.c
+++ b/commands.c
@@ -193,6 +193,20 @@ bool cg_scroll_screen(arg_t a)
 		return tns_scroll(&tns, dir, true);
 }
 
+bool cg_zoom(arg_t a)
+{
+	long d = (long) a;
+
+	if (mode == MODE_THUMB)
+		return tns_zoom(&tns, d);
+	else if (d > 0)
+		return img_zoom_in(&img);
+	else if (d < 0)
+		return img_zoom_out(&img);
+	else
+		return false;
+}
+
 bool cg_toggle_image_mark(arg_t a)
 {
 	files[fileidx].marked = !files[fileidx].marked;
@@ -386,18 +400,6 @@ bool ci_drag(arg_t a)
 	return true;
 }
 
-bool ci_zoom(arg_t a)
-{
-	long scale = (long) a;
-
-	if (scale > 0)
-		return img_zoom_in(&img);
-	else if (scale < 0)
-		return img_zoom_out(&img);
-	else
-		return false;
-}
-
 bool ci_set_zoom(arg_t a)
 {
 	return img_zoom(&img, (prefix ? prefix : (long) a) / 100.0);
diff --git a/commands.lst b/commands.lst
index 5dd401d..a1b14e6 100644
--- a/commands.lst
+++ b/commands.lst
@@ -8,6 +8,7 @@ G_CMD(remove_image)
 G_CMD(first)
 G_CMD(n_or_last)
 G_CMD(scroll_screen)
+G_CMD(zoom)
 G_CMD(toggle_image_mark)
 G_CMD(reverse_marks)
 G_CMD(unmark_all)
@@ -20,7 +21,6 @@ I_CMD(toggle_animation)
 I_CMD(scroll)
 I_CMD(scroll_to_edge)
 I_CMD(drag)
-I_CMD(zoom)
 I_CMD(set_zoom)
 I_CMD(fit_to_win)
 I_CMD(rotate)
diff --git a/config.def.h b/config.def.h
index 7dadfbc..a22d177 100644
--- a/config.def.h
+++ b/config.def.h
@@ -79,6 +79,10 @@ static const keymap_t keys[] = {
 	{ ControlMask,  XK_Up,            g_scroll_screen,      (arg_t) DIR_UP },
 	{ ControlMask,  XK_l,             g_scroll_screen,      (arg_t) DIR_RIGHT },
 	{ ControlMask,  XK_Right,         g_scroll_screen,      (arg_t) DIR_RIGHT },
+	{ 0,            XK_plus,          g_zoom,               (arg_t) +1 },
+	{ 0,            XK_KP_Add,        g_zoom,               (arg_t) +1 },
+	{ 0,            XK_minus,         g_zoom,               (arg_t) -1 },
+	{ 0,            XK_KP_Subtract,   g_zoom,               (arg_t) -1 },
 	{ 0,            XK_m,             g_toggle_image_mark,  (arg_t) None },
 	{ 0,            XK_M,             g_reverse_marks,      (arg_t) None },
 	{ ControlMask,  XK_m,             g_unmark_all,         (arg_t) None },
@@ -119,10 +123,6 @@ static const keymap_t keys[] = {
 	{ 0,            XK_J,             i_scroll_to_edge,     (arg_t) DIR_DOWN },
 	{ 0,            XK_K,             i_scroll_to_edge,     (arg_t) DIR_UP },
 	{ 0,            XK_L,             i_scroll_to_edge,     (arg_t) DIR_RIGHT },
-	{ 0,            XK_plus,          i_zoom,               (arg_t) +1 },
-	{ 0,            XK_KP_Add,        i_zoom,               (arg_t) +1 },
-	{ 0,            XK_minus,         i_zoom,               (arg_t) -1 },
-	{ 0,            XK_KP_Subtract,   i_zoom,               (arg_t) -1 },
 	{ 0,            XK_equal,         i_set_zoom,           (arg_t) 100 },
 	{ 0,            XK_w,             i_fit_to_win,         (arg_t) SCALE_DOWN },
 	{ 0,            XK_W,             i_fit_to_win,         (arg_t) SCALE_FIT },
@@ -153,8 +153,8 @@ static const button_t buttons[] = {
 	{ ShiftMask,    5,                i_scroll,             (arg_t) DIR_RIGHT },
 	{ 0,            6,                i_scroll,             (arg_t) DIR_LEFT },
 	{ 0,            7,                i_scroll,             (arg_t) DIR_RIGHT },
-	{ ControlMask,  4,                i_zoom,               (arg_t) +1 },
-	{ ControlMask,  5,                i_zoom,               (arg_t) -1 },
+	{ ControlMask,  4,                g_zoom,               (arg_t) +1 },
+	{ ControlMask,  5,                g_zoom,               (arg_t) -1 },
 };
 
 #endif
diff --git a/sxiv.1 b/sxiv.1
index c618189..70c9007 100644
--- a/sxiv.1
+++ b/sxiv.1
@@ -140,6 +140,12 @@ Scroll up one screen height.
 .BR Ctrl-l ", " Ctrl-Right
 Scroll right one screen width.
 .TP
+.BR +
+Zoom in.
+.TP
+.B \-
+Zoom out.
+.TP
 .B m
 Mark/unmark the current image.
 .TP
@@ -251,12 +257,6 @@ Scroll to top image edge.
 Scroll to right image edge.
 .SS Zooming
 .TP
-.BR +
-Zoom in.
-.TP
-.B \-
-Zoom out.
-.TP
 .B =
 Set zoom level to 100%, or
 .IR count %.
diff --git a/thumbs.c b/thumbs.c
index ede8d96..572dc52 100644
--- a/thumbs.c
+++ b/thumbs.c
@@ -17,7 +17,6 @@
  */
 
 #define _POSIX_C_SOURCE 200112L
-#define _THUMBS_CONFIG
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -37,7 +36,7 @@ void exif_auto_orientate(const fileinfo_t*);
 #endif
 
 static char *cache_dir;
-static const int thumb_dim = THUMB_SIZE + 10;
+static const int thumb_size[] = { 32, 64, 96, 128, 160 };
 
 char* tns_cache_filepath(const char *filepath)
 {
@@ -51,7 +50,7 @@ char* tns_cache_filepath(const char *filepath)
 		/* don't cache images inside the cache directory! */
 		len = strlen(cache_dir) + strlen(filepath) + 6;
 		cfile = (char*) s_malloc(len);
-		snprintf(cfile, len, "%s/%s.png", cache_dir, filepath + 1);
+		snprintf(cfile, len, "%s/%s.jpg", cache_dir, filepath + 1);
 	}
 	return cfile;
 }
@@ -79,21 +78,19 @@ Imlib_Image tns_cache_load(const char *filepath, bool *outdated)
 	return im;
 }
 
-void tns_cache_write(thumb_t *t, const fileinfo_t *file, bool force)
+void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
 {
 	char *cfile, *dirend;
 	struct stat cstats, fstats;
 	struct utimbuf times;
 	Imlib_Load_Error err = 0;
 
-	if (t == NULL || t->im == NULL)
+	if (im == NULL || filepath == NULL)
 		return;
-	if (file == NULL || file->name == NULL || file->path == NULL)
-		return;
-	if (stat(file->path, &fstats) < 0)
+	if (stat(filepath, &fstats) < 0)
 		return;
 
-	if ((cfile = tns_cache_filepath(file->path)) != NULL) {
+	if ((cfile = tns_cache_filepath(filepath)) != NULL) {
 		if (force || stat(cfile, &cstats) < 0 ||
 		    cstats.st_mtime != fstats.st_mtime)
 		{
@@ -103,8 +100,8 @@ void tns_cache_write(thumb_t *t, const fileinfo_t *file, bool force)
 				*dirend = '/';
 			}
 			if (err == 0) {
-				imlib_context_set_image(t->im);
-				imlib_image_set_format("png");
+				imlib_context_set_image(im);
+				imlib_image_set_format("jpg");
 				imlib_save_image_with_error_return(cfile, &err);
 			}
 			if (err == 0) {
@@ -173,6 +170,7 @@ void tns_init(tns_t *tns, const fileinfo_t *files, int cnt, int *sel, win_t *win
 	tns->loadnext = tns->first = tns->end = tns->r_first = tns->r_end = 0;
 	tns->sel = sel;
 	tns->win = win;
+	tns->zl = 1;
 	tns->dirty = false;
 
 	if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') {
@@ -211,11 +209,34 @@ void tns_free(tns_t *tns)
 	cache_dir = NULL;
 }
 
+Imlib_Image tns_scale_down(Imlib_Image im, int dim)
+{
+	int w, h;
+	float z, zw, zh;
+
+	imlib_context_set_image(im);
+	w = imlib_image_get_width();
+	h = imlib_image_get_height();
+	zw = (float) dim / (float) w;
+	zh = (float) dim / (float) h;
+	z = MIN(zw, zh);
+	z = MIN(z, 1.0);
+
+	if (z < 1.0) {
+		imlib_context_set_anti_alias(1);
+		im = imlib_create_cropped_scaled_image(0, 0, w, h, z * w, z * h);
+		if (im == NULL)
+			die("could not allocate memory");
+		imlib_free_image_and_decache();
+	}
+	return im;
+}
+
 bool tns_load(tns_t *tns, int n, bool force)
 {
 	int w, h;
 	bool cache_hit = false;
-	float z, zw, zh;
+	float zw, zh;
 	thumb_t *t;
 	Imlib_Image im = NULL;
 	const fileinfo_t *file;
@@ -301,31 +322,20 @@ bool tns_load(tns_t *tns, int n, bool force)
 			return false;
 		}
 	}
-	imlib_context_set_image(im);
-	imlib_context_set_anti_alias(1);
 
+	if (!cache_hit) {
 #if HAVE_LIBEXIF
-	if (!cache_hit)
+		imlib_context_set_image(im);
 		exif_auto_orientate(file);
 #endif
+		im = tns_scale_down(im, thumb_size[ARRLEN(thumb_size)-1]);
+		tns_cache_write(im, file->path, true);
+	}
 
-	w = imlib_image_get_width();
-	h = imlib_image_get_height();
-	zw = (float) THUMB_SIZE / (float) w;
-	zh = (float) THUMB_SIZE / (float) h;
-	z = MIN(zw, zh);
-	z = MIN(z, 1.0);
-	t->w = z * w;
-	t->h = z * h;
-
-	t->im = imlib_create_cropped_scaled_image(0, 0, w, h, t->w, t->h);
-	if (t->im == NULL)
-		die("could not allocate memory");
-
-	imlib_free_image_and_decache();
-
-	if (!cache_hit)
-		tns_cache_write(t, file, true);
+	t->im = tns_scale_down(im, thumb_size[tns->zl]);
+	imlib_context_set_image(t->im);
+	t->w = imlib_image_get_width();
+	t->h = imlib_image_get_height();
 
 	tns->dirty = true;
 	return true;
@@ -382,6 +392,7 @@ void tns_render(tns_t *tns)
 	thumb_t *t;
 	win_t *win;
 	int i, cnt, r, x, y;
+	int thumb_dim;
 
 	if (tns == NULL || tns->thumbs == NULL || tns->win == NULL)
 		return;
@@ -392,6 +403,7 @@ void tns_render(tns_t *tns)
 	win_clear(win);
 	imlib_context_set_drawable(win->buf.pm);
 
+	thumb_dim = thumb_size[tns->zl] + 10;
 	tns->cols = MAX(1, win->w / thumb_dim);
 	tns->rows = MAX(1, win->h / thumb_dim);
 
@@ -422,8 +434,8 @@ void tns_render(tns_t *tns)
 	for (i = tns->first; i < tns->end; i++) {
 		t = &tns->thumbs[i];
 		if (t->im != NULL) {
-			t->x = x + (THUMB_SIZE - t->w) / 2;
-			t->y = y + (THUMB_SIZE - t->h) / 2;
+			t->x = x + (thumb_size[tns->zl] - t->w) / 2;
+			t->y = y + (thumb_size[tns->zl] - t->h) / 2;
 			imlib_context_set_image(t->im);
 			imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h);
 			if (tns->files[i].marked)
@@ -550,6 +562,26 @@ bool tns_scroll(tns_t *tns, direction_t dir, bool screen)
 	return tns->first != old;
 }
 
+bool tns_zoom(tns_t *tns, int d)
+{
+	int i, oldzl;
+
+	if (tns == NULL || tns->thumbs == NULL)
+		return false;
+
+	oldzl = tns->zl;
+	tns->zl += -(d < 0) + (d > 0);
+	tns->zl = MAX(tns->zl, 0);
+	tns->zl = MIN(tns->zl, ARRLEN(thumb_size)-1);
+
+	if (tns->zl != oldzl) {
+		for (i = 0; i < tns->cnt; i++)
+			tns_unload(tns, i);
+		tns->dirty = true;
+	}
+	return tns->zl != oldzl;
+}
+
 int tns_translate(tns_t *tns, int x, int y)
 {
 	int n;
@@ -559,8 +591,8 @@ int tns_translate(tns_t *tns, int x, int y)
 	if (x < tns->x || y < tns->y)
 		return -1;
 
-	n = tns->first + (y - tns->y) / thumb_dim * tns->cols +
-	    (x - tns->x) / thumb_dim;
+	n = tns->first + (y - tns->y) / (thumb_size[tns->zl] + 10) * tns->cols +
+	    (x - tns->x) / (thumb_size[tns->zl] + 10);
 	if (n >= tns->cnt)
 		n = -1;
 
diff --git a/thumbs.h b/thumbs.h
index 88ea7c2..8209979 100644
--- a/thumbs.h
+++ b/thumbs.h
@@ -47,6 +47,7 @@ typedef struct {
 	int y;
 	int cols;
 	int rows;
+	int zl;
 
 	bool dirty;
 } tns_t;
@@ -66,6 +67,8 @@ void tns_highlight(tns_t*, int, bool);
 bool tns_move_selection(tns_t*, direction_t, int);
 bool tns_scroll(tns_t*, direction_t, bool);
 
+bool tns_zoom(tns_t*, int);
+
 int tns_translate(tns_t*, int, int);
 
 #endif /* THUMBS_H */