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-2012 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
25 SPIV -- Simple but Powerfull Image Viewer.
36 #include <backends/GP_Backends.h>
37 #include <input/GP_InputDriverLinux.h>
39 #include "image_cache.h"
40 #include "cpu_timer.h"
42 static GP_Pixel black_pixel
;
43 static GP_Pixel white_pixel
;
45 static GP_Backend
*backend
= NULL
;
47 /* image loader thread */
48 static int abort_flag
= 0;
49 static int show_progress
= 0;
51 struct loader_params
{
52 /* current image path */
54 /* current resize ratio */
57 /* show loader progress */
59 long show_progress_once
:2;
60 /* show image info in topleft corner */
62 /* use nearest neighbour resampling first */
64 /* use dithering when blitting to display */
66 /* use low pass before resampling */
68 /* image orientation 0, 90, 180, 270 */
70 /* resampling method */
71 int resampling_method
;
73 /* caches for loaded images */
74 struct image_cache
*img_resized_cache
;
75 struct image_cache
*img_orig_cache
;
78 static int image_loader_callback(GP_ProgressCallback
*self
)
80 static GP_Size size
= 0;
81 GP_Context
*c
= backend
->context
;
91 snprintf(buf
, sizeof(buf
), "%s ... %-3.1f%%",
92 (const char*)self
->priv
, self
->percentage
);
94 int align
= GP_ALIGN_CENTER
|GP_VALIGN_ABOVE
;
96 GP_TextClear(c
, NULL
, c
->w
/2, c
->h
- 4, align
,
97 black_pixel
, GP_MAX(size
, GP_TextWidth(NULL
, buf
)));
99 GP_Text(c
, NULL
, c
->w
/2, c
->h
- 4, align
,
100 white_pixel
, black_pixel
, buf
);
102 size
= GP_TextWidth(NULL
, buf
);
104 GP_BackendUpdateRect(backend
, c
->w
/2 - size
/2 - 1, c
->h
- 4,
105 c
->w
/2 + size
/2 + 1, c
->h
- 4 - GP_TextHeight(NULL
));
111 static float calc_img_size(uint32_t img_w
, uint32_t img_h
,
112 uint32_t src_w
, uint32_t src_h
)
114 float w_rat
= 1.00 * src_w
/ img_w
;
115 float h_rat
= 1.00 * src_h
/ img_h
;
117 return GP_MIN(w_rat
, h_rat
);
120 static const char *img_name(const char *img_path
)
122 int i
, len
= strlen(img_path
);
124 for (i
= len
- 1; i
> 0; i
--) {
125 if (img_path
[i
] == '/')
126 return &img_path
[i
+1];
132 static void set_caption(const char *path
, float rat
)
136 snprintf(buf
, sizeof(buf
), "Spiv ~ %s 1:%3.3f", img_name(path
), rat
);
138 GP_BackendSetCaption(backend
, buf
);
144 GP_Context
*load_image(struct loader_params
*params
, int elevate
)
146 struct cpu_timer timer
;
147 GP_Context
*img
, *context
= backend
->context
;
149 GP_ProgressCallback callback
= {.callback
= image_loader_callback
,
150 .priv
= "Loading image"};
152 img
= image_cache_get(params
->img_orig_cache
, params
->img_path
, 0, 0, elevate
);
154 /* Image not cached, load it */
156 fprintf(stderr
, "Loading '%s'\n", params
->img_path
);
158 cpu_timer_start(&timer
, "Loading");
159 if ((img
= GP_LoadImage(params
->img_path
, &callback
)) == NULL
) {
161 if (errno
== ECANCELED
)
164 GP_Fill(context
, black_pixel
);
165 GP_Print(context
, NULL
, context
->w
/2, context
->h
/2,
166 GP_ALIGN_CENTER
|GP_VALIGN_CENTER
, white_pixel
, black_pixel
,
167 "Failed to load image :( (%s)", strerror(errno
));
168 GP_BackendFlip(backend
);
173 if (img
->pixel_type
!= GP_PIXEL_RGB888
) {
175 tmp
= GP_ContextConvertAlloc(img
, GP_PIXEL_RGB888
);
180 image_cache_put(params
->img_orig_cache
, img
, params
->img_path
, 0, 0);
182 cpu_timer_stop(&timer
);
191 static void update_display(struct loader_params
*params
, GP_Context
*img
)
193 GP_Context
*context
= backend
->context
;
194 struct cpu_timer timer
;
195 GP_ProgressCallback callback
= {.callback
= image_loader_callback
};
197 switch (params
->rotate
) {
201 callback
.priv
= "Rotating image (90)";
202 img
= GP_FilterRotate90_Alloc(img
, &callback
);
205 callback
.priv
= "Rotating image (180)";
206 img
= GP_FilterRotate180_Alloc(img
, &callback
);
209 callback
.priv
= "Rotating image (270)";
210 img
= GP_FilterRotate270_Alloc(img
, &callback
);
217 uint32_t cx
= (context
->w
- img
->w
)/2;
218 uint32_t cy
= (context
->h
- img
->h
)/2;
220 GP_Context sub_display
;
222 cpu_timer_start(&timer
, "Blitting");
224 if (params
->use_dithering
) {
225 callback
.priv
= "Dithering";
226 GP_SubContext(context
, &sub_display
, cx
, cy
, img
->w
, img
->h
);
227 GP_FilterFloydSteinberg_RGB888(img
, &sub_display
, NULL
);
228 // GP_FilterHilbertPeano_RGB888(img, &sub_display, NULL);
230 GP_Blit_Raw(img
, 0, 0, img
->w
, img
->h
, context
, cx
, cy
);
233 cpu_timer_stop(&timer
);
238 /* clean up the rest of the display */
239 GP_FillRectXYWH(context
, 0, 0, cx
, context
->h
, black_pixel
);
240 GP_FillRectXYWH(context
, 0, 0, context
->w
, cy
, black_pixel
);
241 GP_FillRectXYWH(context
, img
->w
+ cx
, 0, context
->w
- img
->w
- cx
, context
->h
, black_pixel
);
242 GP_FillRectXYWH(context
, 0, img
->h
+ cy
, context
->w
, context
->h
- img
->h
- cy
, black_pixel
);
244 set_caption(params
->img_path
, params
->rat
);
246 if (!params
->show_info
) {
247 GP_BackendFlip(backend
);
251 GP_Size th
= GP_TextHeight(NULL
);
253 GP_Print(context
, NULL
, 11, 11, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
254 black_pixel
, white_pixel
, "%ux%u", img
->w
, img
->h
);
256 GP_Print(context
, NULL
, 10, 10, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
257 white_pixel
, black_pixel
, "%ux%u", img
->w
, img
->h
);
259 GP_Print(context
, NULL
, 11, 13 + th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
260 black_pixel
, white_pixel
, "1:%3.3f", params
->rat
);
262 GP_Print(context
, NULL
, 10, 12 + th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
263 white_pixel
, black_pixel
, "1:%3.3f", params
->rat
);
265 GP_Print(context
, NULL
, 11, 15 + 2 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
266 black_pixel
, white_pixel
, "%s", img_name(params
->img_path
));
268 GP_Print(context
, NULL
, 10, 14 + 2 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
269 white_pixel
, black_pixel
, "%s", img_name(params
->img_path
));
271 GP_Print(context
, NULL
, 11, 17 + 3 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
272 black_pixel
, white_pixel
, "%s%s",
273 params
->use_low_pass
&& params
->rat
< 1 ? "Gaussian LP + " : "",
274 GP_InterpolationTypeName(params
->resampling_method
));
276 GP_Print(context
, NULL
, 10, 16 + 3 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
277 white_pixel
, black_pixel
, "%s%s",
278 params
->use_low_pass
&& params
->rat
< 1 ? "Gaussian LP + " : "",
279 GP_InterpolationTypeName(params
->resampling_method
));
281 GP_BackendFlip(backend
);
284 GP_Context
*load_resized_image(struct loader_params
*params
, GP_Size w
, GP_Size h
)
286 long cookie
= (w
& 0xffff) | (h
& 0xffff)<<16;
287 GP_Context
*img
, *res
= NULL
;
288 struct cpu_timer timer
;
289 GP_ProgressCallback callback
= {.callback
= image_loader_callback
};
291 int key
= (params
->resampling_method
<<1) | !!(params
->use_low_pass
);
293 /* Try to get resized cached image */
294 img
= image_cache_get(params
->img_resized_cache
, params
->img_path
, cookie
, key
, 1);
299 /* Otherwise load image and resize it */
300 if ((img
= load_image(params
, 1)) == NULL
)
303 if (params
->show_nn_first
) {
304 /* Do simple interpolation and blit the result */
305 GP_Context
*nn
= GP_FilterResizeNNAlloc(img
, w
, h
, NULL
);
307 update_display(params
, nn
);
312 /* Do low pass filter */
313 if (params
->use_low_pass
&& params
->rat
< 1) {
314 cpu_timer_start(&timer
, "Blur");
315 callback
.priv
= "Blurring Image";
317 res
= GP_FilterGaussianBlur(img
, NULL
,
318 0.3/params
->rat
, 0.3/params
->rat
,
325 cpu_timer_stop(&timer
);
328 // img->gamma = GP_GammaAcquire(img->pixel_type, 2.2);
330 cpu_timer_start(&timer
, "Resampling");
331 callback
.priv
= "Resampling Image";
332 GP_Context
*i1
= GP_FilterResize(img
, NULL
, params
->resampling_method
, w
, h
, &callback
);
333 // img->gamma = NULL;
334 // GP_Context *i2 = GP_FilterResize(img, NULL, params->resampling_method, w, h, &callback);
335 // img = GP_FilterDifferenceAlloc(i2, i1, NULL);
336 // img = GP_FilterInvert(img, NULL, NULL);
338 cpu_timer_stop(&timer
);
340 /* Free low passed context if needed */
346 image_cache_put(params
->img_resized_cache
, img
, params
->img_path
, cookie
, key
);
352 * This function tries to resize spiv window
353 * and if succedes blits the image directly to the screen.
355 static int resize_backend_and_blit(struct loader_params
*params
)
357 GP_Context
*img
= load_image(params
, 1);
359 if (GP_BackendResize(backend
, img
->w
, img
->h
))
362 GP_Blit_Raw(img
, 0, 0, img
->w
, img
->h
, backend
->context
, 0, 0);
363 GP_BackendFlip(backend
);
364 set_caption(params
->img_path
, 1);
369 static void *image_loader(void *ptr
)
371 struct loader_params
*params
= ptr
;
372 struct cpu_timer sum_timer
;
373 GP_Context
*img
, *context
= backend
->context
;
375 cpu_timer_start(&sum_timer
, "sum");
377 show_progress
= params
->show_progress
|| params
->show_progress_once
;
378 params
->show_progress_once
= 0;
380 /* Figure out rotation */
383 switch (params
->rotate
) {
397 if ((img
= load_image(params
, 0)) == NULL
)
400 params
->rat
= calc_img_size(img
->w
, img
->h
, w
, h
);
405 img
= load_resized_image(params
, w
* params
->rat
+ 0.5, h
* params
->rat
+ 0.5);
410 image_cache_print(params
->img_resized_cache
);
411 image_cache_print(params
->img_orig_cache
);
413 update_display(params
, img
);
415 cpu_timer_stop(&sum_timer
);
420 static pthread_t loader_thread
= (pthread_t
)0;
422 static void stop_loader(void)
426 pthread_join(loader_thread
, NULL
);
427 loader_thread
= (pthread_t
)0;
432 static void show_image(struct loader_params
*params
, const char *path
)
437 params
->img_path
= path
;
439 /* stop previous loader thread */
442 ret
= pthread_create(&loader_thread
, NULL
, image_loader
, (void*)params
);
445 fprintf(stderr
, "Failed to start thread: %s\n", strerror(ret
));
446 GP_BackendExit(backend
);
451 static void sighandler(int signo
)
454 GP_BackendExit(backend
);
456 fprintf(stderr
, "Got signal %i\n", signo
);
461 static void init_backend(const char *backend_opts
)
463 backend
= GP_BackendInit(backend_opts
, "Spiv", stderr
);
465 if (backend
== NULL
) {
466 fprintf(stderr
, "Failed to initalize backend '%s'\n", backend_opts
);
471 static int alarm_fired
= 0;
473 static void alarm_handler(int signo
)
478 static int wait_for_event(int sleep_msec
)
480 static int sleep_msec_count
= 0;
481 static int alarm_set
= 0;
483 if (sleep_msec
< 0) {
484 GP_BackendPoll(backend
);
489 /* We can't sleep on backend fd, because the backend doesn't export it. */
490 if (backend
->fd
< 0) {
491 GP_BackendPoll(backend
);
494 sleep_msec_count
+= 10;
496 if (sleep_msec_count
>= sleep_msec
) {
497 sleep_msec_count
= 0;
504 /* Initalize select */
508 FD_SET(backend
->fd
, &rfds
);
511 signal(SIGALRM
, alarm_handler
);
512 alarm(sleep_msec
/ 1000);
517 struct timeval tv
= {.tv_sec
= sleep_msec
/ 1000,
518 .tv_usec
= (sleep_msec
% 1000) * 1000};
520 int ret
= select(backend
->fd
+ 1, &rfds
, NULL
, NULL
, &tv
);
527 GP_BackendExit(backend
);
539 GP_BackendPoll(backend
);
544 static void init_caches(struct loader_params
*params
)
546 size_t size
= image_cache_get_ram_size();
547 unsigned int resized_size
= (1024 * size
)/10;
548 unsigned int orig_size
= (1024 * size
)/50;
550 if (resized_size
> 100 * 1024 * 1024)
551 resized_size
= 100 * 1024 * 1024;
553 if (orig_size
> 40 * 1024 * 1024)
554 orig_size
= 40 * 1024 * 1024;
556 GP_DEBUG(1, "Cache sizes original = %u, resized = %u",
557 orig_size
, resized_size
);
559 params
->img_resized_cache
= image_cache_create(resized_size
);
560 params
->img_orig_cache
= image_cache_create(orig_size
);
562 // params->img_resized_cache = NULL;
563 // params->img_orig_cache = NULL;
566 int main(int argc
, char *argv
[])
568 GP_Context
*context
= NULL
;
569 const char *backend_opts
= "X11";
571 int opt
, debug_level
= 0;
572 GP_PixelType emul_type
= GP_PIXEL_UNKNOWN
;
574 struct loader_params params
= {
578 .show_progress_once
= 0,
583 .resampling_method
= GP_INTERP_LINEAR_LF_INT
,
585 .img_resized_cache
= NULL
,
586 .img_orig_cache
= NULL
,
589 while ((opt
= getopt(argc
, argv
, "b:cd:e:fIPs:r:")) != -1) {
592 params
.show_info
= 1;
595 params
.show_progress
= 1;
598 params
.use_dithering
= 1;
601 sleep_sec
= atoi(optarg
);
604 params
.resampling_method
= GP_INTERP_CUBIC_INT
;
605 /* Cubic resampling needs low pass */
606 params
.use_low_pass
= 1;
607 /* Cubic resampling is slow, show nn first */
608 params
.show_nn_first
= 1;
611 debug_level
= atoi(optarg
);
614 emul_type
= GP_PixelTypeByName(optarg
);
616 if (emul_type
== GP_PIXEL_UNKNOWN
) {
617 fprintf(stderr
, "Invalid pixel type '%s'\n", optarg
);
622 if (!strcmp(optarg
, "90"))
624 else if (!strcmp(optarg
, "180"))
626 else if (!strcmp(optarg
, "270"))
629 backend_opts
= optarg
;
632 fprintf(stderr
, "Invalid paramter '%c'\n", opt
);
636 GP_SetDebugLevel(debug_level
);
638 signal(SIGINT
, sighandler
);
639 signal(SIGSEGV
, sighandler
);
640 signal(SIGBUS
, sighandler
);
641 signal(SIGABRT
, sighandler
);
643 init_caches(¶ms
);
645 init_backend(backend_opts
);
647 if (emul_type
!= GP_PIXEL_UNKNOWN
)
648 backend
= GP_BackendVirtualInit(backend
, emul_type
, GP_BACKEND_CALL_EXIT
);
650 context
= backend
->context
;
652 GP_EventSetScreenSize(context
->w
, context
->h
);
654 black_pixel
= GP_ColorToContextPixel(GP_COL_BLACK
, context
);
655 white_pixel
= GP_ColorToContextPixel(GP_COL_WHITE
, context
);
657 GP_Fill(context
, black_pixel
);
658 GP_BackendFlip(backend
);
663 params
.show_progress_once
= 1;
664 show_image(¶ms
, argv
[argf
]);
667 /* wait for event or a timeout */
668 if (wait_for_event(sleep_sec
* 1000)) {
673 show_image(¶ms
, argv
[argn
]);
676 /* Read and parse events */
679 while (GP_EventGet(&ev
)) {
681 // GP_EventDump(&ev);
685 if (ev
.code
!= GP_EV_KEY_DOWN
)
688 switch (ev
.val
.key
.key
) {
690 params
.show_info
= !params
.show_info
;
692 params
.show_progress_once
= 1;
693 show_image(¶ms
, NULL
);
696 params
.show_progress
= !params
.show_progress
;
700 if (params
.rotate
> 270)
703 params
.show_progress_once
= 1;
704 show_image(¶ms
, NULL
);
706 case GP_KEY_RIGHT_BRACE
:
707 params
.resampling_method
++;
709 if (params
.resampling_method
> GP_INTERP_MAX
)
710 params
.resampling_method
= 0;
712 if (params
.resampling_method
== GP_INTERP_LINEAR_LF_INT
) {
713 params
.use_low_pass
= 0;
714 params
.show_nn_first
= 0;
716 params
.use_low_pass
= 1;
717 params
.show_nn_first
= 1;
720 params
.show_progress_once
= 1;
721 show_image(¶ms
, argv
[argn
]);
723 case GP_KEY_LEFT_BRACE
:
724 if (params
.resampling_method
== 0)
725 params
.resampling_method
= GP_INTERP_MAX
;
727 params
.resampling_method
--;
729 if (params
.resampling_method
== GP_INTERP_LINEAR_LF_INT
) {
730 params
.use_low_pass
= 0;
731 params
.show_nn_first
= 0;
733 params
.use_low_pass
= 1;
734 params
.show_nn_first
= 1;
737 params
.show_progress_once
= 1;
738 show_image(¶ms
, argv
[argn
]);
741 params
.use_low_pass
= !params
.use_low_pass
;
743 params
.show_progress_once
= 1;
744 show_image(¶ms
, argv
[argn
]);
747 image_cache_drop(params
.img_resized_cache
);
748 image_cache_drop(params
.img_orig_cache
);
753 GP_BackendExit(backend
);
762 params
.show_progress_once
= 1;
763 show_image(¶ms
, argv
[argn
]);
765 case GP_KEY_PAGE_DOWN
:
771 params
.show_progress_once
= 1;
772 show_image(¶ms
, argv
[argn
]);
781 params
.show_progress_once
= 1;
782 show_image(¶ms
, argv
[argn
]);
784 case GP_KEY_BACKSPACE
:
792 params
.show_progress_once
= 1;
793 show_image(¶ms
, argv
[argn
]);
796 resize_backend_and_blit(¶ms
);
802 case GP_EV_SYS_RESIZE
:
803 /* stop loader thread before resizing backend buffer */
805 GP_BackendResize(backend
, ev
.val
.sys
.w
, ev
.val
.sys
.h
);
806 GP_Fill(backend
->context
, 0);
807 params
.show_progress_once
= 1;
808 show_image(¶ms
, NULL
);
811 GP_BackendExit(backend
);
820 GP_BackendExit(backend
);