spiv: Key S now can stop and restart slideshow.
[gfxprim.git] / demos / spiv / spiv.c
blob4eb443309c1fb9374d392a3184123a64c26e67d1
1 /*****************************************************************************
2 * This file is part of gfxprim library. *
3 * *
4 * Gfxprim is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU Lesser General Public *
6 * License as published by the Free Software Foundation; either *
7 * version 2.1 of the License, or (at your option) any later version. *
8 * *
9 * Gfxprim is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * Lesser General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Lesser General Public *
15 * License along with gfxprim; if not, write to the Free Software *
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301 USA *
18 * *
19 * Copyright (C) 2009-2013 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
25 SPIV -- Simple yet Powerful Image Viewer.
29 #include <unistd.h>
30 #include <errno.h>
31 #include <signal.h>
32 #include <string.h>
33 #include <pthread.h>
35 #include <GP.h>
37 #include "image_cache.h"
38 #include "image_list.h"
39 #include "image_loader.h"
40 #include "image_actions.h"
41 #include "spiv_help.h"
42 #include "cpu_timer.h"
44 static GP_Pixel black_pixel;
45 static GP_Pixel white_pixel;
46 static GP_Pixel gray_pixel;
48 static GP_Backend *backend = NULL;
50 /* image loader thread */
51 static int abort_flag = 0;
52 static int show_progress = 0;
53 static int loader_running = 0;
55 enum zoom_type {
57 * Resize image to fit current size of the window.
59 ZOOM_FIT,
62 * Use zoom set in zoom float and zoom offsets.
64 ZOOM_FIXED,
67 * Fixed zoom but spiv tries to change
68 * the window size to fit the image size
70 ZOOM_FIXED_WIN,
73 * Do not upscale images but downscale them
74 * if they are too big.
76 ZOOM_FIT_DOWNSCALE,
79 struct loader_params {
80 /* current resize ratio */
81 float rat;
83 /* show loader progress */
84 long show_progress:1;
85 long show_progress_once:2;
86 /* show image info in topleft corner */
87 long show_info:3;
88 /* use nearest neighbour resampling first */
89 long show_nn_first:4;
90 /* use dithering when blitting to display */
91 long use_dithering:5;
92 /* use low pass before resampling */
93 long use_low_pass:6;
94 /* image orientation 0, 90, 180, 270 */
95 int rotate;
96 /* resampling method */
97 int resampling_method;
99 /* slideshow sleep */
100 int sleep_ms;
102 /* offset in pixels */
103 unsigned int zoom_x_offset;
104 unsigned int zoom_y_offset;
106 /* zoom */
107 enum zoom_type zoom_type;
108 float zoom;
110 /* caches for loaded images */
111 struct image_cache *img_resized_cache;
114 static int image_loader_callback(GP_ProgressCallback *self)
116 static GP_Size size = 0;
117 GP_Context *c = backend->context;
119 if (abort_flag)
120 return 1;
122 if (!show_progress)
123 return 0;
125 char buf[100];
127 snprintf(buf, sizeof(buf), "%s ... %-3.1f%%",
128 (const char*)self->priv, self->percentage);
130 int align = GP_ALIGN_CENTER|GP_VALIGN_ABOVE;
132 size = GP_TextWidth(NULL, buf);
134 int start = c->w/2 - size/2 - 10;
135 int end = c->w/2 + size/2 + 10;
136 int middle = start + (end - start) * self->percentage / 100;
137 int top = c->h - GP_TextHeight(NULL) - 11;
139 GP_FillRectXYXY(c, start, c->h - 1, middle, top, gray_pixel);
140 GP_FillRectXYXY(c, middle, c->h - 1, end, top, black_pixel);
142 GP_Text(c, NULL, c->w/2, c->h - 5, align,
143 white_pixel, black_pixel, buf);
145 GP_BackendUpdateRect(backend, start, c->h - 1, end, top);
147 return 0;
150 static GP_Context *load_image(int elevate);
153 * Ask backend to resize window may not be implemented or authorized. If
154 * backend (window) is resized we will get SYS_RESIZE event, see the main event
155 * loop.
157 static void resize_backend(float ratio, int shift_flag)
159 GP_Context *img = load_image(1);
161 if (!shift_flag)
162 ratio = 1.00 / ratio;
164 unsigned int w = img->w * ratio + 0.5;
165 unsigned int h = img->h * ratio + 0.5;
167 GP_BackendResize(backend, w, h);
171 static float calc_img_size(struct loader_params *params,
172 uint32_t img_w, uint32_t img_h,
173 uint32_t src_w, uint32_t src_h)
175 float w_rat;
176 float h_rat;
178 switch (params->zoom_type) {
179 case ZOOM_FIT_DOWNSCALE:
180 if (img_w <= src_w && img_h <= src_h)
181 return 1.00;
182 case ZOOM_FIT:
183 w_rat = 1.00 * src_w / img_w;
184 h_rat = 1.00 * src_h / img_h;
185 return GP_MIN(w_rat, h_rat);
186 case ZOOM_FIXED:
187 return params->zoom;
188 case ZOOM_FIXED_WIN:
189 resize_backend(params->zoom, 0);
190 return params->zoom;
193 return 1.00;
196 static const char *img_name(const char *img_path)
198 int i, len = strlen(img_path);
200 for (i = len - 1; i > 0; i--) {
201 if (img_path[i] == '/')
202 return &img_path[i+1];
205 return img_path;
208 static void set_caption(const char *path, float rat)
210 char buf[256];
212 snprintf(buf, sizeof(buf), "Spiv ~ %s 1:%3.3f", img_name(path), rat);
214 GP_BackendSetCaption(backend, buf);
218 * Loads image
220 static GP_Context *load_image(int elevate)
222 GP_Context *img;
223 GP_ProgressCallback callback = {.callback = image_loader_callback,
224 .priv = "Loading image"};
226 img = image_loader_get_image(&callback, elevate);
228 if (img)
229 return img;
231 GP_Context *ctx = backend->context;
233 GP_Fill(ctx, black_pixel);
234 GP_Print(ctx, NULL, ctx->w/2, ctx->h/2 - 10,
235 GP_ALIGN_CENTER|GP_VALIGN_CENTER, white_pixel, black_pixel,
236 "'%s'", image_loader_img_path());
237 GP_Print(ctx, NULL, ctx->w/2, ctx->h/2 + 10,
238 GP_ALIGN_CENTER|GP_VALIGN_CENTER, white_pixel, black_pixel,
239 "Failed to load image :( (%s)", strerror(errno));
240 GP_BackendFlip(backend);
242 return NULL;
246 * Fill context with chessboard-like pattern.
248 static void pattern_fill(GP_Context *ctx, unsigned int x0, unsigned int y0,
249 unsigned int w, unsigned int h)
251 unsigned int x, y;
253 GP_Pixel g1 = GP_RGBToContextPixel(0x64, 0x64, 0x64, ctx);
254 GP_Pixel g2 = GP_RGBToContextPixel(0x80, 0x80, 0x80, ctx);
256 unsigned int wm = w/10 < 10 ? 10 : w/10;
257 unsigned int hm = h/10 < 10 ? 10 : h/10;
258 unsigned int wt = w/20 < 5 ? 5 : w/20;
259 unsigned int ht = h/20 < 5 ? 5 : h/20;
261 for (y = 0; y < h; y++) {
262 for (x = 0; x < w; x++) {
263 GP_Pixel pix;
265 if ((x % wm < wt) ^ (y % hm < ht))
266 pix = g1;
267 else
268 pix = g2;
270 GP_PutPixel(ctx, x0 + x, y0 + y, pix);
275 static void update_display(struct loader_params *params, GP_Context *img,
276 GP_Context *orig_img)
278 GP_Context *context = backend->context;
279 struct cpu_timer timer;
280 GP_ProgressCallback callback = {.callback = image_loader_callback};
282 switch (params->rotate) {
283 case 0:
284 break;
285 case 90:
286 callback.priv = "Rotating image (90)";
287 img = GP_FilterRotate90Alloc(img, &callback);
288 break;
289 case 180:
290 callback.priv = "Rotating image (180)";
291 img = GP_FilterRotate180Alloc(img, &callback);
292 break;
293 case 270:
294 callback.priv = "Rotating image (270)";
295 img = GP_FilterRotate270Alloc(img, &callback);
296 break;
299 if (img == NULL)
300 return;
302 int cx = 0;
303 int cy = 0;
305 switch (params->zoom_type) {
306 case ZOOM_FIT_DOWNSCALE:
307 case ZOOM_FIT:
308 cx = (context->w - img->w)/2;
309 cy = (context->h - img->h)/2;
310 break;
311 case ZOOM_FIXED:
312 case ZOOM_FIXED_WIN:
313 cx = params->zoom_x_offset;
314 cy = params->zoom_y_offset;
315 break;
318 GP_Context sub_display;
320 cpu_timer_start(&timer, "Blitting");
322 if (params->use_dithering) {
323 callback.priv = "Dithering";
324 GP_SubContext(context, &sub_display, cx, cy, img->w, img->h);
325 GP_FilterFloydSteinberg(img, &sub_display, NULL);
326 // GP_FilterHilbertPeano(img, &sub_display, NULL);
327 } else {
328 if (GP_PixelHasFlags(img->pixel_type, GP_PIXEL_HAS_ALPHA))
329 pattern_fill(context, cx, cy, img->w, img->h);
331 GP_Blit_Clipped(img, 0, 0, img->w, img->h, context, cx, cy);
334 cpu_timer_stop(&timer);
336 /* clean up the rest of the display */
337 GP_FillRectXYWH(context, 0, 0, cx, context->h, black_pixel);
338 GP_FillRectXYWH(context, 0, 0, context->w, cy, black_pixel);
340 int w = context->w - img->w - cx;
342 if (w > 0)
343 GP_FillRectXYWH(context, img->w + cx, 0, w, context->h, black_pixel);
345 int h = context->h - img->h - cy;
347 if (h > 0)
348 GP_FillRectXYWH(context, 0, img->h + cy, context->w, h, black_pixel);
350 const char *img_path = image_loader_img_path();
352 set_caption(img_path, params->rat);
354 if (!params->show_info)
355 goto out;
357 GP_Size th = GP_TextHeight(NULL);
359 GP_Print(context, NULL, 11, 11, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
360 black_pixel, white_pixel, "%ux%u (%ux%u) 1:%3.3f",
361 img->w, img->h, orig_img->w, orig_img->h, params->rat);
363 GP_Print(context, NULL, 10, 10, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
364 white_pixel, black_pixel, "%ux%u (%ux%u) 1:%3.3f",
365 img->w, img->h, orig_img->w, orig_img->h, params->rat);
367 GP_Print(context, NULL, 11, 13 + th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
368 black_pixel, white_pixel, "%s", img_name(img_path));
370 GP_Print(context, NULL, 10, 12 + th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
371 white_pixel, black_pixel, "%s", img_name(img_path));
373 GP_Print(context, NULL, 11, 13 + 2 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
374 black_pixel, white_pixel, "%s%s",
375 params->use_low_pass && params->rat < 1 ? "Gaussian LP + " : "",
376 GP_InterpolationTypeName(params->resampling_method));
378 GP_Print(context, NULL, 10, 14 + 2 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
379 white_pixel, black_pixel, "%s%s",
380 params->use_low_pass && params->rat < 1 ? "Gaussian LP + " : "",
381 GP_InterpolationTypeName(params->resampling_method));
383 unsigned int count = image_loader_count();
384 unsigned int pos = image_loader_pos() + 1;
386 GP_Print(context, NULL, 11, 17 + 3 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
387 black_pixel, white_pixel, "%u of %u", pos, count);
389 GP_Print(context, NULL, 10, 16 + 3 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
390 white_pixel, black_pixel, "%u of %u", pos, count);
392 if (!image_loader_is_in_dir())
393 goto out;
395 unsigned int dir_count = image_loader_dir_count();
396 unsigned int dir_pos = image_loader_dir_pos() + 1;
398 GP_Print(context, NULL, 11, 19 + 4 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
399 black_pixel, white_pixel, "%u of %u in directory", dir_pos, dir_count);
401 GP_Print(context, NULL, 10, 18 + 4* th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
402 white_pixel, black_pixel, "%u of %u in directory", dir_pos, dir_count);
403 out:
404 if (params->rotate)
405 GP_ContextFree(img);
407 GP_BackendFlip(backend);
410 GP_Context *load_resized_image(struct loader_params *params, GP_Size w, GP_Size h)
412 GP_Context *img, *res = NULL;
413 struct cpu_timer timer;
414 GP_ProgressCallback callback = {.callback = image_loader_callback};
416 const char *img_path = image_loader_img_path();
418 /* Try to get resized cached image */
419 img = image_cache_get2(params->img_resized_cache, 1, "%s %ux%u r%i l%i",
420 img_path, w, h, params->resampling_method,
421 params->use_low_pass);
423 if (img != NULL)
424 return img;
426 /* Otherwise load image and resize it */
427 if ((img = load_image(1)) == NULL)
428 return NULL;
430 if (params->show_nn_first) {
431 /* Do simple interpolation and blit the result */
432 GP_Context *nn = GP_FilterResizeNNAlloc(img, w, h, NULL);
433 if (nn != NULL) {
434 update_display(params, nn, img);
435 GP_ContextFree(nn);
439 /* Do low pass filter */
440 if (params->use_low_pass && params->rat < 1) {
441 cpu_timer_start(&timer, "Blur");
442 callback.priv = "Blurring Image";
444 res = GP_FilterGaussianBlurAlloc(img, 0.4/params->rat,
445 0.4/params->rat, &callback);
447 if (res == NULL)
448 return NULL;
450 img = res;
452 cpu_timer_stop(&timer);
455 // img->gamma = GP_GammaAcquire(img->pixel_type, 0.45);
457 cpu_timer_start(&timer, "Resampling");
458 callback.priv = "Resampling Image";
459 GP_Context *i1 = GP_FilterResizeAlloc(img, w, h, params->resampling_method, &callback);
460 // img->gamma = NULL;
461 // GP_Context *i2 = GP_FilterResizeAlloc(img, w, h, params->resampling_method, &callback);
462 // img = GP_FilterDifferenceAlloc(i2, i1, NULL);
463 // img = GP_FilterInvert(img, NULL, NULL);
464 img = i1;
465 // if (params->resampling_method == GP_INTERP_CUBIC_INT)
466 // GP_FilterEdgeSharpening(img, img, 0.2, NULL);
467 cpu_timer_stop(&timer);
470 if (params->rat > 1.5) {
471 cpu_timer_start(&timer, "Sharpening");
472 callback.priv = "Sharpening";
473 GP_FilterEdgeSharpening(i1, i1, 0.1, &callback);
474 cpu_timer_stop(&timer);
478 /* Free low passed context if needed */
479 GP_ContextFree(res);
481 if (img == NULL)
482 return NULL;
484 image_cache_put2(params->img_resized_cache, img, "%s %ux%u r%i l%i",
485 img_path, w, h, params->resampling_method,
486 params->use_low_pass);
488 return img;
491 static void *image_loader(void *ptr)
493 struct loader_params *params = ptr;
494 struct cpu_timer sum_timer;
495 GP_Context *img, *orig_img, *context = backend->context;
497 cpu_timer_start(&sum_timer, "sum");
499 show_progress = params->show_progress || params->show_progress_once;
500 params->show_progress_once = 0;
502 /* Figure out rotation */
503 GP_Size w, h;
505 switch (params->rotate) {
506 case 0:
507 case 180:
508 default:
509 w = context->w;
510 h = context->h;
511 break;
512 case 90:
513 case 270:
514 w = context->h;
515 h = context->w;
516 break;
519 if ((orig_img = load_image(0)) == NULL) {
520 loader_running = 0;
521 return NULL;
524 params->rat = calc_img_size(params, orig_img->w, orig_img->h, w, h);
526 /* Special case => no need to resize */
527 if (params->rat == 1.0) {
528 img = orig_img;
529 goto update;
532 w = orig_img->w * params->rat + 0.5;
533 h = orig_img->h * params->rat + 0.5;
535 img = load_resized_image(params, w, h);
537 if (img == NULL) {
538 loader_running = 0;
539 return NULL;
542 //image_cache_print(params->img_resized_cache);
543 update:
544 update_display(params, img, orig_img);
545 cpu_timer_stop(&sum_timer);
547 loader_running = 0;
549 return NULL;
552 static pthread_t loader_thread = (pthread_t)0;
554 static void stop_loader(void)
556 if (loader_thread) {
557 abort_flag = 1;
558 pthread_join(loader_thread, NULL);
559 loader_thread = (pthread_t)0;
560 abort_flag = 0;
564 static void show_image(struct loader_params *params)
566 int ret;
568 /* stop previous loader thread */
569 stop_loader();
571 loader_running = 1;
573 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
575 if (ret) {
576 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
577 GP_BackendExit(backend);
578 exit(1);
582 static void image_seek(struct loader_params *params,
583 enum img_seek_offset offset, int whence)
586 * We need to stop loader first because image loader seek may free
587 * image we are currently resamling.
589 stop_loader();
590 image_loader_seek(offset, whence);
591 show_image(params);
594 static void set_zoom_offset(struct loader_params *params, int dx, int dy)
596 params->zoom_x_offset += dx;
597 params->zoom_y_offset += dy;
598 show_image(params);
601 static void zoom_mul(struct loader_params *params, float mul)
603 params->zoom *= mul;
604 show_image(params);
607 static void sighandler(int signo)
609 if (backend != NULL)
610 GP_BackendExit(backend);
612 fprintf(stderr, "Got signal %i\n", signo);
614 _exit(1);
617 static void init_backend(const char *backend_opts)
619 backend = GP_BackendInit(backend_opts, "Spiv", stderr);
621 if (backend == NULL) {
622 fprintf(stderr, "Failed to initalize backend '%s'\n", backend_opts);
623 exit(1);
628 * Figure out cache size depending on the size of RAM.
630 * Initialize cache, image loader.
632 static int init_loader(struct loader_params *params, const char **argv)
634 size_t size = image_cache_get_ram_size();
635 unsigned int resized_size = (1024 * size)/10;
636 unsigned int orig_size = (1024 * size)/50;
638 if (resized_size > 100 * 1024 * 1024)
639 resized_size = 100 * 1024 * 1024;
641 GP_DEBUG(1, "Resized cache size = %u", resized_size);
643 if (image_loader_init(argv, orig_size))
644 return 1;
646 params->img_resized_cache = image_cache_create(resized_size);
648 return 0;
651 static uint32_t timer_callback(GP_Timer *self)
653 struct loader_params *params = self->priv;
654 static int retries = 0;
657 * If loader is still running, reschedule after 20ms
659 * If more than two seconds has passed, try load next
661 if (loader_running && retries < 100) {
662 printf("Loader still running %ims\n", 20 * retries);
663 retries++;
664 return 20;
665 } else {
666 retries = 0;
670 * We need to stop loader first because image loader seek may free
671 * image we are currently resamling.
673 stop_loader();
674 image_loader_seek(IMG_CUR, 1);
675 show_image(params);
677 return params->sleep_ms;
680 int main(int argc, char *argv[])
682 GP_Context *context = NULL;
683 const char *backend_opts = "X11";
684 int opt;
685 int shift_flag;
686 GP_PixelType emul_type = GP_PIXEL_UNKNOWN;
688 struct loader_params params = {
689 .show_progress = 0,
690 .show_progress_once = 0,
691 .show_info = 0,
692 .show_nn_first = 0,
693 .use_dithering = 0,
694 .rotate = 0,
695 .resampling_method = GP_INTERP_LINEAR_LF_INT,
697 .zoom_type = ZOOM_FIT,
698 .zoom = 1,
700 .img_resized_cache = NULL,
702 .sleep_ms = 0,
705 GP_TIMER_DECLARE(timer, 0, 0, "Slideshow", timer_callback, &params);
707 while ((opt = getopt(argc, argv, "b:ce:fhIPr:s:tz:0:1:2:3:4:5:6:7:8:9:")) != -1) {
708 switch (opt) {
709 case 'I':
710 params.show_info = 1;
711 break;
712 case 'P':
713 params.show_progress = 1;
714 break;
715 case 'f':
716 params.use_dithering = 1;
717 break;
718 case 's':
719 params.sleep_ms = 1000 * atof(optarg) + 0.5;
720 break;
721 case 'c':
722 params.resampling_method = GP_INTERP_CUBIC_INT;
723 /* Cubic resampling needs low pass */
724 params.use_low_pass = 1;
725 /* Cubic resampling is slow, show nn first */
726 params.show_nn_first = 1;
727 break;
728 case 'e':
729 emul_type = GP_PixelTypeByName(optarg);
731 if (emul_type == GP_PIXEL_UNKNOWN) {
732 fprintf(stderr, "Invalid pixel type '%s'\n", optarg);
733 return 1;
735 break;
736 case 'r':
737 if (!strcmp(optarg, "90"))
738 params.rotate = 90;
739 else if (!strcmp(optarg, "180"))
740 params.rotate = 180;
741 else if (!strcmp(optarg, "270"))
742 params.rotate = 270;
743 case 'b':
744 backend_opts = optarg;
745 break;
746 case 'h':
747 print_help();
748 exit(0);
749 break;
750 case 't':
751 cpu_timer_switch(1);
752 break;
753 case 'z':
754 switch (optarg[0]) {
755 case 'f':
756 params.zoom_type = ZOOM_FIXED;
757 break;
758 case 'w':
759 params.zoom_type = ZOOM_FIXED_WIN;
760 break;
762 break;
763 case '0':
764 /* -0 is mapped to action 10 */
765 image_action_set(9, optarg);
766 break;
767 case '1' ... '9':
768 image_action_set(opt - '1', optarg);
769 break;
770 default:
771 fprintf(stderr, "Invalid paramter '%c'\n", opt);
772 print_help();
773 return 1;
777 if (optind >= argc) {
778 fprintf(stderr, "Requires path to at least one image\n\n");
779 print_help();
780 return 1;
783 signal(SIGINT, sighandler);
784 signal(SIGSEGV, sighandler);
785 signal(SIGBUS, sighandler);
786 signal(SIGABRT, sighandler);
788 if (init_loader(&params, (const char **)argv + optind))
789 return 1;
791 init_backend(backend_opts);
793 if (emul_type != GP_PIXEL_UNKNOWN)
794 backend = GP_BackendVirtualInit(backend, emul_type, GP_BACKEND_CALL_EXIT);
796 context = backend->context;
798 black_pixel = GP_ColorToContextPixel(GP_COL_BLACK, context);
799 white_pixel = GP_ColorToContextPixel(GP_COL_WHITE, context);
800 gray_pixel = GP_RGBToContextPixel(0x33, 0x33, 0x33, context);
802 GP_Fill(context, black_pixel);
803 GP_BackendFlip(backend);
805 params.show_progress_once = 1;
806 show_image(&params);
808 if (params.sleep_ms) {
809 timer.expires = params.sleep_ms;
810 GP_BackendAddTimer(backend, &timer);
813 for (;;) {
814 GP_Event ev;
816 while (GP_BackendWaitEvent(backend, &ev)) {
818 shift_flag = GP_EventGetKey(&ev, GP_KEY_LEFT_SHIFT) ||
819 GP_EventGetKey(&ev, GP_KEY_RIGHT_SHIFT);
821 switch (ev.type) {
822 case GP_EV_REL:
823 switch (ev.code) {
824 case GP_EV_REL_WHEEL:
825 if (ev.val.val > 0)
826 goto next;
828 if (ev.val.val < 0)
829 goto prev;
830 break;
831 case GP_EV_REL_POS:
832 if (GP_EventGetKey(&ev, GP_BTN_LEFT))
833 set_zoom_offset(&params,
834 ev.val.rel.rx,
835 ev.val.rel.ry);
836 break;
838 break;
839 case GP_EV_KEY:
840 if (ev.code != GP_EV_KEY_DOWN)
841 continue;
843 switch (ev.val.key.key) {
844 case GP_KEY_H:
845 draw_help(backend);
846 show_image(&params);
847 break;
848 case GP_KEY_F:
849 if (GP_BackendIsX11(backend))
850 GP_BackendX11RequestFullscreen(backend, 2);
851 break;
852 case GP_KEY_I:
853 params.show_info = !params.show_info;
855 params.show_progress_once = 1;
856 show_image(&params);
857 break;
858 case GP_KEY_P:
859 params.show_progress = !params.show_progress;
860 break;
861 case GP_KEY_R:
862 params.rotate += 90;
863 if (params.rotate > 270)
864 params.rotate = 0;
866 params.show_progress_once = 1;
867 show_image(&params);
868 break;
869 case GP_KEY_S:
870 if (params.sleep_ms) {
871 if (GP_BackendTimersInQueue(backend)) {
872 GP_BackendRemTimer(backend, &timer);
873 } else {
874 timer.expires = params.sleep_ms;
875 GP_BackendAddTimer(backend, &timer);
878 break;
879 case GP_KEY_RIGHT_BRACE:
880 params.resampling_method++;
882 if (params.resampling_method > GP_INTERP_MAX)
883 params.resampling_method = 0;
884 if (params.resampling_method == GP_INTERP_CUBIC)
885 params.resampling_method++;
886 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
887 params.use_low_pass = 0;
888 params.show_nn_first = 0;
889 } else {
890 params.use_low_pass = 1;
891 params.show_nn_first = 1;
894 params.show_progress_once = 1;
895 show_image(&params);
896 break;
897 case GP_KEY_LEFT_BRACE:
898 if (params.resampling_method == 0)
899 params.resampling_method = GP_INTERP_MAX;
900 else
901 params.resampling_method--;
902 if (params.resampling_method == GP_INTERP_CUBIC)
903 params.resampling_method--;
904 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
905 params.use_low_pass = 0;
906 params.show_nn_first = 0;
907 } else {
908 params.use_low_pass = 1;
909 params.show_nn_first = 1;
912 params.show_progress_once = 1;
913 show_image(&params);
914 break;
915 case GP_KEY_L:
916 params.use_low_pass = !params.use_low_pass;
918 params.show_progress_once = 1;
919 show_image(&params);
920 break;
921 case GP_KEY_D:
922 image_cache_drop(params.img_resized_cache);
923 image_loader_drop_cache();
924 break;
925 case GP_KEY_ESC:
926 case GP_KEY_ENTER:
927 case GP_KEY_Q:
928 stop_loader();
929 image_cache_destroy(params.img_resized_cache);
930 image_loader_destroy();
931 GP_BackendExit(backend);
932 return 0;
933 break;
934 case GP_KEY_PAGE_UP:
935 params.show_progress_once = 1;
936 image_seek(&params, IMG_DIR, -1);
937 break;
938 case GP_KEY_PAGE_DOWN:
939 params.show_progress_once = 1;
940 image_seek(&params, IMG_DIR, 1);
941 break;
942 case GP_KEY_HOME:
943 params.show_progress_once = 1;
944 image_seek(&params, IMG_FIRST, 0);
945 break;
946 case GP_KEY_END:
947 params.show_progress_once = 1;
948 image_seek(&params, IMG_LAST, 0);
949 break;
950 case GP_KEY_RIGHT:
951 if (shift_flag)
952 set_zoom_offset(&params, 1, 0);
953 else
954 set_zoom_offset(&params, 10, 0);
956 break;
957 case GP_KEY_LEFT:
958 if (shift_flag)
959 set_zoom_offset(&params, -1, 0);
960 else
961 set_zoom_offset(&params, -10, 0);
962 break;
963 case GP_KEY_UP:
964 if (shift_flag)
965 set_zoom_offset(&params, 0, -1);
966 else
967 set_zoom_offset(&params, 0, -10);
968 break;
969 case GP_KEY_DOWN:
970 if (shift_flag)
971 set_zoom_offset(&params, 0, 1);
972 else
973 set_zoom_offset(&params, 0, 10);
974 break;
975 next:
976 case GP_KEY_SPACE:
977 params.show_progress_once = 1;
978 if (shift_flag)
979 image_seek(&params, IMG_CUR, 10);
980 else
981 image_seek(&params, IMG_CUR, 1);
982 break;
983 prev:
984 case GP_KEY_BACKSPACE:
985 params.show_progress_once = 1;
986 if (shift_flag)
987 image_seek(&params, IMG_CUR, -10);
988 else
989 image_seek(&params, IMG_CUR, -1);
990 break;
991 case GP_KEY_1 ... GP_KEY_9: {
992 int val = ev.val.key.key - GP_KEY_1 + 1;
993 resize_backend(val, shift_flag);
994 } break;
995 case GP_KEY_0:
996 resize_backend(10, shift_flag);
997 break;
998 case GP_KEY_KP_PLUS:
999 case GP_KEY_DOT:
1000 params.show_progress_once = 1;
1001 zoom_mul(&params, 1.5);
1002 break;
1003 case GP_KEY_KP_MINUS:
1004 case GP_KEY_COMMA:
1005 params.show_progress_once = 1;
1006 zoom_mul(&params, 1/1.5);
1007 break;
1008 case GP_KEY_F1 ... GP_KEY_F10:
1009 image_action_run(ev.val.key.key - GP_KEY_F1,
1010 image_loader_img_path());
1011 break;
1013 break;
1014 case GP_EV_SYS:
1015 switch (ev.code) {
1016 case GP_EV_SYS_RESIZE:
1017 /* stop loader thread before resizing backend buffer */
1018 stop_loader();
1019 GP_BackendResizeAck(backend);
1020 GP_Fill(backend->context, 0);
1021 params.show_progress_once = 1;
1022 show_image(&params);
1023 break;
1024 case GP_EV_SYS_QUIT:
1025 GP_BackendExit(backend);
1026 return 0;
1027 break;
1029 break;
1034 return 0;