spiv: Cleanup the progress callback.
[gfxprim.git] / demos / spiv / spiv.c
blob5e0352e806c53be104c9666e0ec82118cacf83a8
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 but Powerfull 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>
36 #include <backends/GP_Backends.h>
37 #include <input/GP_InputDriverLinux.h>
39 #include "image_cache.h"
40 #include "image_list.h"
41 #include "image_actions.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;
54 enum zoom_type {
55 /*
56 * Resize image to fit current size of the window.
58 ZOOM_FIT,
60 /*
61 * Use zoom set in zoom float and zoom offsets.
63 ZOOM_FIXED,
65 /*
66 * Fixed zoom but spiv tries to change
67 * the window size to fit the image size
69 ZOOM_FIXED_WIN,
72 struct loader_params {
73 /* current image path */
74 const char *img_path;
75 /* current resize ratio */
76 float rat;
78 /* show loader progress */
79 long show_progress:1;
80 long show_progress_once:2;
81 /* show image info in topleft corner */
82 long show_info:3;
83 /* use nearest neighbour resampling first */
84 long show_nn_first:4;
85 /* use dithering when blitting to display */
86 long use_dithering:5;
87 /* use low pass before resampling */
88 long use_low_pass:6;
89 /* image orientation 0, 90, 180, 270 */
90 int rotate;
91 /* resampling method */
92 int resampling_method;
94 /* offset in pixels */
95 unsigned int zoom_x_offset;
96 unsigned int zoom_y_offset;
98 /* zoom */
99 enum zoom_type zoom_type;
100 float zoom;
102 /* caches for loaded images */
103 struct image_cache *img_resized_cache;
104 struct image_cache *img_orig_cache;
106 /* image list structure */
107 struct image_list *img_list;
110 static int image_loader_callback(GP_ProgressCallback *self)
112 static GP_Size size = 0;
113 GP_Context *c = backend->context;
115 if (abort_flag)
116 return 1;
118 if (!show_progress)
119 return 0;
121 char buf[100];
123 snprintf(buf, sizeof(buf), "%s ... %-3.1f%%",
124 (const char*)self->priv, self->percentage);
126 int align = GP_ALIGN_CENTER|GP_VALIGN_ABOVE;
128 size = GP_TextWidth(NULL, buf);
130 int start = c->w/2 - size/2 - 10;
131 int end = c->w/2 + size/2 + 10;
132 int middle = start + (end - start) * self->percentage / 100;
133 int top = c->h - GP_TextHeight(NULL) - 11;
135 GP_FillRectXYXY(c, start, c->h - 1, middle, top, gray_pixel);
136 GP_FillRectXYXY(c, middle, c->h - 1, end, top, black_pixel);
138 GP_Text(c, NULL, c->w/2, c->h - 5, align,
139 white_pixel, black_pixel, buf);
141 GP_BackendUpdateRect(backend, start, c->h - 1, end, top);
143 return 0;
146 static GP_Context *load_image(struct loader_params *params, int elevate);
149 * Ask backend to resize window may not be implemented or authorized. If
150 * backend (window) is resized we will get SYS_RESIZE event, see the main event
151 * loop.
153 static void resize_backend(struct loader_params *params, float ratio, int shift_flag)
155 GP_Context *img = load_image(params, 1);
157 if (!shift_flag)
158 ratio = 1.00 / ratio;
160 unsigned int w = img->w * ratio + 0.5;
161 unsigned int h = img->h * ratio + 0.5;
163 GP_BackendResize(backend, w, h);
167 static float calc_img_size(struct loader_params *params,
168 uint32_t img_w, uint32_t img_h,
169 uint32_t src_w, uint32_t src_h)
171 float w_rat;
172 float h_rat;
174 switch (params->zoom_type) {
175 case ZOOM_FIT:
176 w_rat = 1.00 * src_w / img_w;
177 h_rat = 1.00 * src_h / img_h;
178 return GP_MIN(w_rat, h_rat);
179 case ZOOM_FIXED:
180 return params->zoom;
181 case ZOOM_FIXED_WIN:
182 resize_backend(params, params->zoom, 0);
183 return params->zoom;
186 return 1;
189 static const char *img_name(const char *img_path)
191 int i, len = strlen(img_path);
193 for (i = len - 1; i > 0; i--) {
194 if (img_path[i] == '/')
195 return &img_path[i+1];
198 return img_path;
201 static void set_caption(const char *path, float rat)
203 char buf[256];
205 snprintf(buf, sizeof(buf), "Spiv ~ %s 1:%3.3f", img_name(path), rat);
207 GP_BackendSetCaption(backend, buf);
211 * Loads image
213 static GP_Context *load_image(struct loader_params *params, int elevate)
215 struct cpu_timer timer;
216 GP_Context *img, *context = backend->context;
218 GP_ProgressCallback callback = {.callback = image_loader_callback,
219 .priv = "Loading image"};
221 img = image_cache_get(params->img_orig_cache, params->img_path, 0, 0, elevate);
223 /* Image not cached, load it */
224 if (img == NULL) {
225 fprintf(stderr, "Loading '%s'\n", params->img_path);
227 cpu_timer_start(&timer, "Loading");
228 if ((img = GP_LoadImage(params->img_path, &callback)) == NULL) {
230 if (errno == ECANCELED)
231 return NULL;
233 GP_Fill(context, black_pixel);
234 GP_Print(context, NULL, context->w/2, context->h/2 - 10,
235 GP_ALIGN_CENTER|GP_VALIGN_CENTER, white_pixel, black_pixel,
236 "'%s'", params->img_path);
237 GP_Print(context, NULL, context->w/2, context->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);
241 return NULL;
244 /* Workaround */
245 // if (img->pixel_type != GP_PIXEL_RGB888) {
246 // GP_Context *tmp;
247 // tmp = GP_ContextConvertAlloc(img, GP_PIXEL_RGB888);
248 // GP_ContextFree(img);
249 // img = tmp;
250 // }
252 image_cache_put(params->img_orig_cache, img, params->img_path, 0, 0);
254 cpu_timer_stop(&timer);
257 return img;
261 * Fill context with chessboard-like pattern.
263 static void pattern_fill(GP_Context *ctx, unsigned int x0, unsigned int y0, unsigned int w, unsigned int h)
265 unsigned int x, y;
267 GP_Pixel g1 = GP_RGBToContextPixel(0x44, 0x44, 0x44, ctx);
268 GP_Pixel g2 = GP_RGBToContextPixel(0x33, 0x33, 0x33, ctx);
270 for (y = 0; y < h; y++) {
271 for (x = 0; x < w; x++) {
272 GP_Pixel pix;
274 if ((x%(w/10) < (w/20)) ^ (y%(h/10) < (h/20)))
275 pix = g1;
276 else
277 pix = g2;
279 GP_PutPixel(ctx, x0 + x, y0 + y, pix);
285 * Updates display.
287 static void update_display(struct loader_params *params, GP_Context *img)
289 GP_Context *context = backend->context;
290 struct cpu_timer timer;
291 GP_ProgressCallback callback = {.callback = image_loader_callback};
293 switch (params->rotate) {
294 case 0:
295 break;
296 case 90:
297 callback.priv = "Rotating image (90)";
298 img = GP_FilterRotate90Alloc(img, &callback);
299 break;
300 case 180:
301 callback.priv = "Rotating image (180)";
302 img = GP_FilterRotate180Alloc(img, &callback);
303 break;
304 case 270:
305 callback.priv = "Rotating image (270)";
306 img = GP_FilterRotate270Alloc(img, &callback);
307 break;
310 if (img == NULL)
311 return;
313 int cx = 0;
314 int cy = 0;
316 switch (params->zoom_type) {
317 case ZOOM_FIT:
318 cx = (context->w - img->w)/2;
319 cy = (context->h - img->h)/2;
320 break;
321 case ZOOM_FIXED:
322 case ZOOM_FIXED_WIN:
323 cx = params->zoom_x_offset;
324 cy = params->zoom_y_offset;
325 break;
328 GP_Context sub_display;
330 cpu_timer_start(&timer, "Blitting");
332 if (params->use_dithering) {
333 callback.priv = "Dithering";
334 GP_SubContext(context, &sub_display, cx, cy, img->w, img->h);
335 GP_FilterFloydSteinberg_RGB888(img, &sub_display, NULL);
336 // GP_FilterHilbertPeano_RGB888(img, &sub_display, NULL);
337 } else {
338 if (GP_PixelHasFlags(img->pixel_type, GP_PIXEL_HAS_ALPHA))
339 pattern_fill(context, cx, cy, img->w, img->h);
341 GP_Blit_Clipped(img, 0, 0, img->w, img->h, context, cx, cy);
344 cpu_timer_stop(&timer);
346 if (params->rotate)
347 GP_ContextFree(img);
349 /* clean up the rest of the display */
350 GP_FillRectXYWH(context, 0, 0, cx, context->h, black_pixel);
351 GP_FillRectXYWH(context, 0, 0, context->w, cy, black_pixel);
353 int w = context->w - img->w - cx;
355 if (w > 0)
356 GP_FillRectXYWH(context, img->w + cx, 0, w, context->h, black_pixel);
358 int h = context->h - img->h - cy;
360 if (h > 0)
361 GP_FillRectXYWH(context, 0, img->h + cy, context->w, h, black_pixel);
363 set_caption(params->img_path, params->rat);
365 if (!params->show_info) {
366 GP_BackendFlip(backend);
367 return;
370 GP_Size th = GP_TextHeight(NULL);
372 GP_Print(context, NULL, 11, 11, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
373 black_pixel, white_pixel, "%ux%u", img->w, img->h);
375 GP_Print(context, NULL, 10, 10, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
376 white_pixel, black_pixel, "%ux%u", img->w, img->h);
378 GP_Print(context, NULL, 11, 13 + th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
379 black_pixel, white_pixel, "1:%3.3f", params->rat);
381 GP_Print(context, NULL, 10, 12 + th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
382 white_pixel, black_pixel, "1:%3.3f", params->rat);
384 GP_Print(context, NULL, 11, 15 + 2 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
385 black_pixel, white_pixel, "%s", img_name(params->img_path));
387 GP_Print(context, NULL, 10, 14 + 2 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
388 white_pixel, black_pixel, "%s", img_name(params->img_path));
390 GP_Print(context, NULL, 11, 17 + 3 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
391 black_pixel, white_pixel, "%s%s",
392 params->use_low_pass && params->rat < 1 ? "Gaussian LP + " : "",
393 GP_InterpolationTypeName(params->resampling_method));
395 GP_Print(context, NULL, 10, 16 + 3 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
396 white_pixel, black_pixel, "%s%s",
397 params->use_low_pass && params->rat < 1 ? "Gaussian LP + " : "",
398 GP_InterpolationTypeName(params->resampling_method));
400 unsigned int count = image_list_count(params->img_list);
401 unsigned int pos = image_list_pos(params->img_list) + 1;
403 GP_Print(context, NULL, 11, 19 + 4 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
404 black_pixel, white_pixel, "%u of %u", pos, count);
406 GP_Print(context, NULL, 10, 18 + 4 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
407 white_pixel, black_pixel, "%u of %u", pos, count);
409 unsigned int dir_count = image_list_dir_count(params->img_list);
410 unsigned int dir_pos = image_list_dir_pos(params->img_list) + 1;
412 if (dir_count > 1 && dir_count != count) {
413 GP_Print(context, NULL, 11, 21 + 5 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
414 black_pixel, white_pixel, "%u of %u in directory", dir_pos, dir_count);
416 GP_Print(context, NULL, 10, 20 + 5 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
417 white_pixel, black_pixel, "%u of %u in directory", dir_pos, dir_count);
420 GP_BackendFlip(backend);
423 GP_Context *load_resized_image(struct loader_params *params, GP_Size w, GP_Size h)
425 long cookie = (w & 0xffff) | (h & 0xffff)<<16;
426 GP_Context *img, *res = NULL;
427 struct cpu_timer timer;
428 GP_ProgressCallback callback = {.callback = image_loader_callback};
430 int key = (params->resampling_method<<1) | !!(params->use_low_pass);
432 /* Try to get resized cached image */
433 img = image_cache_get(params->img_resized_cache, params->img_path, cookie, key, 1);
435 if (img != NULL)
436 return img;
438 /* Otherwise load image and resize it */
439 if ((img = load_image(params, 1)) == NULL)
440 return NULL;
442 if (params->show_nn_first) {
443 /* Do simple interpolation and blit the result */
444 GP_Context *nn = GP_FilterResizeNNAlloc(img, w, h, NULL);
445 if (nn != NULL) {
446 update_display(params, nn);
447 GP_ContextFree(nn);
451 /* Do low pass filter */
452 if (params->use_low_pass && params->rat < 1) {
453 cpu_timer_start(&timer, "Blur");
454 callback.priv = "Blurring Image";
456 res = GP_FilterGaussianBlurAlloc(img, 0.3/params->rat,
457 0.3/params->rat, &callback);
459 if (res == NULL)
460 return NULL;
462 img = res;
464 cpu_timer_stop(&timer);
467 // img->gamma = GP_GammaAcquire(img->pixel_type, 0.45);
469 cpu_timer_start(&timer, "Resampling");
470 callback.priv = "Resampling Image";
471 GP_Context *i1 = GP_FilterResize(img, NULL, params->resampling_method, w, h, &callback);
472 // img->gamma = NULL;
473 // GP_Context *i2 = GP_FilterResize(img, NULL, params->resampling_method, w, h, &callback);
474 // img = GP_FilterDifferenceAlloc(i2, i1, NULL);
475 // img = GP_FilterInvert(img, NULL, NULL);
476 img = i1;
477 // if (params->resampling_method == GP_INTERP_CUBIC_INT)
478 // GP_FilterEdgeSharpening(img, img, 0.2, NULL);
479 cpu_timer_stop(&timer);
482 if (params->rat > 1.5) {
483 cpu_timer_start(&timer, "Sharpening");
484 callback.priv = "Sharpening";
485 GP_FilterEdgeSharpening(i1, i1, 0.1, &callback);
486 cpu_timer_stop(&timer);
490 /* Free low passed context if needed */
491 GP_ContextFree(res);
493 if (img == NULL)
494 return NULL;
496 image_cache_put(params->img_resized_cache, img, params->img_path, cookie, key);
498 return img;
501 static void *image_loader(void *ptr)
503 struct loader_params *params = ptr;
504 struct cpu_timer sum_timer;
505 GP_Context *img, *context = backend->context;
507 cpu_timer_start(&sum_timer, "sum");
509 show_progress = params->show_progress || params->show_progress_once;
510 params->show_progress_once = 0;
512 /* Figure out rotation */
513 GP_Size w, h;
515 switch (params->rotate) {
516 case 0:
517 case 180:
518 default:
519 w = context->w;
520 h = context->h;
521 break;
522 case 90:
523 case 270:
524 w = context->h;
525 h = context->w;
526 break;
529 if ((img = load_image(params, 0)) == NULL)
530 return NULL;
532 params->rat = calc_img_size(params, img->w, img->h, w, h);
534 /* Special case => no need to resize */
535 if (params->rat == 1.0)
536 goto update;
538 w = img->w;
539 h = img->h;
541 img = load_resized_image(params, w * params->rat + 0.5, h * params->rat + 0.5);
543 if (img == NULL)
544 return NULL;
546 image_cache_print(params->img_resized_cache);
547 image_cache_print(params->img_orig_cache);
549 update:
550 update_display(params, img);
551 cpu_timer_stop(&sum_timer);
553 return NULL;
556 static pthread_t loader_thread = (pthread_t)0;
558 static void stop_loader(void)
560 if (loader_thread) {
561 abort_flag = 1;
562 pthread_join(loader_thread, NULL);
563 loader_thread = (pthread_t)0;
564 abort_flag = 0;
568 static void show_image(struct loader_params *params, const char *path)
570 int ret;
572 if (path != NULL)
573 params->img_path = path;
575 /* stop previous loader thread */
576 stop_loader();
578 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
580 if (ret) {
581 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
582 GP_BackendExit(backend);
583 exit(1);
587 static void set_zoom_offset(struct loader_params *params, int dx, int dy)
589 params->zoom_x_offset += dx;
590 params->zoom_y_offset += dy;
591 show_image(params, NULL);
594 static void zoom_mul(struct loader_params *params, float mul)
596 params->zoom *= mul;
597 show_image(params, NULL);
600 static void sighandler(int signo)
602 if (backend != NULL)
603 GP_BackendExit(backend);
605 fprintf(stderr, "Got signal %i\n", signo);
607 exit(1);
610 static void init_backend(const char *backend_opts)
612 backend = GP_BackendInit(backend_opts, "Spiv", stderr);
614 if (backend == NULL) {
615 fprintf(stderr, "Failed to initalize backend '%s'\n", backend_opts);
616 exit(1);
620 static int alarm_fired = 0;
622 static void alarm_handler(int signo)
624 alarm_fired = 1;
627 static int wait_for_event(int sleep_msec)
629 static int sleep_msec_count = 0;
630 static int alarm_set = 0;
632 if (sleep_msec < 0) {
633 GP_BackendPoll(backend);
634 usleep(10000);
635 return 0;
638 /* We can't sleep on backend fd, because the backend doesn't export it. */
639 if (backend->fd < 0) {
640 GP_BackendPoll(backend);
641 usleep(10000);
643 sleep_msec_count += 10;
645 if (sleep_msec_count >= sleep_msec) {
646 sleep_msec_count = 0;
647 return 1;
650 return 0;
653 /* Initalize select */
654 fd_set rfds;
655 FD_ZERO(&rfds);
657 FD_SET(backend->fd, &rfds);
659 if (!alarm_set) {
660 signal(SIGALRM, alarm_handler);
661 alarm(sleep_msec / 1000);
662 alarm_fired = 0;
663 alarm_set = 1;
666 struct timeval tv = {.tv_sec = sleep_msec / 1000,
667 .tv_usec = (sleep_msec % 1000) * 1000};
669 int ret = select(backend->fd + 1, &rfds, NULL, NULL, &tv);
671 switch (ret) {
672 case -1:
673 if (errno == EINTR)
674 return 1;
676 GP_BackendExit(backend);
677 exit(1);
678 break;
679 case 0:
680 if (alarm_fired) {
681 alarm_set = 0;
682 return 1;
685 return 0;
686 break;
687 default:
688 GP_BackendPoll(backend);
689 return 0;
693 static void init_caches(struct loader_params *params)
695 size_t size = image_cache_get_ram_size();
696 unsigned int resized_size = (1024 * size)/10;
697 unsigned int orig_size = (1024 * size)/50;
699 if (resized_size > 100 * 1024 * 1024)
700 resized_size = 100 * 1024 * 1024;
702 if (orig_size > 40 * 1024 * 1024)
703 orig_size = 40 * 1024 * 1024;
705 GP_DEBUG(1, "Cache sizes original = %u, resized = %u",
706 orig_size, resized_size);
708 params->img_resized_cache = image_cache_create(resized_size);
709 params->img_orig_cache = image_cache_create(orig_size);
711 // params->img_resized_cache = NULL;
712 // params->img_orig_cache = NULL;
715 static const char *keys_help[] = {
716 "Keyboard control:",
718 "Esc, Enter, Q - quit spiv",
720 "< KP Minus - zoom out by 1.5",
721 ">, KP Plus - zoom in by 1.5",
722 "R - rotate by 90 degrees clockwise",
723 "Up, Down, Left, Right - move image by 1px",
724 " (by 10 with Shift)",
726 "Space - move to the next image",
727 "BackSpace - move to the prev image",
728 "PgDown - move to the start of directory",
729 "PgUp - move to the end of directory",
730 "Home - move to the first image",
731 "End - move to the last image",
733 "I - toggle show info box",
734 "P - toggle show progress",
736 "] - change to next resampling method",
737 "[ - change to prev resampling method",
738 " (current method is shown in info box)",
739 "L - toggle low pass filter",
740 "D - drop image cache",
741 "H - toggle help",
743 "F1-F10 - execute action 0 - 9",
745 "1 - resize spiv window to the image size",
746 "1 - resize spiv window to the image size",
747 "2 - resize spiv window to the half of the image size",
748 "3 - resize spiv window to the third of the image size",
749 "...",
750 "9 - resize spiv window to the ninth of the image size",
752 "Shift 2 - resize spiv window twice of the image size",
753 "Shift 3 - resize spiv window three times of the image size",
754 "...",
757 static const int keys_help_len = sizeof(keys_help) / sizeof(char*);
759 static void print_help(void)
761 int i;
763 printf("Usage: spiv [opts] images\n\n");
764 printf("-I\n\tshow image info (filename and size)\n\n");
765 printf("-P\n\tshow loading progress\n\n");
766 printf("-f\n\tuse floyd-steinberg dithering\n\n");
767 printf("-s sec\n\tsleep interval in seconds\n\n");
768 printf("-c\n\tturns on bicubic resampling (experimental)\n\n");
769 printf("-d level\n\tsets GFXprim debug level\n\n");
770 printf("-e pixel_type\n\tturns on backend type emulation\n");
771 printf("\tfor example -e G1 sets 1-bit grayscale\n\n");
772 printf("-r angle\n\trotate display 90,180 or 270 degrees\n\n");
773 printf("-z sets zoom mode\n\t-zf zoom is set and modified by user\n");
774 printf("\t-zw zoom is fixed to window size (currently default)\n\n");
775 printf("-b\n\tpass backend init string to backend init\n");
776 printf("\tpass -b help for more info\n\n");
777 puts("");
778 printf("Actions\n");
779 printf("-0 'cmd' sets first action\n");
780 printf("-1 'cmd' sets second action\n");
781 printf("...\n");
782 printf("-9 'cmd' sets tenth action\n");
783 puts("");
784 printf(" actions are shell commands with following modifiers:\n");
785 printf(" %%f path to current image\n");
786 printf(" %%F shell escaped path to current image\n");
787 printf(" %%n current image filename without extension\n");
788 printf(" %%N shell escaped image filename without extension\n");
789 printf(" %%e current image file extension\n");
790 puts("");
792 for (i = 0; i < keys_help_len; i++)
793 puts(keys_help[i]);
795 puts("");
797 printf("Some cool options to try:\n\n");
798 printf("spiv -0 'cp %%F sorted/");
799 printf("\tcopies current image into directory 'sorted/' on F1\n");
800 printf("spiv -e G1 -f images\n");
801 printf("\truns spiv in 1-bit bitmap mode and turns on dithering\n\n");
802 printf("spiv -b 'X11:ROOT_WIN' images\n");
803 printf("\truns spiv using X root window as backend window\n\n");
804 printf("spiv -b 'X11:CREATE_ROOT' images\n");
805 printf("\tSame as abowe but works in KDE\n");
808 static void show_help(void)
810 GP_Context *c = backend->context;
811 int i;
813 GP_Fill(c, black_pixel);
815 for (i = 0; i < keys_help_len; i++) {
816 GP_Print(c, NULL, 20, 2 + i * 15, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
817 white_pixel, black_pixel, "%s", keys_help[i]);
820 GP_BackendFlip(backend);
823 int main(int argc, char *argv[])
825 GP_Context *context = NULL;
826 const char *backend_opts = "X11";
827 int sleep_sec = -1;
828 int opt, debug_level = 0;
829 int shift_flag, help_flag = 0;
830 GP_PixelType emul_type = GP_PIXEL_UNKNOWN;
832 struct loader_params params = {
833 .img_path = NULL,
835 .show_progress = 0,
836 .show_progress_once = 0,
837 .show_info = 0,
838 .show_nn_first = 0,
839 .use_dithering = 0,
840 .rotate = 0,
841 .resampling_method = GP_INTERP_LINEAR_LF_INT,
843 .zoom_type = ZOOM_FIT,
844 .zoom = 1,
846 .img_resized_cache = NULL,
847 .img_orig_cache = NULL,
850 while ((opt = getopt(argc, argv, "b:cd:e:fhIPs:r:z:0:1:2:3:4:5:6:7:8:9:")) != -1) {
851 switch (opt) {
852 case 'I':
853 params.show_info = 1;
854 break;
855 case 'P':
856 params.show_progress = 1;
857 break;
858 case 'f':
859 params.use_dithering = 1;
860 break;
861 case 's':
862 sleep_sec = atoi(optarg);
863 break;
864 case 'c':
865 params.resampling_method = GP_INTERP_CUBIC_INT;
866 /* Cubic resampling needs low pass */
867 params.use_low_pass = 1;
868 /* Cubic resampling is slow, show nn first */
869 params.show_nn_first = 1;
870 break;
871 case 'd':
872 debug_level = atoi(optarg);
873 break;
874 case 'e':
875 emul_type = GP_PixelTypeByName(optarg);
877 if (emul_type == GP_PIXEL_UNKNOWN) {
878 fprintf(stderr, "Invalid pixel type '%s'\n", optarg);
879 return 1;
881 break;
882 case 'r':
883 if (!strcmp(optarg, "90"))
884 params.rotate = 90;
885 else if (!strcmp(optarg, "180"))
886 params.rotate = 180;
887 else if (!strcmp(optarg, "270"))
888 params.rotate = 270;
889 case 'b':
890 backend_opts = optarg;
891 break;
892 case 'h':
893 print_help();
894 exit(0);
895 break;
896 case 'z':
897 switch (optarg[0]) {
898 case 'f':
899 params.zoom_type = ZOOM_FIXED;
900 break;
901 case 'w':
902 params.zoom_type = ZOOM_FIXED_WIN;
903 break;
905 break;
906 case '0' ... '9':
907 image_action_set(opt - '0', optarg);
908 break;
909 default:
910 fprintf(stderr, "Invalid paramter '%c'\n", opt);
911 print_help();
912 return 1;
916 if (optind >= argc) {
917 fprintf(stderr, "Requires path to at least one image\n\n");
918 print_help();
919 return 1;
922 GP_SetDebugLevel(debug_level);
924 signal(SIGINT, sighandler);
925 signal(SIGSEGV, sighandler);
926 signal(SIGBUS, sighandler);
927 signal(SIGABRT, sighandler);
929 init_caches(&params);
931 init_backend(backend_opts);
933 if (emul_type != GP_PIXEL_UNKNOWN)
934 backend = GP_BackendVirtualInit(backend, emul_type, GP_BACKEND_CALL_EXIT);
936 context = backend->context;
938 black_pixel = GP_ColorToContextPixel(GP_COL_BLACK, context);
939 white_pixel = GP_ColorToContextPixel(GP_COL_WHITE, context);
940 gray_pixel = GP_RGBToContextPixel(0x33, 0x33, 0x33, context);
942 GP_Fill(context, black_pixel);
943 GP_BackendFlip(backend);
945 struct image_list *list = image_list_create((const char**)argv + optind);
946 params.img_list = list;
948 params.show_progress_once = 1;
949 show_image(&params, image_list_img_path(list));
951 for (;;) {
952 /* wait for event or a timeout */
953 if (wait_for_event(sleep_sec * 1000))
954 show_image(&params, image_list_move(list, 1));
956 /* Read and parse events */
957 GP_Event ev;
959 while (GP_BackendGetEvent(backend, &ev)) {
961 shift_flag = GP_EventGetKey(&ev, GP_KEY_LEFT_SHIFT) ||
962 GP_EventGetKey(&ev, GP_KEY_RIGHT_SHIFT);
964 switch (ev.type) {
965 case GP_EV_REL:
966 switch (ev.code) {
967 case GP_EV_REL_WHEEL:
968 if (ev.val.val > 0)
969 goto next;
971 if (ev.val.val < 0)
972 goto prev;
973 break;
974 case GP_EV_REL_POS:
975 if (GP_EventGetKey(&ev, GP_BTN_LEFT))
976 set_zoom_offset(&params,
977 ev.val.rel.rx,
978 ev.val.rel.ry);
979 break;
981 break;
982 case GP_EV_KEY:
983 if (ev.code != GP_EV_KEY_DOWN)
984 continue;
986 switch (ev.val.key.key) {
987 case GP_KEY_H:
988 if (help_flag)
989 show_image(&params, NULL);
990 else
991 show_help();
993 //TODO: remove help_flag on any other action done
994 help_flag = !help_flag;
995 break;
996 case GP_KEY_F:
997 if (GP_BackendIsX11(backend))
998 GP_BackendX11RequestFullscreen(backend, 2);
999 break;
1000 case GP_KEY_I:
1001 params.show_info = !params.show_info;
1003 params.show_progress_once = 1;
1004 show_image(&params, NULL);
1005 break;
1006 case GP_KEY_P:
1007 params.show_progress = !params.show_progress;
1008 break;
1009 case GP_KEY_R:
1010 params.rotate += 90;
1011 if (params.rotate > 270)
1012 params.rotate = 0;
1014 params.show_progress_once = 1;
1015 show_image(&params, NULL);
1016 break;
1017 case GP_KEY_RIGHT_BRACE:
1018 params.resampling_method++;
1020 if (params.resampling_method > GP_INTERP_MAX)
1021 params.resampling_method = 0;
1023 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
1024 params.use_low_pass = 0;
1025 params.show_nn_first = 0;
1026 } else {
1027 params.use_low_pass = 1;
1028 params.show_nn_first = 1;
1031 params.show_progress_once = 1;
1032 show_image(&params, image_list_img_path(list));
1033 break;
1034 case GP_KEY_LEFT_BRACE:
1035 if (params.resampling_method == 0)
1036 params.resampling_method = GP_INTERP_MAX;
1037 else
1038 params.resampling_method--;
1040 if (params.resampling_method == GP_INTERP_LINEAR_LF_INT) {
1041 params.use_low_pass = 0;
1042 params.show_nn_first = 0;
1043 } else {
1044 params.use_low_pass = 1;
1045 params.show_nn_first = 1;
1048 params.show_progress_once = 1;
1049 show_image(&params, image_list_img_path(list));
1050 break;
1051 case GP_KEY_L:
1052 params.use_low_pass = !params.use_low_pass;
1054 params.show_progress_once = 1;
1055 show_image(&params, image_list_img_path(list));
1056 break;
1057 case GP_KEY_D:
1058 image_cache_drop(params.img_resized_cache);
1059 image_cache_drop(params.img_orig_cache);
1060 break;
1061 case GP_KEY_ESC:
1062 case GP_KEY_ENTER:
1063 case GP_KEY_Q:
1064 image_cache_drop(params.img_resized_cache);
1065 image_cache_drop(params.img_orig_cache);
1066 GP_BackendExit(backend);
1067 return 0;
1068 break;
1069 case GP_KEY_PAGE_UP:
1070 params.show_progress_once = 1;
1071 show_image(&params, image_list_dir_move(list, -1));
1072 break;
1073 case GP_KEY_PAGE_DOWN:
1074 params.show_progress_once = 1;
1075 show_image(&params, image_list_dir_move(list, 1));
1076 break;
1077 case GP_KEY_HOME:
1078 params.show_progress_once = 1;
1079 show_image(&params, image_list_first(list));
1080 break;
1081 case GP_KEY_END:
1082 params.show_progress_once = 1;
1083 show_image(&params, image_list_last(list));
1084 break;
1085 case GP_KEY_RIGHT:
1086 if (shift_flag)
1087 set_zoom_offset(&params, 1, 0);
1088 else
1089 set_zoom_offset(&params, 10, 0);
1091 break;
1092 case GP_KEY_LEFT:
1093 if (shift_flag)
1094 set_zoom_offset(&params, -1, 0);
1095 else
1096 set_zoom_offset(&params, -10, 0);
1097 break;
1098 case GP_KEY_UP:
1099 if (shift_flag)
1100 set_zoom_offset(&params, 0, -1);
1101 else
1102 set_zoom_offset(&params, 0, -10);
1103 break;
1104 case GP_KEY_DOWN:
1105 if (shift_flag)
1106 set_zoom_offset(&params, 0, 1);
1107 else
1108 set_zoom_offset(&params, 0, 10);
1109 break;
1110 next:
1111 case GP_KEY_SPACE:
1112 params.show_progress_once = 1;
1113 if (shift_flag)
1114 show_image(&params, image_list_move(list, 10));
1115 else
1116 show_image(&params, image_list_move(list, 1));
1117 break;
1118 prev:
1119 case GP_KEY_BACKSPACE:
1120 params.show_progress_once = 1;
1121 if (shift_flag)
1122 show_image(&params, image_list_move(list, -10));
1123 else
1124 show_image(&params, image_list_move(list, -1));
1125 break;
1126 case GP_KEY_1:
1127 resize_backend(&params, 1, shift_flag);
1128 break;
1129 case GP_KEY_2:
1130 resize_backend(&params, 2, shift_flag);
1131 break;
1132 case GP_KEY_3:
1133 resize_backend(&params, 3, shift_flag);
1134 break;
1135 case GP_KEY_4:
1136 resize_backend(&params, 4, shift_flag);
1137 break;
1138 case GP_KEY_5:
1139 resize_backend(&params, 5, shift_flag);
1140 break;
1141 case GP_KEY_6:
1142 resize_backend(&params, 6, shift_flag);
1143 break;
1144 case GP_KEY_7:
1145 resize_backend(&params, 7, shift_flag);
1146 break;
1147 case GP_KEY_8:
1148 resize_backend(&params, 8, shift_flag);
1149 break;
1150 case GP_KEY_9:
1151 resize_backend(&params, 9, shift_flag);
1152 break;
1153 case GP_KEY_KP_PLUS:
1154 case GP_KEY_DOT:
1155 params.show_progress_once = 1;
1156 zoom_mul(&params, 1.5);
1157 break;
1158 case GP_KEY_KP_MINUS:
1159 case GP_KEY_COMMA:
1160 params.show_progress_once = 1;
1161 zoom_mul(&params, 1/1.5);
1162 break;
1163 case GP_KEY_F1 ... GP_KEY_F10:
1164 image_action_run(ev.val.key.key - GP_KEY_F1,
1165 image_list_img_path(list));
1166 break;
1168 break;
1169 case GP_EV_SYS:
1170 switch (ev.code) {
1171 case GP_EV_SYS_RESIZE:
1172 /* stop loader thread before resizing backend buffer */
1173 stop_loader();
1174 GP_BackendResizeAck(backend);
1175 GP_Fill(backend->context, 0);
1176 params.show_progress_once = 1;
1177 show_image(&params, NULL);
1178 break;
1179 case GP_EV_SYS_QUIT:
1180 GP_BackendExit(backend);
1181 return 0;
1182 break;
1184 break;
1189 GP_BackendExit(backend);
1191 return 0;