core: Get rid of GP_Color.
[gfxprim.git] / demos / spiv / spiv.c
blob75af202b874dd2a45fa75f92ae4dc52d51ff7092
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-2014 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
25 SPIV -- Simple yet Powerful Image Viewer.
29 #include <errno.h>
30 #include <signal.h>
31 #include <string.h>
32 #include <pthread.h>
34 #include <GP.h>
36 #include "image_cache.h"
37 #include "image_list.h"
38 #include "image_loader.h"
39 #include "image_actions.h"
40 #include "spiv_help.h"
41 #include "spiv_config.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 struct loader_params {
56 /* current resize ratio */
57 float zoom_rat;
59 /* offset in pixels */
60 unsigned int zoom_x_offset;
61 unsigned int zoom_y_offset;
63 /* flag that is turned on when user changes zoom */
64 unsigned int zoom_manual;
66 long show_progress_once:2;
67 /* use nearest neighbour resampling first */
68 long show_nn_first:4;
69 /* use low pass before resampling */
70 long use_low_pass:6;
71 /* resampling method */
72 int resampling_method;
74 /* slideshow sleep */
75 int sleep_ms;
77 /* caches for loaded images */
78 struct image_cache *img_resized_cache;
81 static int image_loader_callback(GP_ProgressCallback *self)
83 static GP_Size size = 0;
84 GP_Context *c = backend->context;
86 if (abort_flag)
87 return 1;
89 if (!show_progress)
90 return 0;
92 char buf[100];
94 snprintf(buf, sizeof(buf), "%s ... %-3.1f%%",
95 (const char*)self->priv, self->percentage);
97 int align = GP_ALIGN_CENTER|GP_VALIGN_ABOVE;
99 size = GP_TextWidth(config.style, buf);
101 int start = c->w/2 - size/2 - 10;
102 int end = c->w/2 + size/2 + 10;
103 int middle = start + (end - start) * self->percentage / 100;
104 int top = c->h - GP_TextHeight(config.style) - 11;
106 GP_FillRectXYXY(c, start, c->h - 1, middle, top, gray_pixel);
107 GP_FillRectXYXY(c, middle, c->h - 1, end, top, black_pixel);
109 GP_Text(c, config.style, c->w/2, c->h - 5, align,
110 white_pixel, black_pixel, buf);
112 GP_BackendUpdateRect(backend, start, c->h - 1, end, top);
114 return 0;
117 static GP_Context *load_image(int elevate);
119 static const char *img_name(const char *img_path)
121 int i, len = strlen(img_path);
123 for (i = len - 1; i > 0; i--) {
124 if (img_path[i] == '/')
125 return &img_path[i+1];
128 return img_path;
131 static void set_caption(const char *path, float rat)
133 char buf[256];
135 snprintf(buf, sizeof(buf), "Spiv ~ %s 1:%3.3f", img_name(path), rat);
137 GP_BackendSetCaption(backend, buf);
141 * Loads image
143 static GP_Context *load_image(int elevate)
145 GP_Context *img;
146 GP_ProgressCallback callback = {.callback = image_loader_callback,
147 .priv = "Loading image"};
149 img = image_loader_get_image(&callback, elevate);
151 if (img)
152 return img;
154 GP_Context *ctx = backend->context;
156 GP_Fill(ctx, black_pixel);
157 GP_Print(ctx, config.style, ctx->w/2, ctx->h/2 - 10,
158 GP_ALIGN_CENTER|GP_VALIGN_CENTER, white_pixel, black_pixel,
159 "'%s'", image_loader_img_path());
160 GP_Print(ctx, config.style, ctx->w/2, ctx->h/2 + 10,
161 GP_ALIGN_CENTER|GP_VALIGN_CENTER, white_pixel, black_pixel,
162 "Failed to load image :( (%s)", strerror(errno));
163 GP_BackendFlip(backend);
165 return NULL;
169 * Fill context with chessboard-like pattern.
171 static void pattern_fill(GP_Context *ctx, unsigned int x0, unsigned int y0,
172 unsigned int w, unsigned int h)
174 unsigned int x, y, i, j = 0;
175 GP_Pixel col[2];
177 col[0] = GP_RGBToContextPixel(0x64, 0x64, 0x64, ctx);
178 col[1] = GP_RGBToContextPixel(0x80, 0x80, 0x80, ctx);
180 unsigned int wm = w/20 < 5 ? 5 : w/20;
181 unsigned int hm = h/20 < 5 ? 5 : h/20;
183 for (y = 0; y < h; y += hm) {
184 i = j;
185 j = !j;
186 for (x = 0; x < w; x += wm) {
187 GP_FillRectXYWH(ctx, x0 + x, y0 + y, wm, hm, col[i]);
188 i = !i;
194 static void info_printf(GP_Context *ctx, GP_Coord x, GP_Coord y,
195 const char *fmt, ...)
196 __attribute__ ((format (printf, 4, 5)));
198 static void info_printf(GP_Context *ctx, GP_Coord x, GP_Coord y,
199 const char *fmt, ...)
201 va_list va, vac;
203 va_start(va, fmt);
205 va_copy(vac, va);
206 GP_VPrint(ctx, config.style, x-1, y-1, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM|GP_TEXT_NOBG,
207 black_pixel, white_pixel, fmt, vac);
208 va_end(vac);
210 GP_VPrint(ctx, config.style, x, y, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM|GP_TEXT_NOBG,
211 white_pixel, black_pixel, fmt, va);
213 va_end(va);
216 static unsigned int print_meta_data(GP_DataNode *node, GP_Context *context,
217 unsigned int th, unsigned int y, int level)
219 GP_DataNode *i;
220 unsigned int x;
222 for (i = GP_DataDictFirst(node); i; i = i->next) {
223 y += th;
224 x = th * level + 10;
225 switch (i->type) {
226 case GP_DATA_INT:
227 info_printf(context, x, y, "%s : %li", i->id, i->value.i);
228 break;
229 case GP_DATA_DOUBLE:
230 info_printf(context, x, y, "%s : %lf", i->id, i->value.d);
231 break;
232 case GP_DATA_STRING:
233 info_printf(context, x, y, "%s : %s", i->id, i->value.str);
234 break;
235 case GP_DATA_RATIONAL:
236 info_printf(context, x, y, "%s : %li/%li",
237 i->id, i->value.rat.num, i->value.rat.den);
238 break;
239 case GP_DATA_DICT:
240 info_printf(context, x, y, "%s", i->id);
241 y = print_meta_data(i, context, th, y, level+1);
242 break;
246 return y;
249 static void show_info(struct loader_params *params, GP_Context *img,
250 GP_Context *orig_img)
252 GP_Context *context = backend->context;
253 const char *img_path = image_loader_img_path();
255 set_caption(img_path, params->zoom_rat);
257 if (!config.show_info)
258 return;
260 GP_Size th = GP_TextHeight(config.style), y = 10;
262 info_printf(context, 10, y, "%ux%u (%ux%u) 1:%3.3f %3.1f%% %s",
263 img->w, img->h, orig_img->w, orig_img->h, params->zoom_rat,
264 params->zoom_rat * 100, GP_PixelTypeName(img->pixel_type));
265 y += th + 2;
267 info_printf(context, 10, y, "%s", img_name(img_path));
269 y += th + 2;
271 if (params->zoom_rat != 1.00) {
272 info_printf(context, 10, y, "%s%s",
273 params->use_low_pass && params->zoom_rat < 1 ? "Gaussian LP + " : "",
274 GP_InterpolationTypeName(params->resampling_method));
275 y += th + 2;
278 unsigned int count = image_loader_count();
279 unsigned int pos = image_loader_pos() + 1;
281 info_printf(context, 10, y, "%u of %u", pos, count);
282 y += th + 2;
284 if (image_loader_is_in_dir()) {
285 unsigned int dir_count = image_loader_dir_count();
286 unsigned int dir_pos = image_loader_dir_pos() + 1;
288 info_printf(context, 10, y,
289 "%u of %u in directory", dir_pos, dir_count);
292 GP_DataStorage *meta_data = image_loader_get_meta_data();
294 if (!meta_data)
295 return;
297 GP_DataNode *node = GP_DataStorageRoot(meta_data);
299 if (node->type != GP_DATA_DICT)
300 return;
302 print_meta_data(node, context, th + 2, y + th, 0);
305 static void update_display(struct loader_params *params, GP_Context *img,
306 GP_Context *orig_img)
308 GP_Context *context = backend->context;
309 struct cpu_timer timer;
310 GP_ProgressCallback callback = {.callback = image_loader_callback};
312 switch (config.orientation) {
313 case ROTATE_0:
314 break;
315 case ROTATE_90:
316 callback.priv = "Rotating image (90)";
317 img = GP_FilterRotate90Alloc(img, &callback);
318 break;
319 case ROTATE_180:
320 callback.priv = "Rotating image (180)";
321 img = GP_FilterRotate180Alloc(img, &callback);
322 break;
323 case ROTATE_270:
324 callback.priv = "Rotating image (270)";
325 img = GP_FilterRotate270Alloc(img, &callback);
326 break;
329 if (img == NULL)
330 return;
332 int cx = 0;
333 int cy = 0;
336 * Center the image, if window size is fixed and
337 * the image is smaller than window.
339 if (config.win_strategy == ZOOM_WIN_FIXED) {
341 if (img->w < context->w)
342 cx = (context->w - img->w)/2;
344 if (img->h < context->h)
345 cy = (context->h - img->h)/2;
348 if (params->zoom_manual) {
349 cx = params->zoom_x_offset;
350 cy = params->zoom_y_offset;
353 GP_Context sub_display;
355 cpu_timer_start(&timer, "Blitting");
357 if (config.floyd_steinberg) {
358 callback.priv = "Dithering";
359 GP_SubContext(context, &sub_display, cx, cy, img->w, img->h);
360 GP_FilterFloydSteinberg(img, &sub_display, NULL);
361 // GP_FilterHilbertPeano(img, &sub_display, NULL);
362 } else {
363 if (GP_PixelHasFlags(img->pixel_type, GP_PIXEL_HAS_ALPHA))
364 pattern_fill(context, cx, cy, img->w, img->h);
365 GP_Blit_Clipped(img, 0, 0, img->w, img->h, context, cx, cy);
368 cpu_timer_stop(&timer);
370 /* clean up the rest of the display */
371 GP_FillRectXYWH(context, 0, 0, cx, context->h, black_pixel);
372 GP_FillRectXYWH(context, 0, 0, context->w, cy, black_pixel);
375 int w = context->w - img->w - cx;
377 if (w > 0)
378 GP_FillRectXYWH(context, img->w + cx, 0, w, context->h, black_pixel);
380 int h = context->h - img->h - cy;
382 if (h > 0)
383 GP_FillRectXYWH(context, 0, img->h + cy, context->w, h, black_pixel);
385 show_info(params, img, orig_img);
387 if (config.orientation)
388 GP_ContextFree(img);
390 GP_BackendFlip(backend);
393 GP_Context *load_resized_image(struct loader_params *params, GP_Size w, GP_Size h)
395 GP_Context *img, *res = NULL;
396 struct cpu_timer timer;
397 GP_ProgressCallback callback = {.callback = image_loader_callback};
399 const char *img_path = image_loader_img_path();
401 /* Try to get resized cached image */
402 img = image_cache_get2(params->img_resized_cache, 1, "%s %ux%u r%i l%i",
403 img_path, w, h, params->resampling_method,
404 params->use_low_pass);
406 if (img != NULL)
407 return img;
409 /* Otherwise load image and resize it */
410 if ((img = load_image(1)) == NULL)
411 return NULL;
413 if (params->show_nn_first) {
414 /* Do simple interpolation and blit the result */
415 GP_Context *nn = GP_FilterResizeNNAlloc(img, w, h, NULL);
416 if (nn != NULL) {
417 update_display(params, nn, img);
418 GP_ContextFree(nn);
422 /* Do low pass filter */
423 if (params->use_low_pass && params->zoom_rat < 1) {
424 cpu_timer_start(&timer, "Blur");
425 callback.priv = "Blurring Image";
427 res = GP_FilterGaussianBlurAlloc(img, 0.4/params->zoom_rat,
428 0.4/params->zoom_rat, &callback);
430 if (res == NULL)
431 return NULL;
433 img = res;
435 cpu_timer_stop(&timer);
438 // img->gamma = GP_GammaAcquire(img->pixel_type, 0.45);
440 cpu_timer_start(&timer, "Resampling");
441 callback.priv = "Resampling Image";
442 GP_Context *i1 = GP_FilterResizeAlloc(img, w, h, params->resampling_method, &callback);
443 img = i1;
444 cpu_timer_stop(&timer);
447 if (params->zoom_rat > 1.5) {
448 cpu_timer_start(&timer, "Sharpening");
449 callback.priv = "Sharpening";
450 GP_FilterEdgeSharpening(i1, i1, 0.1, &callback);
451 cpu_timer_stop(&timer);
455 /* Free low passed context if needed */
456 GP_ContextFree(res);
458 if (img == NULL)
459 return NULL;
461 image_cache_put2(params->img_resized_cache, img, NULL, "%s %ux%u r%i l%i",
462 img_path, w, h, params->resampling_method,
463 params->use_low_pass);
465 return img;
468 static float calc_img_size(struct loader_params *params,
469 uint32_t img_w, uint32_t img_h,
470 uint32_t win_w, uint32_t win_h)
472 float w_rat, h_rat, rat;
473 unsigned int max_win_w = config.max_win_w;
474 unsigned int max_win_h = config.max_win_h;
476 switch (config.orientation) {
477 case ROTATE_90:
478 case ROTATE_270:
479 GP_SWAP(win_w, win_h);
480 GP_SWAP(max_win_w, max_win_h);
481 break;
482 default:
483 break;
486 if (params->zoom_manual) {
487 if (config.win_strategy == ZOOM_WIN_RESIZABLE) {
488 win_w = GP_MIN(max_win_w, img_w * params->zoom_rat + 0.5);
489 win_h = GP_MIN(max_win_h, img_h * params->zoom_rat + 0.5);
491 switch (config.orientation) {
492 case ROTATE_90:
493 case ROTATE_270:
494 GP_BackendResize(backend, win_h, win_w);
495 break;
496 default:
497 GP_BackendResize(backend, win_w, win_h);
500 return params->zoom_rat;
504 if (config.win_strategy == ZOOM_WIN_RESIZABLE) {
505 win_w = GP_MIN(max_win_w, img_w);
506 win_h = GP_MIN(max_win_h, img_h);
509 * Image is larger than screen and downscale is enabled ->
510 * resize window to match image ratio.
512 if ((win_w != img_w || win_h != img_h) &&
513 config.zoom_strategy & ZOOM_IMAGE_DOWNSCALE) {
515 w_rat = 1.00 * win_w / img_w;
516 h_rat = 1.00 * win_h / img_h;
518 if (w_rat > 1)
519 w_rat = 1;
521 if (h_rat > 1)
522 w_rat = 1;
524 rat = GP_MIN(h_rat, w_rat);
526 win_w = rat * img_w + 0.5;
527 win_h = rat * img_h + 0.5;
530 switch (config.orientation) {
531 case ROTATE_90:
532 case ROTATE_270:
533 GP_BackendResize(backend, win_h, win_w);
534 break;
535 default:
536 GP_BackendResize(backend, win_w, win_h);
540 if (img_w <= win_w && img_h <= win_h) {
541 if (!(config.zoom_strategy & ZOOM_IMAGE_UPSCALE))
542 return 1.00;
543 } else {
544 if (!(config.zoom_strategy & ZOOM_IMAGE_DOWNSCALE))
545 return 1.00;
549 w_rat = 1.00 * win_w / img_w;
550 h_rat = 1.00 * win_h / img_h;
552 return GP_MIN(w_rat, h_rat);
555 static void *image_loader(void *ptr)
557 struct loader_params *params = ptr;
558 struct cpu_timer sum_timer;
559 GP_Context *img, *orig_img, *context = backend->context;
561 cpu_timer_start(&sum_timer, "sum");
563 show_progress = config.show_progress || params->show_progress_once;
564 params->show_progress_once = 0;
566 if ((orig_img = load_image(0)) == NULL) {
567 loader_running = 0;
568 return NULL;
571 /* Figure zoom */
572 GP_Size w, h;
574 params->zoom_rat = calc_img_size(params, orig_img->w, orig_img->h,
575 context->w, context->h);
577 w = orig_img->w * params->zoom_rat + 0.5;
578 h = orig_img->h * params->zoom_rat + 0.5;
580 /* Special case => no need to resize */
581 if (w == orig_img->w && h == orig_img->h) {
582 img = orig_img;
583 goto update;
586 img = load_resized_image(params, w, h);
588 if (img == NULL) {
589 loader_running = 0;
590 return NULL;
593 update:
594 update_display(params, img, orig_img);
595 cpu_timer_stop(&sum_timer);
597 loader_running = 0;
599 return NULL;
602 static pthread_t loader_thread = (pthread_t)0;
604 static void stop_loader(void)
606 if (loader_thread) {
607 abort_flag = 1;
608 pthread_join(loader_thread, NULL);
609 loader_thread = (pthread_t)0;
610 abort_flag = 0;
614 static void show_image(struct loader_params *params)
616 int ret;
618 /* stop previous loader thread */
619 stop_loader();
621 loader_running = 1;
623 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
625 if (ret) {
626 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
627 GP_BackendExit(backend);
628 exit(1);
632 static void image_seek(struct loader_params *params,
633 enum img_seek_offset offset, int whence)
636 * We need to stop loader first because image loader seek may free
637 * image we are currently resamling.
639 stop_loader();
640 params->zoom_manual = 0;
641 image_loader_seek(offset, whence);
642 show_image(params);
645 static void set_zoom_offset(struct loader_params *params, int dx, int dy)
647 params->zoom_manual = 1;
648 params->zoom_x_offset += dx;
649 params->zoom_y_offset += dy;
650 show_image(params);
653 static void zoom_mul(struct loader_params *params, float mul)
655 params->zoom_manual = 1;
656 params->zoom_rat *= mul;
657 show_image(params);
660 static void zoom_set(struct loader_params *params, float mul)
662 params->zoom_manual = 1;
663 params->zoom_rat = mul;
664 show_image(params);
667 static void sighandler(int signo)
669 if (backend != NULL)
670 GP_BackendExit(backend);
672 fprintf(stderr, "Got signal %i\n", signo);
674 _exit(1);
677 static void init_backend(const char *backend_opts)
679 backend = GP_BackendInit(backend_opts, "Spiv");
681 if (backend == NULL) {
682 fprintf(stderr, "Failed to initalize backend '%s'\n", backend_opts);
683 exit(1);
686 if (config.full_screen) {
687 if (GP_BackendIsX11(backend))
688 GP_BackendX11RequestFullscreen(backend, 2);
692 #define RESIZED_CACHE_MAX 400 * 1024
693 #define ORIG_CACHE_MAX 80 * 1024
696 * Figure out cache size depending on the size of RAM.
698 * Initialize cache, image loader.
700 static int init_loader(struct loader_params *params, const char **argv)
702 size_t size = image_cache_get_ram_size();
703 size_t resized_size = size/10;
704 size_t orig_size = size/50;
706 if (resized_size > RESIZED_CACHE_MAX)
707 resized_size = RESIZED_CACHE_MAX;
709 if (orig_size > ORIG_CACHE_MAX)
710 orig_size = ORIG_CACHE_MAX;
712 GP_DEBUG(1, "Resized cache size = %zukB", resized_size);
713 GP_DEBUG(1, "Orig cache size = %zukB", orig_size);
715 if (image_loader_init(argv, orig_size))
716 return 1;
718 params->img_resized_cache = image_cache_create(resized_size);
720 return 0;
723 static uint32_t timer_callback(GP_Timer *self)
725 struct loader_params *params = self->priv;
726 static int retries = 0;
729 * If loader is still running, reschedule after 20ms
731 * If more than two seconds has passed, try load next
733 if (loader_running && retries < 100) {
734 printf("Loader still running %ims\n", 20 * retries);
735 retries++;
736 return 20;
737 } else {
738 retries = 0;
742 * We need to stop loader first because image loader seek may free
743 * image we are currently resamling.
745 stop_loader();
746 image_loader_seek(IMG_CUR, 1);
747 show_image(params);
749 return params->sleep_ms;
752 int main(int argc, char *argv[])
754 GP_Context *context = NULL;
755 int shift_flag;
756 int opts;
758 struct loader_params params = {
759 .show_progress_once = 0,
760 .show_nn_first = 0,
761 .resampling_method = GP_INTERP_LINEAR_LF_INT,
763 .img_resized_cache = NULL,
765 .sleep_ms = 0,
768 GP_TIMER_DECLARE(timer, 0, 0, "Slideshow", timer_callback, &params);
770 if (access("/etc/spiv.conf", R_OK) == 0)
771 spiv_config_load("/etc/spiv.conf");
773 if (getenv("HOME")) {
774 char buf[256];
776 snprintf(buf, sizeof(buf), "%s/%s", getenv("HOME"), ".spiv");
778 if (access(buf, R_OK) == 0)
779 spiv_config_load(buf);
782 opts = spiv_config_parse_args(argc, argv);
784 if (opts < 0) {
785 print_help();
786 return 1;
789 cpu_timer_switch(config.timers);
790 params.sleep_ms = 1000 * config.slideshow_delay + 0.5;
792 if (opts >= argc) {
794 if (!strcmp(config.backend_init, "help")) {
795 init_backend(config.backend_init);
796 return 0;
799 fprintf(stderr, "Requires path to at least one image\n\n");
800 print_help();
801 return 1;
804 signal(SIGINT, sighandler);
805 signal(SIGSEGV, sighandler);
806 signal(SIGBUS, sighandler);
807 signal(SIGABRT, sighandler);
809 if (init_loader(&params, (const char **)argv + opts))
810 return 1;
812 init_backend(config.backend_init);
814 if (config.emul_type != GP_PIXEL_UNKNOWN) {
815 backend = GP_BackendVirtualInit(backend, config.emul_type,
816 GP_BACKEND_CALL_EXIT);
819 context = backend->context;
821 black_pixel = GP_RGBToContextPixel(0x00, 0x00, 0x00, context);
822 white_pixel = GP_RGBToContextPixel(0xff, 0xff, 0xff, context);
823 gray_pixel = GP_RGBToContextPixel(0x33, 0x33, 0x33, context);
825 GP_Fill(context, black_pixel);
826 GP_BackendFlip(backend);
828 params.show_progress_once = 1;
829 show_image(&params);
831 if (params.sleep_ms) {
832 timer.expires = params.sleep_ms;
833 GP_BackendAddTimer(backend, &timer);
836 for (;;) {
837 GP_Event ev;
839 while (GP_BackendWaitEvent(backend, &ev)) {
841 shift_flag = GP_EventGetKey(&ev, GP_KEY_LEFT_SHIFT) ||
842 GP_EventGetKey(&ev, GP_KEY_RIGHT_SHIFT);
844 switch (ev.type) {
845 case GP_EV_REL:
846 switch (ev.code) {
847 case GP_EV_REL_WHEEL:
848 if (ev.val.val > 0)
849 goto next;
851 if (ev.val.val < 0)
852 goto prev;
853 break;
854 case GP_EV_REL_POS:
855 if (GP_EventGetKey(&ev, GP_BTN_LEFT))
856 set_zoom_offset(&params,
857 ev.val.rel.rx,
858 ev.val.rel.ry);
859 break;
861 break;
862 case GP_EV_KEY:
863 if (ev.code != GP_EV_KEY_DOWN)
864 continue;
866 switch (ev.val.key.key) {
867 case GP_KEY_H:
868 draw_help(backend);
869 show_image(&params);
870 break;
871 case GP_KEY_F:
872 if (GP_BackendIsX11(backend))
873 GP_BackendX11RequestFullscreen(backend, 2);
874 break;
875 case GP_KEY_I:
876 config.show_info = !config.show_info;
878 params.show_progress_once = 1;
879 show_image(&params);
880 break;
881 case GP_KEY_P:
882 config.show_progress = !config.show_progress;
883 break;
884 case GP_KEY_R:
885 config.orientation++;
887 if (config.orientation > ROTATE_270)
888 config.orientation = 0;
890 params.show_progress_once = 1;
891 show_image(&params);
892 break;
893 case GP_KEY_E:
894 if (config.orientation > 0)
895 config.orientation--;
896 else
897 config.orientation = ROTATE_270;
899 params.show_progress_once = 1;
900 show_image(&params);
901 break;
902 case GP_KEY_S:
903 if (params.sleep_ms) {
904 if (GP_BackendTimersInQueue(backend)) {
905 GP_BackendRemTimer(backend, &timer);
906 } else {
907 timer.expires = params.sleep_ms;
908 GP_BackendAddTimer(backend, &timer);
911 break;
912 case GP_KEY_RIGHT_BRACE:
913 params.resampling_method++;
915 if (params.resampling_method > GP_INTERP_MAX)
916 params.resampling_method = 0;
917 if (params.resampling_method == GP_INTERP_CUBIC)
918 params.resampling_method++;
919 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
920 params.use_low_pass = 0;
921 params.show_nn_first = 0;
922 } else {
923 params.use_low_pass = 1;
924 params.show_nn_first = 1;
927 params.show_progress_once = 1;
928 show_image(&params);
929 break;
930 case GP_KEY_LEFT_BRACE:
931 if (params.resampling_method == 0)
932 params.resampling_method = GP_INTERP_MAX;
933 else
934 params.resampling_method--;
935 if (params.resampling_method == GP_INTERP_CUBIC)
936 params.resampling_method--;
937 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
938 params.use_low_pass = 0;
939 params.show_nn_first = 0;
940 } else {
941 params.use_low_pass = 1;
942 params.show_nn_first = 1;
945 params.show_progress_once = 1;
946 show_image(&params);
947 break;
948 case GP_KEY_L:
949 params.use_low_pass = !params.use_low_pass;
951 params.show_progress_once = 1;
952 show_image(&params);
953 break;
954 case GP_KEY_C:
955 image_cache_drop(params.img_resized_cache);
956 image_loader_drop_cache();
957 break;
958 case GP_KEY_W:
959 config_win_toggle();
960 params.show_progress_once = 1;
961 show_image(&params);
962 break;
963 case GP_KEY_U:
964 config_upscale_toggle();
965 params.show_progress_once = 1;
966 show_image(&params);
967 break;
968 case GP_KEY_D:
969 config_downscale_toggle();
970 params.show_progress_once = 1;
971 show_image(&params);
972 break;
973 case GP_KEY_ESC:
974 case GP_KEY_ENTER:
975 case GP_KEY_Q:
976 stop_loader();
977 image_cache_destroy(params.img_resized_cache);
978 image_loader_destroy();
979 GP_BackendExit(backend);
980 return 0;
981 break;
982 case GP_KEY_PAGE_UP:
983 params.show_progress_once = 1;
984 image_seek(&params, IMG_DIR, -1);
985 break;
986 case GP_KEY_PAGE_DOWN:
987 params.show_progress_once = 1;
988 image_seek(&params, IMG_DIR, 1);
989 break;
990 case GP_KEY_HOME:
991 params.show_progress_once = 1;
992 image_seek(&params, IMG_FIRST, 0);
993 break;
994 case GP_KEY_END:
995 params.show_progress_once = 1;
996 image_seek(&params, IMG_LAST, 0);
997 break;
998 case GP_KEY_RIGHT:
999 if (shift_flag)
1000 set_zoom_offset(&params, 1, 0);
1001 else
1002 set_zoom_offset(&params, 10, 0);
1004 break;
1005 case GP_KEY_LEFT:
1006 if (shift_flag)
1007 set_zoom_offset(&params, -1, 0);
1008 else
1009 set_zoom_offset(&params, -10, 0);
1010 break;
1011 case GP_KEY_UP:
1012 if (shift_flag)
1013 set_zoom_offset(&params, 0, -1);
1014 else
1015 set_zoom_offset(&params, 0, -10);
1016 break;
1017 case GP_KEY_DOWN:
1018 if (shift_flag)
1019 set_zoom_offset(&params, 0, 1);
1020 else
1021 set_zoom_offset(&params, 0, 10);
1022 break;
1023 next:
1024 case GP_KEY_SPACE:
1025 params.show_progress_once = 1;
1026 if (shift_flag)
1027 image_seek(&params, IMG_CUR, 10);
1028 else
1029 image_seek(&params, IMG_CUR, 1);
1030 break;
1031 prev:
1032 case GP_KEY_BACKSPACE:
1033 params.show_progress_once = 1;
1034 if (shift_flag)
1035 image_seek(&params, IMG_CUR, -10);
1036 else
1037 image_seek(&params, IMG_CUR, -1);
1038 break;
1039 case GP_KEY_1 ... GP_KEY_9: {
1040 float val = ev.val.key.key - GP_KEY_1 + 1;
1042 if (!shift_flag)
1043 val = 1/val;
1045 zoom_set(&params, val);
1046 } break;
1047 case GP_KEY_0:
1048 if (shift_flag)
1049 zoom_set(&params, 10);
1050 else
1051 zoom_set(&params, 0.1);
1052 break;
1053 case GP_KEY_KP_PLUS:
1054 case GP_KEY_DOT:
1055 params.show_progress_once = 1;
1056 if (shift_flag)
1057 zoom_mul(&params, 1.1);
1058 else
1059 zoom_mul(&params, 1.5);
1060 break;
1061 case GP_KEY_KP_MINUS:
1062 case GP_KEY_COMMA:
1063 params.show_progress_once = 1;
1064 if (shift_flag)
1065 zoom_mul(&params, 1/1.1);
1066 else
1067 zoom_mul(&params, 1/1.5);
1068 break;
1069 case GP_KEY_F1 ... GP_KEY_F10:
1070 image_action_run(ev.val.key.key - GP_KEY_F1 + 1,
1071 image_loader_img_path());
1072 break;
1074 break;
1075 case GP_EV_SYS:
1076 switch (ev.code) {
1077 case GP_EV_SYS_RESIZE:
1078 /* stop loader thread before resizing backend buffer */
1079 stop_loader();
1080 GP_BackendResizeAck(backend);
1081 GP_Fill(backend->context, 0);
1082 params.show_progress_once = 1;
1083 show_image(&params);
1084 break;
1085 case GP_EV_SYS_QUIT:
1086 GP_BackendExit(backend);
1087 return 0;
1088 break;
1090 break;
1095 return 0;