1 /*****************************************************************************
2 * This file is part of gfxprim library. *
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. *
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. *
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 *
19 * Copyright (C) 2009-2013 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
25 SPIV -- Simple yet Powerful Image Viewer.
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 */
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 */
69 /* use low pass before resampling */
71 /* resampling method */
72 int resampling_method
;
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
;
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(NULL
, 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(NULL
) - 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
, NULL
, c
->w
/2, c
->h
- 5, align
,
110 white_pixel
, black_pixel
, buf
);
112 GP_BackendUpdateRect(backend
, start
, c
->h
- 1, end
, top
);
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];
131 static void set_caption(const char *path
, float rat
)
135 snprintf(buf
, sizeof(buf
), "Spiv ~ %s 1:%3.3f", img_name(path
), rat
);
137 GP_BackendSetCaption(backend
, buf
);
143 static GP_Context
*load_image(int elevate
)
146 GP_ProgressCallback callback
= {.callback
= image_loader_callback
,
147 .priv
= "Loading image"};
149 img
= image_loader_get_image(&callback
, elevate
);
154 GP_Context
*ctx
= backend
->context
;
156 GP_Fill(ctx
, black_pixel
);
157 GP_Print(ctx
, NULL
, 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
, NULL
, 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
);
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
)
176 GP_Pixel g1
= GP_RGBToContextPixel(0x64, 0x64, 0x64, ctx
);
177 GP_Pixel g2
= GP_RGBToContextPixel(0x80, 0x80, 0x80, ctx
);
179 unsigned int wm
= w
/10 < 10 ? 10 : w
/10;
180 unsigned int hm
= h
/10 < 10 ? 10 : h
/10;
181 unsigned int wt
= w
/20 < 5 ? 5 : w
/20;
182 unsigned int ht
= h
/20 < 5 ? 5 : h
/20;
184 for (y
= 0; y
< h
; y
++) {
185 for (x
= 0; x
< w
; x
++) {
188 if ((x
% wm
< wt
) ^ (y
% hm
< ht
))
193 GP_PutPixel(ctx
, x0
+ x
, y0
+ y
, pix
);
199 static void info_printf(GP_Context
*ctx
, GP_Coord x
, GP_Coord y
,
200 const char *fmt
, ...)
201 __attribute__ ((format (printf
, 4, 5)));
203 static void info_printf(GP_Context
*ctx
, GP_Coord x
, GP_Coord y
,
204 const char *fmt
, ...)
211 GP_VPrint(ctx
, NULL
, x
-1, y
-1, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
212 black_pixel
, white_pixel
, fmt
, vac
);
215 GP_VPrint(ctx
, NULL
, x
, y
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
216 white_pixel
, black_pixel
, fmt
, va
);
221 static void show_info(struct loader_params
*params
, GP_Context
*img
,
222 GP_Context
*orig_img
)
224 GP_Context
*context
= backend
->context
;
225 const char *img_path
= image_loader_img_path();
227 set_caption(img_path
, params
->zoom_rat
);
229 if (!config
.show_info
)
232 GP_Size th
= GP_TextHeight(NULL
), y
= 10;
234 info_printf(context
, 10, y
, "%ux%u (%ux%u) 1:%3.3f %3.1f%%",
235 img
->w
, img
->h
, orig_img
->w
, orig_img
->h
, params
->zoom_rat
,
236 params
->zoom_rat
* 100);
239 info_printf(context
, 10, y
, "%s", img_name(img_path
));
243 if (params
->zoom_rat
!= 1.00) {
244 info_printf(context
, 10, y
, "%s%s",
245 params
->use_low_pass
&& params
->zoom_rat
< 1 ? "Gaussian LP + " : "",
246 GP_InterpolationTypeName(params
->resampling_method
));
250 unsigned int count
= image_loader_count();
251 unsigned int pos
= image_loader_pos() + 1;
253 info_printf(context
, 10, y
, "%u of %u", pos
, count
);
256 if (!image_loader_is_in_dir())
259 unsigned int dir_count
= image_loader_dir_count();
260 unsigned int dir_pos
= image_loader_dir_pos() + 1;
262 info_printf(context
, 10, y
,
263 "%u of %u in directory", dir_pos
, dir_count
);
266 static void update_display(struct loader_params
*params
, GP_Context
*img
,
267 GP_Context
*orig_img
)
269 GP_Context
*context
= backend
->context
;
270 struct cpu_timer timer
;
271 GP_ProgressCallback callback
= {.callback
= image_loader_callback
};
273 switch (config
.orientation
) {
277 callback
.priv
= "Rotating image (90)";
278 img
= GP_FilterRotate90Alloc(img
, &callback
);
281 callback
.priv
= "Rotating image (180)";
282 img
= GP_FilterRotate180Alloc(img
, &callback
);
285 callback
.priv
= "Rotating image (270)";
286 img
= GP_FilterRotate270Alloc(img
, &callback
);
297 * Center the image, if window size is fixed and
298 * the image is smaller than window.
300 if (config
.win_strategy
== ZOOM_WIN_FIXED
) {
302 if (img
->w
< context
->w
)
303 cx
= (context
->w
- img
->w
)/2;
305 if (img
->h
< context
->h
)
306 cy
= (context
->h
- img
->h
)/2;
309 if (params
->zoom_manual
) {
310 cx
= params
->zoom_x_offset
;
311 cy
= params
->zoom_y_offset
;
314 GP_Context sub_display
;
316 cpu_timer_start(&timer
, "Blitting");
318 if (config
.floyd_steinberg
) {
319 callback
.priv
= "Dithering";
320 GP_SubContext(context
, &sub_display
, cx
, cy
, img
->w
, img
->h
);
321 GP_FilterFloydSteinberg(img
, &sub_display
, NULL
);
322 // GP_FilterHilbertPeano(img, &sub_display, NULL);
324 if (GP_PixelHasFlags(img
->pixel_type
, GP_PIXEL_HAS_ALPHA
))
325 pattern_fill(context
, cx
, cy
, img
->w
, img
->h
);
327 GP_Blit_Clipped(img
, 0, 0, img
->w
, img
->h
, context
, cx
, cy
);
330 cpu_timer_stop(&timer
);
332 /* clean up the rest of the display */
333 GP_FillRectXYWH(context
, 0, 0, cx
, context
->h
, black_pixel
);
334 GP_FillRectXYWH(context
, 0, 0, context
->w
, cy
, black_pixel
);
337 int w
= context
->w
- img
->w
- cx
;
340 GP_FillRectXYWH(context
, img
->w
+ cx
, 0, w
, context
->h
, black_pixel
);
342 int h
= context
->h
- img
->h
- cy
;
345 GP_FillRectXYWH(context
, 0, img
->h
+ cy
, context
->w
, h
, black_pixel
);
347 show_info(params
, img
, orig_img
);
349 if (config
.orientation
)
352 GP_BackendFlip(backend
);
355 GP_Context
*load_resized_image(struct loader_params
*params
, GP_Size w
, GP_Size h
)
357 GP_Context
*img
, *res
= NULL
;
358 struct cpu_timer timer
;
359 GP_ProgressCallback callback
= {.callback
= image_loader_callback
};
361 const char *img_path
= image_loader_img_path();
363 /* Try to get resized cached image */
364 img
= image_cache_get2(params
->img_resized_cache
, 1, "%s %ux%u r%i l%i",
365 img_path
, w
, h
, params
->resampling_method
,
366 params
->use_low_pass
);
371 /* Otherwise load image and resize it */
372 if ((img
= load_image(1)) == NULL
)
375 if (params
->show_nn_first
) {
376 /* Do simple interpolation and blit the result */
377 GP_Context
*nn
= GP_FilterResizeNNAlloc(img
, w
, h
, NULL
);
379 update_display(params
, nn
, img
);
384 /* Do low pass filter */
385 if (params
->use_low_pass
&& params
->zoom_rat
< 1) {
386 cpu_timer_start(&timer
, "Blur");
387 callback
.priv
= "Blurring Image";
389 res
= GP_FilterGaussianBlurAlloc(img
, 0.4/params
->zoom_rat
,
390 0.4/params
->zoom_rat
, &callback
);
397 cpu_timer_stop(&timer
);
400 // img->gamma = GP_GammaAcquire(img->pixel_type, 0.45);
402 cpu_timer_start(&timer
, "Resampling");
403 callback
.priv
= "Resampling Image";
404 GP_Context
*i1
= GP_FilterResizeAlloc(img
, w
, h
, params
->resampling_method
, &callback
);
406 cpu_timer_stop(&timer
);
409 if (params->zoom_rat > 1.5) {
410 cpu_timer_start(&timer, "Sharpening");
411 callback.priv = "Sharpening";
412 GP_FilterEdgeSharpening(i1, i1, 0.1, &callback);
413 cpu_timer_stop(&timer);
417 /* Free low passed context if needed */
423 image_cache_put2(params
->img_resized_cache
, img
, "%s %ux%u r%i l%i",
424 img_path
, w
, h
, params
->resampling_method
,
425 params
->use_low_pass
);
430 static float calc_img_size(struct loader_params
*params
,
431 uint32_t img_w
, uint32_t img_h
,
432 uint32_t win_w
, uint32_t win_h
)
434 float w_rat
, h_rat
, rat
;
435 unsigned int max_win_w
= config
.max_win_w
;
436 unsigned int max_win_h
= config
.max_win_h
;
438 switch (config
.orientation
) {
441 GP_SWAP(win_w
, win_h
);
442 GP_SWAP(max_win_w
, max_win_h
);
448 if (params
->zoom_manual
) {
449 if (config
.win_strategy
== ZOOM_WIN_RESIZABLE
) {
450 win_w
= GP_MIN(max_win_w
, img_w
* params
->zoom_rat
+ 0.5);
451 win_h
= GP_MIN(max_win_h
, img_h
* params
->zoom_rat
+ 0.5);
453 switch (config
.orientation
) {
456 GP_BackendResize(backend
, win_h
, win_w
);
459 GP_BackendResize(backend
, win_w
, win_h
);
462 return params
->zoom_rat
;
466 if (config
.win_strategy
== ZOOM_WIN_RESIZABLE
) {
467 win_w
= GP_MIN(max_win_w
, img_w
);
468 win_h
= GP_MIN(max_win_h
, img_h
);
471 * Image is larger than screen and downscale is enabled ->
472 * resize window to match image ratio.
474 if ((win_w
!= img_w
|| win_h
!= img_h
) &&
475 config
.zoom_strategy
& ZOOM_IMAGE_DOWNSCALE
) {
477 w_rat
= 1.00 * win_w
/ img_w
;
478 h_rat
= 1.00 * win_h
/ img_h
;
486 rat
= GP_MIN(h_rat
, w_rat
);
488 win_w
= rat
* img_w
+ 0.5;
489 win_h
= rat
* img_h
+ 0.5;
492 switch (config
.orientation
) {
495 GP_BackendResize(backend
, win_h
, win_w
);
498 GP_BackendResize(backend
, win_w
, win_h
);
502 if (img_w
<= win_w
&& img_h
<= win_h
) {
503 if (!(config
.zoom_strategy
& ZOOM_IMAGE_UPSCALE
))
506 if (!(config
.zoom_strategy
& ZOOM_IMAGE_DOWNSCALE
))
511 w_rat
= 1.00 * win_w
/ img_w
;
512 h_rat
= 1.00 * win_h
/ img_h
;
514 return GP_MIN(w_rat
, h_rat
);
517 static void *image_loader(void *ptr
)
519 struct loader_params
*params
= ptr
;
520 struct cpu_timer sum_timer
;
521 GP_Context
*img
, *orig_img
, *context
= backend
->context
;
523 cpu_timer_start(&sum_timer
, "sum");
525 show_progress
= config
.show_progress
|| params
->show_progress_once
;
526 params
->show_progress_once
= 0;
528 if ((orig_img
= load_image(0)) == NULL
) {
536 params
->zoom_rat
= calc_img_size(params
, orig_img
->w
, orig_img
->h
,
537 context
->w
, context
->h
);
539 w
= orig_img
->w
* params
->zoom_rat
+ 0.5;
540 h
= orig_img
->h
* params
->zoom_rat
+ 0.5;
542 /* Special case => no need to resize */
543 if (w
== orig_img
->w
&& h
== orig_img
->h
) {
548 img
= load_resized_image(params
, w
, h
);
556 update_display(params
, img
, orig_img
);
557 cpu_timer_stop(&sum_timer
);
564 static pthread_t loader_thread
= (pthread_t
)0;
566 static void stop_loader(void)
570 pthread_join(loader_thread
, NULL
);
571 loader_thread
= (pthread_t
)0;
576 static void show_image(struct loader_params
*params
)
580 /* stop previous loader thread */
585 ret
= pthread_create(&loader_thread
, NULL
, image_loader
, (void*)params
);
588 fprintf(stderr
, "Failed to start thread: %s\n", strerror(ret
));
589 GP_BackendExit(backend
);
594 static void image_seek(struct loader_params
*params
,
595 enum img_seek_offset offset
, int whence
)
598 * We need to stop loader first because image loader seek may free
599 * image we are currently resamling.
602 params
->zoom_manual
= 0;
603 image_loader_seek(offset
, whence
);
607 static void set_zoom_offset(struct loader_params
*params
, int dx
, int dy
)
609 params
->zoom_manual
= 1;
610 params
->zoom_x_offset
+= dx
;
611 params
->zoom_y_offset
+= dy
;
615 static void zoom_mul(struct loader_params
*params
, float mul
)
617 params
->zoom_manual
= 1;
618 params
->zoom_rat
*= mul
;
622 static void zoom_set(struct loader_params
*params
, float mul
)
624 params
->zoom_manual
= 1;
625 params
->zoom_rat
= mul
;
629 static void sighandler(int signo
)
632 GP_BackendExit(backend
);
634 fprintf(stderr
, "Got signal %i\n", signo
);
639 static void init_backend(const char *backend_opts
)
641 backend
= GP_BackendInit(backend_opts
, "Spiv", stderr
);
643 if (backend
== NULL
) {
644 fprintf(stderr
, "Failed to initalize backend '%s'\n", backend_opts
);
648 if (config
.full_screen
) {
649 if (GP_BackendIsX11(backend
))
650 GP_BackendX11RequestFullscreen(backend
, 2);
654 #define RESIZED_CACHE_MAX 400 * 1024
655 #define ORIG_CACHE_MAX 80 * 1024
658 * Figure out cache size depending on the size of RAM.
660 * Initialize cache, image loader.
662 static int init_loader(struct loader_params
*params
, const char **argv
)
664 size_t size
= image_cache_get_ram_size();
665 size_t resized_size
= size
/10;
666 size_t orig_size
= size
/50;
668 if (resized_size
> RESIZED_CACHE_MAX
)
669 resized_size
= RESIZED_CACHE_MAX
;
671 if (orig_size
> ORIG_CACHE_MAX
)
672 orig_size
= ORIG_CACHE_MAX
;
674 GP_DEBUG(1, "Resized cache size = %zukB", resized_size
);
675 GP_DEBUG(1, "Orig cache size = %zukB", orig_size
);
677 if (image_loader_init(argv
, orig_size
))
680 params
->img_resized_cache
= image_cache_create(resized_size
);
685 static uint32_t timer_callback(GP_Timer
*self
)
687 struct loader_params
*params
= self
->priv
;
688 static int retries
= 0;
691 * If loader is still running, reschedule after 20ms
693 * If more than two seconds has passed, try load next
695 if (loader_running
&& retries
< 100) {
696 printf("Loader still running %ims\n", 20 * retries
);
704 * We need to stop loader first because image loader seek may free
705 * image we are currently resamling.
708 image_loader_seek(IMG_CUR
, 1);
711 return params
->sleep_ms
;
714 int main(int argc
, char *argv
[])
716 GP_Context
*context
= NULL
;
720 struct loader_params params
= {
721 .show_progress_once
= 0,
723 .resampling_method
= GP_INTERP_LINEAR_LF_INT
,
725 .img_resized_cache
= NULL
,
730 GP_TIMER_DECLARE(timer
, 0, 0, "Slideshow", timer_callback
, ¶ms
);
732 if (access("/etc/spiv.conf", R_OK
) == 0)
733 spiv_config_load("/etc/spiv.conf");
735 if (getenv("HOME")) {
738 snprintf(buf
, sizeof(buf
), "%s/%s", getenv("HOME"), ".spiv");
740 if (access(buf
, R_OK
) == 0)
741 spiv_config_load(buf
);
744 opts
= spiv_config_parse_args(argc
, argv
);
751 cpu_timer_switch(config
.timers
);
752 params
.sleep_ms
= 1000 * config
.slideshow_delay
+ 0.5;
756 if (!strcmp(config
.backend_init
, "help")) {
757 init_backend(config
.backend_init
);
761 fprintf(stderr
, "Requires path to at least one image\n\n");
766 signal(SIGINT
, sighandler
);
767 signal(SIGSEGV
, sighandler
);
768 signal(SIGBUS
, sighandler
);
769 signal(SIGABRT
, sighandler
);
771 if (init_loader(¶ms
, (const char **)argv
+ opts
))
774 init_backend(config
.backend_init
);
776 if (config
.emul_type
!= GP_PIXEL_UNKNOWN
) {
777 backend
= GP_BackendVirtualInit(backend
, config
.emul_type
,
778 GP_BACKEND_CALL_EXIT
);
781 context
= backend
->context
;
783 black_pixel
= GP_ColorToContextPixel(GP_COL_BLACK
, context
);
784 white_pixel
= GP_ColorToContextPixel(GP_COL_WHITE
, context
);
785 gray_pixel
= GP_RGBToContextPixel(0x33, 0x33, 0x33, context
);
787 GP_Fill(context
, black_pixel
);
788 GP_BackendFlip(backend
);
790 params
.show_progress_once
= 1;
793 if (params
.sleep_ms
) {
794 timer
.expires
= params
.sleep_ms
;
795 GP_BackendAddTimer(backend
, &timer
);
801 while (GP_BackendWaitEvent(backend
, &ev
)) {
803 shift_flag
= GP_EventGetKey(&ev
, GP_KEY_LEFT_SHIFT
) ||
804 GP_EventGetKey(&ev
, GP_KEY_RIGHT_SHIFT
);
809 case GP_EV_REL_WHEEL
:
817 if (GP_EventGetKey(&ev
, GP_BTN_LEFT
))
818 set_zoom_offset(¶ms
,
825 if (ev
.code
!= GP_EV_KEY_DOWN
)
828 switch (ev
.val
.key
.key
) {
834 if (GP_BackendIsX11(backend
))
835 GP_BackendX11RequestFullscreen(backend
, 2);
838 config
.show_info
= !config
.show_info
;
840 params
.show_progress_once
= 1;
844 config
.show_progress
= !config
.show_progress
;
847 config
.orientation
++;
849 if (config
.orientation
> ROTATE_270
)
850 config
.orientation
= 0;
852 params
.show_progress_once
= 1;
856 if (config
.orientation
> 0)
857 config
.orientation
--;
859 config
.orientation
= ROTATE_270
;
861 params
.show_progress_once
= 1;
865 if (params
.sleep_ms
) {
866 if (GP_BackendTimersInQueue(backend
)) {
867 GP_BackendRemTimer(backend
, &timer
);
869 timer
.expires
= params
.sleep_ms
;
870 GP_BackendAddTimer(backend
, &timer
);
874 case GP_KEY_RIGHT_BRACE
:
875 params
.resampling_method
++;
877 if (params
.resampling_method
> GP_INTERP_MAX
)
878 params
.resampling_method
= 0;
879 if (params
.resampling_method
== GP_INTERP_CUBIC
)
880 params
.resampling_method
++;
881 if (params
.resampling_method
== GP_INTERP_LINEAR_LF_INT
) {
882 params
.use_low_pass
= 0;
883 params
.show_nn_first
= 0;
885 params
.use_low_pass
= 1;
886 params
.show_nn_first
= 1;
889 params
.show_progress_once
= 1;
892 case GP_KEY_LEFT_BRACE
:
893 if (params
.resampling_method
== 0)
894 params
.resampling_method
= GP_INTERP_MAX
;
896 params
.resampling_method
--;
897 if (params
.resampling_method
== GP_INTERP_CUBIC
)
898 params
.resampling_method
--;
899 if (params
.resampling_method
== GP_INTERP_LINEAR_LF_INT
) {
900 params
.use_low_pass
= 0;
901 params
.show_nn_first
= 0;
903 params
.use_low_pass
= 1;
904 params
.show_nn_first
= 1;
907 params
.show_progress_once
= 1;
911 params
.use_low_pass
= !params
.use_low_pass
;
913 params
.show_progress_once
= 1;
917 image_cache_drop(params
.img_resized_cache
);
918 image_loader_drop_cache();
922 params
.show_progress_once
= 1;
926 config_upscale_toggle();
927 params
.show_progress_once
= 1;
931 config_downscale_toggle();
932 params
.show_progress_once
= 1;
939 image_cache_destroy(params
.img_resized_cache
);
940 image_loader_destroy();
941 GP_BackendExit(backend
);
945 params
.show_progress_once
= 1;
946 image_seek(¶ms
, IMG_DIR
, -1);
948 case GP_KEY_PAGE_DOWN
:
949 params
.show_progress_once
= 1;
950 image_seek(¶ms
, IMG_DIR
, 1);
953 params
.show_progress_once
= 1;
954 image_seek(¶ms
, IMG_FIRST
, 0);
957 params
.show_progress_once
= 1;
958 image_seek(¶ms
, IMG_LAST
, 0);
962 set_zoom_offset(¶ms
, 1, 0);
964 set_zoom_offset(¶ms
, 10, 0);
969 set_zoom_offset(¶ms
, -1, 0);
971 set_zoom_offset(¶ms
, -10, 0);
975 set_zoom_offset(¶ms
, 0, -1);
977 set_zoom_offset(¶ms
, 0, -10);
981 set_zoom_offset(¶ms
, 0, 1);
983 set_zoom_offset(¶ms
, 0, 10);
987 params
.show_progress_once
= 1;
989 image_seek(¶ms
, IMG_CUR
, 10);
991 image_seek(¶ms
, IMG_CUR
, 1);
994 case GP_KEY_BACKSPACE
:
995 params
.show_progress_once
= 1;
997 image_seek(¶ms
, IMG_CUR
, -10);
999 image_seek(¶ms
, IMG_CUR
, -1);
1001 case GP_KEY_1
... GP_KEY_9
: {
1002 float val
= ev
.val
.key
.key
- GP_KEY_1
+ 1;
1007 zoom_set(¶ms
, val
);
1011 zoom_set(¶ms
, 10);
1013 zoom_set(¶ms
, 0.1);
1015 case GP_KEY_KP_PLUS
:
1017 params
.show_progress_once
= 1;
1019 zoom_mul(¶ms
, 1.1);
1021 zoom_mul(¶ms
, 1.5);
1023 case GP_KEY_KP_MINUS
:
1025 params
.show_progress_once
= 1;
1027 zoom_mul(¶ms
, 1/1.1);
1029 zoom_mul(¶ms
, 1/1.5);
1031 case GP_KEY_F1
... GP_KEY_F10
:
1032 image_action_run(ev
.val
.key
.key
- GP_KEY_F1
+ 1,
1033 image_loader_img_path());
1039 case GP_EV_SYS_RESIZE
:
1040 /* stop loader thread before resizing backend buffer */
1042 GP_BackendResizeAck(backend
);
1043 GP_Fill(backend
->context
, 0);
1044 params
.show_progress_once
= 1;
1045 show_image(¶ms
);
1047 case GP_EV_SYS_QUIT
:
1048 GP_BackendExit(backend
);