Rename GP_Context -> GP_Pixmap
[gfxprim.git] / demos / spiv / spiv.c
blob947390c964be2f2df144b229263409b531bc2983
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_Pixmap *c = backend->pixmap;
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_Pixmap *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_Pixmap *load_image(int elevate)
145 GP_Pixmap *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_Pixmap *pixmap = backend->pixmap;
156 GP_Fill(pixmap, black_pixel);
157 GP_Print(pixmap, config.style, pixmap->w/2, pixmap->h/2 - 10,
158 GP_ALIGN_CENTER|GP_VALIGN_CENTER, white_pixel, black_pixel,
159 "'%s'", image_loader_img_path());
160 GP_Print(pixmap, config.style, pixmap->w/2, pixmap->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 pixmap with chessboard-like pattern.
171 static void pattern_fill(GP_Pixmap *pixmap, 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_RGBToPixmapPixel(0x64, 0x64, 0x64, pixmap);
178 col[1] = GP_RGBToPixmapPixel(0x80, 0x80, 0x80, pixmap);
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(pixmap, x0 + x, y0 + y, wm, hm, col[i]);
188 i = !i;
194 static void info_printf(GP_Pixmap *pixmap, GP_Coord x, GP_Coord y,
195 const char *fmt, ...)
196 __attribute__ ((format (printf, 4, 5)));
198 static void info_printf(GP_Pixmap *pixmap, 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(pixmap, 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(pixmap, 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_Pixmap *pixmap,
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(pixmap, x, y, "%s : %li", i->id, i->value.i);
228 break;
229 case GP_DATA_DOUBLE:
230 info_printf(pixmap, x, y, "%s : %lf", i->id, i->value.d);
231 break;
232 case GP_DATA_STRING:
233 info_printf(pixmap, x, y, "%s : %s", i->id, i->value.str);
234 break;
235 case GP_DATA_RATIONAL:
236 info_printf(pixmap, 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(pixmap, x, y, "%s", i->id);
241 y = print_meta_data(i, pixmap, th, y, level+1);
242 break;
246 return y;
249 static void show_info(struct loader_params *params, GP_Pixmap *img,
250 GP_Pixmap *orig_img)
252 GP_Pixmap *pixmap = backend->pixmap;
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(pixmap, 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(pixmap, 10, y, "%s", img_name(img_path));
269 y += th + 2;
271 if (params->zoom_rat != 1.00) {
272 info_printf(pixmap, 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(pixmap, 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(pixmap, 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, pixmap, th + 2, y + th, 0);
305 static void update_display(struct loader_params *params, GP_Pixmap *img,
306 GP_Pixmap *orig_img)
308 GP_Pixmap *pixmap = backend->pixmap;
309 struct cpu_timer timer;
310 GP_ProgressCallback callback = {.callback = image_loader_callback};
312 switch (config.combined_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;
327 default:
328 break;
331 if (img == NULL)
332 return;
334 int cx = 0;
335 int cy = 0;
338 * Center the image, if window size is fixed and
339 * the image is smaller than window.
341 if (config.win_strategy == ZOOM_WIN_FIXED) {
343 if (img->w < pixmap->w)
344 cx = (pixmap->w - img->w)/2;
346 if (img->h < pixmap->h)
347 cy = (pixmap->h - img->h)/2;
350 if (params->zoom_manual) {
351 cx = params->zoom_x_offset;
352 cy = params->zoom_y_offset;
355 GP_Pixmap sub_display;
357 cpu_timer_start(&timer, "Blitting");
359 if (config.floyd_steinberg) {
360 callback.priv = "Dithering";
361 GP_SubPixmap(pixmap, &sub_display, cx, cy, img->w, img->h);
362 GP_FilterFloydSteinberg(img, &sub_display, NULL);
363 // GP_FilterHilbertPeano(img, &sub_display, NULL);
364 } else {
365 if (GP_PixelHasFlags(img->pixel_type, GP_PIXEL_HAS_ALPHA))
366 pattern_fill(pixmap, cx, cy, img->w, img->h);
367 GP_Blit_Clipped(img, 0, 0, img->w, img->h, pixmap, cx, cy);
370 cpu_timer_stop(&timer);
372 /* clean up the rest of the display */
373 GP_FillRectXYWH(pixmap, 0, 0, cx, pixmap->h, black_pixel);
374 GP_FillRectXYWH(pixmap, 0, 0, pixmap->w, cy, black_pixel);
377 int w = pixmap->w - img->w - cx;
379 if (w > 0)
380 GP_FillRectXYWH(pixmap, img->w + cx, 0, w, pixmap->h, black_pixel);
382 int h = pixmap->h - img->h - cy;
384 if (h > 0)
385 GP_FillRectXYWH(pixmap, 0, img->h + cy, pixmap->w, h, black_pixel);
387 show_info(params, img, orig_img);
389 if (config.combined_orientation)
390 GP_PixmapFree(img);
392 GP_BackendFlip(backend);
395 GP_Pixmap *load_resized_image(struct loader_params *params, GP_Size w, GP_Size h)
397 GP_Pixmap *img, *res = NULL;
398 struct cpu_timer timer;
399 GP_ProgressCallback callback = {.callback = image_loader_callback};
401 const char *img_path = image_loader_img_path();
403 /* Try to get resized cached image */
404 img = image_cache_get2(params->img_resized_cache, 1, "%s %ux%u r%i l%i",
405 img_path, w, h, params->resampling_method,
406 params->use_low_pass);
408 if (img != NULL)
409 return img;
411 /* Otherwise load image and resize it */
412 if ((img = load_image(1)) == NULL)
413 return NULL;
415 if (params->show_nn_first) {
416 /* Do simple interpolation and blit the result */
417 GP_Pixmap *nn = GP_FilterResizeNNAlloc(img, w, h, NULL);
418 if (nn != NULL) {
419 update_display(params, nn, img);
420 GP_PixmapFree(nn);
424 /* Do low pass filter */
425 if (params->use_low_pass && params->zoom_rat < 1) {
426 cpu_timer_start(&timer, "Blur");
427 callback.priv = "Blurring Image";
429 res = GP_FilterGaussianBlurAlloc(img, 0.4/params->zoom_rat,
430 0.4/params->zoom_rat, &callback);
432 if (res == NULL)
433 return NULL;
435 img = res;
437 cpu_timer_stop(&timer);
440 // img->gamma = GP_GammaAcquire(img->pixel_type, 0.45);
442 cpu_timer_start(&timer, "Resampling");
443 callback.priv = "Resampling Image";
444 GP_Pixmap *i1 = GP_FilterResizeAlloc(img, w, h, params->resampling_method, &callback);
445 img = i1;
446 cpu_timer_stop(&timer);
449 if (params->zoom_rat > 1.5) {
450 cpu_timer_start(&timer, "Sharpening");
451 callback.priv = "Sharpening";
452 GP_FilterEdgeSharpening(i1, i1, 0.1, &callback);
453 cpu_timer_stop(&timer);
457 /* Free low passed pixmap if needed */
458 GP_PixmapFree(res);
460 if (img == NULL)
461 return NULL;
463 image_cache_put2(params->img_resized_cache, img, NULL, "%s %ux%u r%i l%i",
464 img_path, w, h, params->resampling_method,
465 params->use_low_pass);
467 return img;
470 static void exif_autorotate(void)
472 GP_DataNode *orientation;
474 config.combined_orientation = config.orientation;
476 if (!config.exif_autorotate)
477 return;
479 orientation = GP_DataStorageGetByPath(image_loader_get_meta_data(),
480 NULL, "/Exif/Orientation");
481 if (!orientation)
482 return;
484 switch (orientation->value.i) {
485 case GP_EXIF_UPPER_LEFT:
486 config.combined_orientation += ROTATE_0;
487 break;
488 case GP_EXIF_LOWER_RIGHT:
489 config.combined_orientation += ROTATE_180;
490 break;
491 case GP_EXIF_UPPER_RIGHT:
492 config.combined_orientation += ROTATE_90;
493 break;
494 case GP_EXIF_LOWER_LEFT:
495 config.combined_orientation += ROTATE_270;
496 break;
499 if (config.combined_orientation > ROTATE_270)
500 config.combined_orientation -= ROTATE_360;
503 static float calc_img_size(struct loader_params *params,
504 uint32_t img_w, uint32_t img_h,
505 uint32_t win_w, uint32_t win_h)
507 float w_rat, h_rat, rat;
508 unsigned int max_win_w = config.max_win_w;
509 unsigned int max_win_h = config.max_win_h;
511 exif_autorotate();
513 switch (config.combined_orientation) {
514 case ROTATE_90:
515 case ROTATE_270:
516 GP_SWAP(win_w, win_h);
517 GP_SWAP(max_win_w, max_win_h);
518 break;
519 default:
520 break;
523 if (params->zoom_manual) {
524 if (config.win_strategy == ZOOM_WIN_RESIZABLE) {
525 win_w = GP_MIN(max_win_w, img_w * params->zoom_rat + 0.5);
526 win_h = GP_MIN(max_win_h, img_h * params->zoom_rat + 0.5);
528 switch (config.combined_orientation) {
529 case ROTATE_90:
530 case ROTATE_270:
531 GP_BackendResize(backend, win_h, win_w);
532 break;
533 default:
534 GP_BackendResize(backend, win_w, win_h);
537 return params->zoom_rat;
541 if (config.win_strategy == ZOOM_WIN_RESIZABLE) {
542 win_w = GP_MIN(max_win_w, img_w);
543 win_h = GP_MIN(max_win_h, img_h);
546 * Image is larger than screen and downscale is enabled ->
547 * resize window to match image ratio.
549 if ((win_w != img_w || win_h != img_h) &&
550 config.zoom_strategy & ZOOM_IMAGE_DOWNSCALE) {
552 w_rat = 1.00 * win_w / img_w;
553 h_rat = 1.00 * win_h / img_h;
555 if (w_rat > 1)
556 w_rat = 1;
558 if (h_rat > 1)
559 w_rat = 1;
561 rat = GP_MIN(h_rat, w_rat);
563 win_w = rat * img_w + 0.5;
564 win_h = rat * img_h + 0.5;
567 switch (config.combined_orientation) {
568 case ROTATE_90:
569 case ROTATE_270:
570 GP_BackendResize(backend, win_h, win_w);
571 break;
572 default:
573 GP_BackendResize(backend, win_w, win_h);
577 if (img_w <= win_w && img_h <= win_h) {
578 if (!(config.zoom_strategy & ZOOM_IMAGE_UPSCALE))
579 return 1.00;
580 } else {
581 if (!(config.zoom_strategy & ZOOM_IMAGE_DOWNSCALE))
582 return 1.00;
586 w_rat = 1.00 * win_w / img_w;
587 h_rat = 1.00 * win_h / img_h;
589 return GP_MIN(w_rat, h_rat);
592 static void *image_loader(void *ptr)
594 struct loader_params *params = ptr;
595 struct cpu_timer sum_timer;
596 GP_Pixmap *img, *orig_img, *pixmap = backend->pixmap;
598 cpu_timer_start(&sum_timer, "sum");
600 show_progress = config.show_progress || params->show_progress_once;
601 params->show_progress_once = 0;
603 if ((orig_img = load_image(0)) == NULL) {
604 loader_running = 0;
605 return NULL;
608 /* Figure zoom */
609 GP_Size w, h;
611 params->zoom_rat = calc_img_size(params, orig_img->w, orig_img->h,
612 pixmap->w, pixmap->h);
614 w = orig_img->w * params->zoom_rat + 0.5;
615 h = orig_img->h * params->zoom_rat + 0.5;
617 /* Special case => no need to resize */
618 if (w == orig_img->w && h == orig_img->h) {
619 img = orig_img;
620 goto update;
623 img = load_resized_image(params, w, h);
625 if (img == NULL) {
626 loader_running = 0;
627 return NULL;
630 update:
631 update_display(params, img, orig_img);
632 cpu_timer_stop(&sum_timer);
634 loader_running = 0;
636 return NULL;
639 static pthread_t loader_thread = (pthread_t)0;
641 static void stop_loader(void)
643 if (loader_thread) {
644 abort_flag = 1;
645 pthread_join(loader_thread, NULL);
646 loader_thread = (pthread_t)0;
647 abort_flag = 0;
651 static void show_image(struct loader_params *params)
653 int ret;
655 /* stop previous loader thread */
656 stop_loader();
658 loader_running = 1;
660 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
662 if (ret) {
663 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
664 GP_BackendExit(backend);
665 exit(1);
669 static void image_seek(struct loader_params *params,
670 enum img_seek_offset offset, int whence)
673 * We need to stop loader first because image loader seek may free
674 * image we are currently resamling.
676 stop_loader();
677 params->zoom_manual = 0;
678 image_loader_seek(offset, whence);
679 show_image(params);
682 static void set_zoom_offset(struct loader_params *params, int dx, int dy)
684 params->zoom_manual = 1;
685 params->zoom_x_offset += dx;
686 params->zoom_y_offset += dy;
687 show_image(params);
690 static void zoom_mul(struct loader_params *params, float mul)
692 params->zoom_manual = 1;
693 params->zoom_rat *= mul;
694 show_image(params);
697 static void zoom_set(struct loader_params *params, float mul)
699 params->zoom_manual = 1;
700 params->zoom_rat = mul;
701 show_image(params);
704 static void sighandler(int signo)
706 if (backend != NULL)
707 GP_BackendExit(backend);
709 fprintf(stderr, "Got signal %i\n", signo);
711 _exit(1);
714 static void init_backend(const char *backend_opts)
716 backend = GP_BackendInit(backend_opts, "Spiv");
718 if (backend == NULL) {
719 fprintf(stderr, "Failed to initalize backend '%s'\n", backend_opts);
720 exit(1);
723 if (config.full_screen) {
724 if (GP_BackendIsX11(backend))
725 GP_BackendX11RequestFullscreen(backend, 2);
729 #define RESIZED_CACHE_MAX 400 * 1024
730 #define ORIG_CACHE_MAX 80 * 1024
733 * Figure out cache size depending on the size of RAM.
735 * Initialize cache, image loader.
737 static int init_loader(struct loader_params *params, const char **argv)
739 size_t size = image_cache_get_ram_size();
740 size_t resized_size = size/10;
741 size_t orig_size = size/50;
743 if (resized_size > RESIZED_CACHE_MAX)
744 resized_size = RESIZED_CACHE_MAX;
746 if (orig_size > ORIG_CACHE_MAX)
747 orig_size = ORIG_CACHE_MAX;
749 GP_DEBUG(1, "Resized cache size = %zukB", resized_size);
750 GP_DEBUG(1, "Orig cache size = %zukB", orig_size);
752 if (image_loader_init(argv, orig_size))
753 return 1;
755 params->img_resized_cache = image_cache_create(resized_size);
757 return 0;
760 static uint32_t timer_callback(GP_Timer *self)
762 struct loader_params *params = self->priv;
763 static int retries = 0;
766 * If loader is still running, reschedule after 20ms
768 * If more than two seconds has passed, try load next
770 if (loader_running && retries < 100) {
771 printf("Loader still running %ims\n", 20 * retries);
772 retries++;
773 return 20;
774 } else {
775 retries = 0;
779 * We need to stop loader first because image loader seek may free
780 * image we are currently resamling.
782 stop_loader();
783 image_loader_seek(IMG_CUR, 1);
784 show_image(params);
786 return params->sleep_ms;
789 int main(int argc, char *argv[])
791 GP_Pixmap *pixmap = NULL;
792 int shift_flag;
793 int opts;
795 struct loader_params params = {
796 .show_progress_once = 0,
797 .show_nn_first = 0,
798 .resampling_method = GP_INTERP_LINEAR_LF_INT,
800 .img_resized_cache = NULL,
802 .sleep_ms = 0,
805 GP_TIMER_DECLARE(timer, 0, 0, "Slideshow", timer_callback, &params);
807 if (access("/etc/spiv.conf", R_OK) == 0)
808 spiv_config_load("/etc/spiv.conf");
810 if (getenv("HOME")) {
811 char buf[256];
813 snprintf(buf, sizeof(buf), "%s/%s", getenv("HOME"), ".spiv");
815 if (access(buf, R_OK) == 0)
816 spiv_config_load(buf);
819 opts = spiv_config_parse_args(argc, argv);
821 if (opts < 0) {
822 print_help();
823 return 1;
826 cpu_timer_switch(config.timers);
827 params.sleep_ms = 1000 * config.slideshow_delay + 0.5;
829 if (opts >= argc) {
831 if (!strcmp(config.backend_init, "help")) {
832 init_backend(config.backend_init);
833 return 0;
836 fprintf(stderr, "Requires path to at least one image\n\n");
837 print_help();
838 return 1;
841 signal(SIGINT, sighandler);
842 signal(SIGSEGV, sighandler);
843 signal(SIGBUS, sighandler);
844 signal(SIGABRT, sighandler);
846 if (init_loader(&params, (const char **)argv + opts))
847 return 1;
849 init_backend(config.backend_init);
851 if (config.emul_type != GP_PIXEL_UNKNOWN) {
852 backend = GP_BackendVirtualInit(backend, config.emul_type,
853 GP_BACKEND_CALL_EXIT);
856 pixmap = backend->pixmap;
858 black_pixel = GP_RGBToPixmapPixel(0x00, 0x00, 0x00, pixmap);
859 white_pixel = GP_RGBToPixmapPixel(0xff, 0xff, 0xff, pixmap);
860 gray_pixel = GP_RGBToPixmapPixel(0x33, 0x33, 0x33, pixmap);
862 GP_Fill(pixmap, black_pixel);
863 GP_BackendFlip(backend);
865 params.show_progress_once = 1;
866 show_image(&params);
868 if (params.sleep_ms) {
869 timer.expires = params.sleep_ms;
870 GP_BackendAddTimer(backend, &timer);
873 for (;;) {
874 GP_Event ev;
876 while (GP_BackendWaitEvent(backend, &ev)) {
878 shift_flag = GP_EventGetKey(&ev, GP_KEY_LEFT_SHIFT) ||
879 GP_EventGetKey(&ev, GP_KEY_RIGHT_SHIFT);
881 switch (ev.type) {
882 case GP_EV_REL:
883 switch (ev.code) {
884 case GP_EV_REL_WHEEL:
885 if (ev.val.val > 0)
886 goto next;
888 if (ev.val.val < 0)
889 goto prev;
890 break;
891 case GP_EV_REL_POS:
892 if (GP_EventGetKey(&ev, GP_BTN_LEFT))
893 set_zoom_offset(&params,
894 ev.val.rel.rx,
895 ev.val.rel.ry);
896 break;
898 break;
899 case GP_EV_KEY:
900 if (ev.code != GP_EV_KEY_DOWN)
901 continue;
903 switch (ev.val.key.key) {
904 case GP_KEY_H:
905 draw_help(backend);
906 show_image(&params);
907 break;
908 case GP_KEY_F:
909 if (GP_BackendIsX11(backend))
910 GP_BackendX11RequestFullscreen(backend, 2);
911 break;
912 case GP_KEY_I:
913 config.show_info = !config.show_info;
915 params.show_progress_once = 1;
916 show_image(&params);
917 break;
918 case GP_KEY_P:
919 config.show_progress = !config.show_progress;
920 break;
921 case GP_KEY_R:
922 config.orientation++;
924 if (config.orientation > ROTATE_270)
925 config.orientation = ROTATE_0;
927 params.show_progress_once = 1;
928 show_image(&params);
929 break;
930 case GP_KEY_E:
931 if (config.orientation > ROTATE_0)
932 config.orientation--;
933 else
934 config.orientation = ROTATE_270;
936 params.show_progress_once = 1;
937 show_image(&params);
938 break;
939 case GP_KEY_S:
940 if (params.sleep_ms) {
941 if (GP_BackendTimersInQueue(backend)) {
942 GP_BackendRemTimer(backend, &timer);
943 } else {
944 timer.expires = params.sleep_ms;
945 GP_BackendAddTimer(backend, &timer);
948 break;
949 case GP_KEY_RIGHT_BRACE:
950 params.resampling_method++;
952 if (params.resampling_method > GP_INTERP_MAX)
953 params.resampling_method = 0;
954 if (params.resampling_method == GP_INTERP_CUBIC)
955 params.resampling_method++;
956 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
957 params.use_low_pass = 0;
958 params.show_nn_first = 0;
959 } else {
960 params.use_low_pass = 1;
961 params.show_nn_first = 1;
964 params.show_progress_once = 1;
965 show_image(&params);
966 break;
967 case GP_KEY_LEFT_BRACE:
968 if (params.resampling_method == 0)
969 params.resampling_method = GP_INTERP_MAX;
970 else
971 params.resampling_method--;
972 if (params.resampling_method == GP_INTERP_CUBIC)
973 params.resampling_method--;
974 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
975 params.use_low_pass = 0;
976 params.show_nn_first = 0;
977 } else {
978 params.use_low_pass = 1;
979 params.show_nn_first = 1;
982 params.show_progress_once = 1;
983 show_image(&params);
984 break;
985 case GP_KEY_L:
986 params.use_low_pass = !params.use_low_pass;
988 params.show_progress_once = 1;
989 show_image(&params);
990 break;
991 case GP_KEY_C:
992 image_cache_drop(params.img_resized_cache);
993 image_loader_drop_cache();
994 break;
995 case GP_KEY_W:
996 config_win_toggle();
997 params.show_progress_once = 1;
998 show_image(&params);
999 break;
1000 case GP_KEY_U:
1001 config_upscale_toggle();
1002 params.show_progress_once = 1;
1003 show_image(&params);
1004 break;
1005 case GP_KEY_D:
1006 config_downscale_toggle();
1007 params.show_progress_once = 1;
1008 show_image(&params);
1009 break;
1010 case GP_KEY_ESC:
1011 case GP_KEY_ENTER:
1012 case GP_KEY_Q:
1013 stop_loader();
1014 image_cache_destroy(params.img_resized_cache);
1015 image_loader_destroy();
1016 GP_BackendExit(backend);
1017 return 0;
1018 break;
1019 case GP_KEY_PAGE_UP:
1020 params.show_progress_once = 1;
1021 image_seek(&params, IMG_DIR, -1);
1022 break;
1023 case GP_KEY_PAGE_DOWN:
1024 params.show_progress_once = 1;
1025 image_seek(&params, IMG_DIR, 1);
1026 break;
1027 case GP_KEY_HOME:
1028 params.show_progress_once = 1;
1029 image_seek(&params, IMG_FIRST, 0);
1030 break;
1031 case GP_KEY_END:
1032 params.show_progress_once = 1;
1033 image_seek(&params, IMG_LAST, 0);
1034 break;
1035 case GP_KEY_RIGHT:
1036 if (shift_flag)
1037 set_zoom_offset(&params, 1, 0);
1038 else
1039 set_zoom_offset(&params, 10, 0);
1041 break;
1042 case GP_KEY_LEFT:
1043 if (shift_flag)
1044 set_zoom_offset(&params, -1, 0);
1045 else
1046 set_zoom_offset(&params, -10, 0);
1047 break;
1048 case GP_KEY_UP:
1049 if (shift_flag)
1050 set_zoom_offset(&params, 0, -1);
1051 else
1052 set_zoom_offset(&params, 0, -10);
1053 break;
1054 case GP_KEY_DOWN:
1055 if (shift_flag)
1056 set_zoom_offset(&params, 0, 1);
1057 else
1058 set_zoom_offset(&params, 0, 10);
1059 break;
1060 next:
1061 case GP_KEY_SPACE:
1062 params.show_progress_once = 1;
1063 if (shift_flag)
1064 image_seek(&params, IMG_CUR, 10);
1065 else
1066 image_seek(&params, IMG_CUR, 1);
1067 break;
1068 prev:
1069 case GP_KEY_BACKSPACE:
1070 params.show_progress_once = 1;
1071 if (shift_flag)
1072 image_seek(&params, IMG_CUR, -10);
1073 else
1074 image_seek(&params, IMG_CUR, -1);
1075 break;
1076 case GP_KEY_1 ... GP_KEY_9: {
1077 float val = ev.val.key.key - GP_KEY_1 + 1;
1079 if (!shift_flag)
1080 val = 1/val;
1082 zoom_set(&params, val);
1083 } break;
1084 case GP_KEY_0:
1085 if (shift_flag)
1086 zoom_set(&params, 10);
1087 else
1088 zoom_set(&params, 0.1);
1089 break;
1090 case GP_KEY_KP_PLUS:
1091 case GP_KEY_DOT:
1092 params.show_progress_once = 1;
1093 if (shift_flag)
1094 zoom_mul(&params, 1.1);
1095 else
1096 zoom_mul(&params, 1.5);
1097 break;
1098 case GP_KEY_KP_MINUS:
1099 case GP_KEY_COMMA:
1100 params.show_progress_once = 1;
1101 if (shift_flag)
1102 zoom_mul(&params, 1/1.1);
1103 else
1104 zoom_mul(&params, 1/1.5);
1105 break;
1106 case GP_KEY_F1 ... GP_KEY_F10:
1107 image_action_run(ev.val.key.key - GP_KEY_F1 + 1,
1108 image_loader_img_path());
1109 break;
1111 break;
1112 case GP_EV_SYS:
1113 switch (ev.code) {
1114 case GP_EV_SYS_RESIZE:
1115 /* stop loader thread before resizing backend buffer */
1116 stop_loader();
1117 GP_BackendResizeAck(backend);
1118 GP_Fill(backend->pixmap, 0);
1119 params.show_progress_once = 1;
1120 show_image(&params);
1121 break;
1122 case GP_EV_SYS_QUIT:
1123 GP_BackendExit(backend);
1124 return 0;
1125 break;
1127 break;
1132 return 0;