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 rotate
= 0;
50 static int show_progress
= 0;
51 static int resampling_method
= GP_INTERP_LINEAR_LF_INT
;
52 static int dithering
= 0;
54 static int image_loader_callback(GP_ProgressCallback
*self
)
56 static GP_Size size
= 0;
57 GP_Context
*c
= backend
->context
;
67 snprintf(buf
, sizeof(buf
), "%s ... %-3.1f%%",
68 (const char*)self
->priv
, self
->percentage
);
70 int align
= GP_ALIGN_CENTER
|GP_VALIGN_ABOVE
;
72 GP_TextClear(c
, NULL
, c
->w
/2, c
->h
- 4, align
,
73 black_pixel
, GP_MAX(size
, GP_TextWidth(NULL
, buf
)));
75 GP_Text(c
, NULL
, c
->w
/2, c
->h
- 4, align
,
76 white_pixel
, black_pixel
, buf
);
78 size
= GP_TextWidth(NULL
, buf
);
80 GP_BackendUpdateRect(backend
, c
->w
/2 - size
/2 - 1, c
->h
- 4,
81 c
->w
/2 + size
/2 + 1, c
->h
- 4 - GP_TextHeight(NULL
));
86 struct loader_params
{
90 int show_progress_once
;
93 /* cached loaded images */
94 struct image_cache
*img_resized_cache
;
95 struct image_cache
*img_orig_cache
;
98 static float calc_img_size(uint32_t img_w
, uint32_t img_h
,
99 uint32_t src_w
, uint32_t src_h
)
101 float w_rat
= 1.00 * src_w
/ img_w
;
102 float h_rat
= 1.00 * src_h
/ img_h
;
104 return GP_MIN(w_rat
, h_rat
);
107 static const char *img_name(const char *img_path
)
109 int i
, len
= strlen(img_path
);
111 for (i
= len
- 1; i
> 0; i
--) {
112 if (img_path
[i
] == '/')
113 return &img_path
[i
+1];
119 static void set_caption(const char *path
, float rat
)
123 snprintf(buf
, sizeof(buf
), "Spiv ~ %s 1:%3.3f", img_name(path
), rat
);
125 GP_BackendSetCaption(backend
, buf
);
131 GP_Context
*load_image(struct loader_params
*params
, int elevate
)
133 struct cpu_timer timer
;
134 GP_Context
*img
, *context
= backend
->context
;
136 GP_ProgressCallback callback
= {.callback
= image_loader_callback
,
137 .priv
= "Loading image"};
139 img
= image_cache_get(params
->img_orig_cache
, params
->img_path
, 0, 0, elevate
);
141 /* Image not cached, load it */
143 fprintf(stderr
, "Loading '%s'\n", params
->img_path
);
145 cpu_timer_start(&timer
, "Loading");
146 if ((img
= GP_LoadImage(params
->img_path
, &callback
)) == NULL
) {
147 GP_Fill(context
, black_pixel
);
148 GP_Print(context
, NULL
, context
->w
/2, context
->h
/2,
149 GP_ALIGN_CENTER
|GP_VALIGN_CENTER
, white_pixel
, black_pixel
,
150 "Failed to load image :( (%s)", strerror(errno
));
151 GP_BackendFlip(backend
);
156 if (img
->pixel_type
!= GP_PIXEL_RGB888
) {
157 GP_Context
*tmp
= GP_ContextConvert(img
, GP_PIXEL_RGB888
);
162 image_cache_put(params
->img_orig_cache
, img
, params
->img_path
, 0, 0);
164 cpu_timer_stop(&timer
);
170 GP_Context
*load_resized_image(struct loader_params
*params
, GP_Size w
, GP_Size h
, float rat
)
172 long cookie
= (w
& 0xffff) | (h
& 0xffff)<<16;
173 GP_Context
*img
, *res
= NULL
;
174 struct cpu_timer timer
;
175 GP_ProgressCallback callback
= {.callback
= image_loader_callback
};
177 /* Try to get resized cached image */
178 img
= image_cache_get(params
->img_resized_cache
, params
->img_path
, cookie
, resampling_method
, 1);
183 /* Otherwise load image and resize it */
184 if ((img
= load_image(params
, 1)) == NULL
)
187 /* Do low pass filter */
188 if (resampling_method
!= GP_INTERP_LINEAR_LF_INT
&& rat
< 1) {
189 cpu_timer_start(&timer
, "Blur");
190 callback
.priv
= "Blurring Image";
192 res
= GP_FilterGaussianBlur(img
, NULL
, 0.4/rat
, 0.4/rat
, &callback
);
199 cpu_timer_stop(&timer
);
202 cpu_timer_start(&timer
, "Resampling");
203 callback
.priv
= "Resampling Image";
204 img
= GP_FilterResize(img
, NULL
, resampling_method
, w
, h
, &callback
);
205 cpu_timer_stop(&timer
);
207 /* Free low passed context if needed */
213 image_cache_put(params
->img_resized_cache
, img
, params
->img_path
, cookie
, resampling_method
);
219 * This function tries to resize spiv window
220 * and if succedes blits the image directly to the screen.
222 static int resize_backend_and_blit(struct loader_params
*params
)
224 GP_Context
*img
= load_image(params
, 1);
226 if (GP_BackendResize(backend
, img
->w
, img
->h
))
229 GP_Blit_Raw(img
, 0, 0, img
->w
, img
->h
, backend
->context
, 0, 0);
230 GP_BackendFlip(backend
);
231 set_caption(params
->img_path
, 1);
236 static void *image_loader(void *ptr
)
238 struct loader_params
*params
= ptr
;
239 struct cpu_timer timer
, sum_timer
;
240 GP_Context
*img
, *context
= backend
->context
;
241 GP_ProgressCallback callback
= {.callback
= image_loader_callback
};
243 cpu_timer_start(&sum_timer
, "sum");
245 show_progress
= params
->show_progress
|| params
->show_progress_once
;
246 params
->show_progress_once
= 0;
248 /* Figure out rotation */
265 if ((img
= load_image(params
, 0)) == NULL
)
268 float rat
= calc_img_size(img
->w
, img
->h
, w
, h
);
273 img
= load_resized_image(params
, w
* rat
+ 0.5, h
* rat
+ 0.5, rat
);
278 image_cache_print(params
->img_resized_cache
);
279 image_cache_print(params
->img_orig_cache
);
285 callback
.priv
= "Rotating image (90)";
286 img
= GP_FilterRotate90_Alloc(img
, &callback
);
289 callback
.priv
= "Rotating image (180)";
290 img
= GP_FilterRotate180_Alloc(img
, &callback
);
293 callback
.priv
= "Rotating image (270)";
294 img
= GP_FilterRotate270_Alloc(img
, &callback
);
301 uint32_t cx
= (context
->w
- img
->w
)/2;
302 uint32_t cy
= (context
->h
- img
->h
)/2;
304 GP_Context sub_display
;
306 cpu_timer_start(&timer
, "Blitting");
309 callback
.priv
= "Dithering";
310 GP_ContextSubContext(context
, &sub_display
, cx
, cy
, img
->w
, img
->h
);
311 // GP_FilterFloydSteinberg_RGB888(ret, &sub_display, NULL);
312 GP_FilterHilbertPeano_RGB888(img
, &sub_display
, NULL
);
314 GP_Blit_Raw(img
, 0, 0, img
->w
, img
->h
, context
, cx
, cy
);
317 cpu_timer_stop(&timer
);
322 /* clean up the rest of the display */
323 GP_FillRectXYWH(context
, 0, 0, cx
, context
->h
, black_pixel
);
324 GP_FillRectXYWH(context
, 0, 0, context
->w
, cy
, black_pixel
);
325 GP_FillRectXYWH(context
, img
->w
+ cx
, 0, context
->w
- img
->w
- cx
, context
->h
, black_pixel
);
326 GP_FillRectXYWH(context
, 0, img
->h
+ cy
, context
->w
, context
->h
- img
->h
- cy
, black_pixel
);
328 cpu_timer_stop(&sum_timer
);
330 set_caption(params
->img_path
, rat
);
332 if (!params
->show_info
) {
333 GP_BackendFlip(backend
);
337 GP_Size th
= GP_TextHeight(NULL
);
339 GP_Print(context
, NULL
, 11, 11, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
340 black_pixel
, white_pixel
, "%ux%u", w
, h
);
342 GP_Print(context
, NULL
, 10, 10, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
343 white_pixel
, black_pixel
, "%ux%u", w
, h
);
345 GP_Print(context
, NULL
, 11, 13 + th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
346 black_pixel
, white_pixel
, "1:%3.3f", rat
);
348 GP_Print(context
, NULL
, 10, 12 + th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
349 white_pixel
, black_pixel
, "1:%3.3f", rat
);
351 GP_Print(context
, NULL
, 11, 15 + 2 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
352 black_pixel
, white_pixel
, "%s", img_name(params
->img_path
));
354 GP_Print(context
, NULL
, 10, 14 + 2 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
355 white_pixel
, black_pixel
, "%s", img_name(params
->img_path
));
357 GP_BackendFlip(backend
);
362 static pthread_t loader_thread
= (pthread_t
)0;
364 static void show_image(struct loader_params
*params
, const char *path
)
368 /* stop previous loader thread */
371 pthread_join(loader_thread
, NULL
);
372 loader_thread
= (pthread_t
)0;
377 params
->img_path
= path
;
379 ret
= pthread_create(&loader_thread
, NULL
, image_loader
, (void*)params
);
382 fprintf(stderr
, "Failed to start thread: %s\n", strerror(ret
));
383 GP_BackendExit(backend
);
388 static void sighandler(int signo
)
391 GP_BackendExit(backend
);
393 fprintf(stderr
, "Got signal %i\n", signo
);
398 static void init_backend(const char *backend_opts
)
400 backend
= GP_BackendInit(backend_opts
, "Spiv", stderr
);
402 if (backend
== NULL
) {
403 fprintf(stderr
, "Failed to initalize backend '%s'\n", backend_opts
);
408 static int alarm_fired
= 0;
410 static void alarm_handler(int signo
)
415 static int wait_for_event(int sleep_msec
)
417 static int sleep_msec_count
= 0;
418 static int alarm_set
= 0;
420 if (sleep_msec
< 0) {
421 GP_BackendPoll(backend
);
426 /* We can't sleep on backend fd, because the backend doesn't export it. */
427 if (backend
->fd
< 0) {
428 GP_BackendPoll(backend
);
431 sleep_msec_count
+= 10;
433 if (sleep_msec_count
>= sleep_msec
) {
434 sleep_msec_count
= 0;
441 /* Initalize select */
445 FD_SET(backend
->fd
, &rfds
);
448 signal(SIGALRM
, alarm_handler
);
449 alarm(sleep_msec
/ 1000);
454 struct timeval tv
= {.tv_sec
= sleep_msec
/ 1000,
455 .tv_usec
= (sleep_msec
% 1000) * 1000};
457 int ret
= select(backend
->fd
+ 1, &rfds
, NULL
, NULL
, &tv
);
464 GP_BackendExit(backend
);
476 GP_BackendPoll(backend
);
481 static void init_caches(struct loader_params
*params
)
483 size_t size
= image_cache_get_ram_size();
484 unsigned int resized_size
= (1024 * size
)/10;
485 unsigned int orig_size
= (1024 * size
)/50;
487 if (resized_size
> 100 * 1024 * 1024)
488 resized_size
= 100 * 1024 * 1024;
490 if (orig_size
> 40 * 1024 * 1024)
491 orig_size
= 40 * 1024 * 1024;
493 GP_DEBUG(1, "Cache sizes original = %u, resized = %u",
494 orig_size
, resized_size
);
496 params
->img_resized_cache
= image_cache_create(resized_size
);
497 params
->img_orig_cache
= image_cache_create(orig_size
);
500 int main(int argc
, char *argv
[])
502 GP_Context
*context
= NULL
;
503 const char *backend_opts
= "X11";
505 struct loader_params params
= {NULL
, 0, 0, 0, NULL
, NULL
};
506 int opt
, debug_level
= 0;
507 GP_PixelType emul_type
= GP_PIXEL_UNKNOWN
;
509 while ((opt
= getopt(argc
, argv
, "b:cd:e:fIPs:r:")) != -1) {
512 params
.show_info
= 1;
515 params
.show_progress
= 1;
521 sleep_sec
= atoi(optarg
);
524 resampling_method
= GP_INTERP_CUBIC_INT
;
527 debug_level
= atoi(optarg
);
530 emul_type
= GP_PixelTypeByName(optarg
);
532 if (emul_type
== GP_PIXEL_UNKNOWN
) {
533 fprintf(stderr
, "Invalid pixel type '%s'\n", optarg
);
538 if (!strcmp(optarg
, "90"))
540 else if (!strcmp(optarg
, "180"))
542 else if (!strcmp(optarg
, "270"))
545 backend_opts
= optarg
;
548 fprintf(stderr
, "Invalid paramter '%c'\n", opt
);
552 GP_SetDebugLevel(debug_level
);
554 signal(SIGINT
, sighandler
);
555 signal(SIGSEGV
, sighandler
);
556 signal(SIGBUS
, sighandler
);
557 signal(SIGABRT
, sighandler
);
559 init_caches(¶ms
);
561 init_backend(backend_opts
);
563 if (emul_type
!= GP_PIXEL_UNKNOWN
)
564 backend
= GP_BackendVirtualInit(backend
, emul_type
, GP_BACKEND_CALL_EXIT
);
566 context
= backend
->context
;
568 GP_EventSetScreenSize(context
->w
, context
->h
);
570 black_pixel
= GP_ColorToContextPixel(GP_COL_BLACK
, context
);
571 white_pixel
= GP_ColorToContextPixel(GP_COL_WHITE
, context
);
573 GP_Fill(context
, black_pixel
);
574 GP_BackendFlip(backend
);
579 params
.show_progress_once
= 1;
580 show_image(¶ms
, argv
[argf
]);
583 /* wait for event or a timeout */
584 if (wait_for_event(sleep_sec
* 1000)) {
589 show_image(¶ms
, argv
[argn
]);
592 /* Read and parse events */
595 while (GP_EventGet(&ev
)) {
597 // GP_EventDump(&ev);
601 if (ev
.code
!= GP_EV_KEY_DOWN
)
604 switch (ev
.val
.key
.key
) {
606 params
.show_info
= !params
.show_info
;
608 params
.show_progress_once
= 1;
609 show_image(¶ms
, NULL
);
612 params
.show_progress
= !params
.show_progress
;
619 params
.show_progress_once
= 1;
620 show_image(¶ms
, NULL
);
623 image_cache_drop(params
.img_resized_cache
);
624 image_cache_drop(params
.img_orig_cache
);
629 GP_BackendExit(backend
);
638 params
.show_progress_once
= 1;
639 show_image(¶ms
, argv
[argn
]);
641 case GP_KEY_PAGE_DOWN
:
647 params
.show_progress_once
= 1;
648 show_image(¶ms
, argv
[argn
]);
657 params
.show_progress_once
= 1;
658 show_image(¶ms
, argv
[argn
]);
660 case GP_KEY_BACKSPACE
:
668 params
.show_progress_once
= 1;
669 show_image(¶ms
, argv
[argn
]);
672 resize_backend_and_blit(¶ms
);
678 case GP_EV_SYS_RESIZE
:
679 GP_BackendResize(backend
, ev
.val
.sys
.w
, ev
.val
.sys
.h
);
680 GP_Fill(backend
->context
, 0);
681 params
.show_progress_once
= 1;
682 show_image(¶ms
, NULL
);
685 GP_BackendExit(backend
);
694 GP_BackendExit(backend
);