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
{
89 int show_progress_once
;
92 /* cached loaded images */
93 struct image_cache
*image_cache
;
96 static float calc_img_size(uint32_t img_w
, uint32_t img_h
,
97 uint32_t src_w
, uint32_t src_h
)
99 float w_rat
= 1.00 * src_w
/ img_w
;
100 float h_rat
= 1.00 * src_h
/ img_h
;
102 return GP_MIN(w_rat
, h_rat
);
105 static const char *img_name(const char *img_path
)
107 int i
, len
= strlen(img_path
);
109 for (i
= len
- 1; i
> 0; i
--) {
110 if (img_path
[i
] == '/')
111 return &img_path
[i
+1];
117 static void set_caption(const char *path
, float rat
)
121 snprintf(buf
, sizeof(buf
), "Spiv ~ %s 1:%3.3f", img_name(path
), rat
);
123 GP_BackendSetCaption(backend
, buf
);
129 GP_Context
*load_image(struct loader_params
*params
)
131 struct cpu_timer timer
;
132 GP_Context
*img
, *context
= backend
->context
;
134 GP_ProgressCallback callback
= {.callback
= image_loader_callback
,
135 .priv
= "Loading image"};
137 img
= image_cache_get(params
->image_cache
, params
->img_path
, 0);
139 /* Image not cached, load it */
141 show_progress
= params
->show_progress
|| params
->show_progress_once
;
142 params
->show_progress_once
= 0;
144 fprintf(stderr
, "Loading '%s'\n", params
->img_path
);
146 cpu_timer_start(&timer
, "Loading");
147 if ((img
= GP_LoadImage(params
->img_path
, &callback
)) == NULL
) {
148 GP_Fill(context
, black_pixel
);
149 GP_Print(context
, NULL
, context
->w
/2, context
->h
/2,
150 GP_ALIGN_CENTER
|GP_VALIGN_CENTER
, white_pixel
, black_pixel
,
151 "Failed to load image :( (%s)", strerror(errno
));
152 GP_BackendFlip(backend
);
157 if (img
->pixel_type
!= GP_PIXEL_RGB888
) {
158 GP_Context
*tmp
= GP_ContextConvert(img
, GP_PIXEL_RGB888
);
163 image_cache_put(params
->image_cache
, img
, params
->img_path
, 0);
165 cpu_timer_stop(&timer
);
172 * This function tries to resize spiv window
173 * and if succedes blits the image directly to the screen.
175 static int resize_backend_and_blit(struct loader_params
*params
)
177 GP_Context
*img
= load_image(params
);
179 if (GP_BackendResize(backend
, img
->w
, img
->h
))
182 GP_Blit_Raw(img
, 0, 0, img
->w
, img
->h
, backend
->context
, 0, 0);
183 GP_BackendFlip(backend
);
184 set_caption(params
->img_path
, 1);
189 static void *image_loader(void *ptr
)
191 struct loader_params
*params
= ptr
;
192 struct cpu_timer timer
;
193 struct cpu_timer sum_timer
;
194 GP_Context
*img
, *context
= backend
->context
;
195 GP_ProgressCallback callback
= {.callback
= image_loader_callback
};
197 cpu_timer_start(&sum_timer
, "sum");
200 img
= load_image(params
);
206 if (img->w < 320 && img->h < 240) {
207 if (!resize_backend_and_blit(img, params))
212 /* Figure out rotation */
229 float rat
= calc_img_size(img
->w
, img
->h
, w
, h
);
236 /* Do low pass filter */
237 if (resampling_method
!= GP_INTERP_LINEAR_LF_INT
) {
239 cpu_timer_start(&timer
, "Blur");
240 callback
.priv
= "Blurring Image";
241 //TODO: We can't blur saved image!
242 if (GP_FilterGaussianBlur(img
, img
, 0.4/rat
, 0.4/rat
,
245 cpu_timer_stop(&timer
);
249 cpu_timer_start(&timer
, "Resampling");
250 callback
.priv
= "Resampling Image";
251 ret
= GP_FilterResize(img
, NULL
, resampling_method
, img
->w
* rat
, img
->h
* rat
, &callback
);
252 cpu_timer_stop(&timer
);
261 callback
.priv
= "Rotating image (90)";
262 img
= GP_FilterRotate90_Alloc(ret
, &callback
);
265 callback
.priv
= "Rotating image (180)";
266 img
= GP_FilterRotate180_Alloc(ret
, &callback
);
269 callback
.priv
= "Rotating image (270)";
270 img
= GP_FilterRotate270_Alloc(ret
, &callback
);
282 uint32_t cx
= (context
->w
- ret
->w
)/2;
283 uint32_t cy
= (context
->h
- ret
->h
)/2;
285 GP_Context sub_display
;
287 cpu_timer_start(&timer
, "Blitting");
290 callback
.priv
= "Dithering";
291 GP_ContextSubContext(context
, &sub_display
, cx
, cy
, ret
->w
, ret
->h
);
292 // GP_FilterFloydSteinberg_RGB888(ret, &sub_display, NULL);
293 GP_FilterHilbertPeano_RGB888(ret
, &sub_display
, NULL
);
295 GP_Blit_Raw(ret
, 0, 0, ret
->w
, ret
->h
, context
, cx
, cy
);
298 cpu_timer_stop(&timer
);
301 /* clean up the rest of the display */
302 GP_FillRectXYWH(context
, 0, 0, cx
, context
->h
, black_pixel
);
303 GP_FillRectXYWH(context
, 0, 0, context
->w
, cy
, black_pixel
);
304 GP_FillRectXYWH(context
, ret
->w
+cx
, 0, context
->w
- ret
->w
- cx
, context
->h
, black_pixel
);
305 GP_FillRectXYWH(context
, 0, ret
->h
+cy
, context
->w
, context
->h
- ret
->h
- cy
, black_pixel
);
307 cpu_timer_stop(&sum_timer
);
309 set_caption(params
->img_path
, rat
);
311 if (!params
->show_info
) {
312 GP_BackendFlip(backend
);
316 GP_Size th
= GP_TextHeight(NULL
);
318 GP_Print(context
, NULL
, 11, 11, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
319 black_pixel
, white_pixel
, "%ux%u", w
, h
);
321 GP_Print(context
, NULL
, 10, 10, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
322 white_pixel
, black_pixel
, "%ux%u", w
, h
);
324 GP_Print(context
, NULL
, 11, 13 + th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
325 black_pixel
, white_pixel
, "1:%3.3f", rat
);
327 GP_Print(context
, NULL
, 10, 12 + th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
328 white_pixel
, black_pixel
, "1:%3.3f", rat
);
330 GP_Print(context
, NULL
, 11, 15 + 2 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
331 black_pixel
, white_pixel
, "%s", img_name(params
->img_path
));
333 GP_Print(context
, NULL
, 10, 14 + 2 * th
, GP_ALIGN_RIGHT
|GP_VALIGN_BOTTOM
,
334 white_pixel
, black_pixel
, "%s", img_name(params
->img_path
));
336 GP_BackendFlip(backend
);
341 static pthread_t loader_thread
= (pthread_t
)0;
343 static void show_image(struct loader_params
*params
, const char *path
)
347 /* stop previous loader thread */
350 pthread_join(loader_thread
, NULL
);
351 loader_thread
= (pthread_t
)0;
356 params
->img_path
= path
;
358 ret
= pthread_create(&loader_thread
, NULL
, image_loader
, (void*)params
);
361 fprintf(stderr
, "Failed to start thread: %s\n", strerror(ret
));
362 GP_BackendExit(backend
);
367 static void sighandler(int signo
)
370 GP_BackendExit(backend
);
372 fprintf(stderr
, "Got signal %i\n", signo
);
377 static void init_backend(const char *backend_opts
)
379 backend
= GP_BackendInit(backend_opts
, "Spiv", stderr
);
381 if (backend
== NULL
) {
382 fprintf(stderr
, "Failed to initalize backend '%s'\n", backend_opts
);
387 static int alarm_fired
= 0;
389 static void alarm_handler(int signo
)
394 static int wait_for_event(int sleep_msec
)
396 static int sleep_msec_count
= 0;
397 static int alarm_set
= 0;
399 if (sleep_msec
< 0) {
400 GP_BackendPoll(backend
);
404 /* We can't sleep on backend fd, because the backend doesn't export it. */
405 if (backend
->fd
< 0) {
406 GP_BackendPoll(backend
);
409 sleep_msec_count
+= 10;
411 if (sleep_msec_count
>= sleep_msec
) {
412 sleep_msec_count
= 0;
419 /* Initalize select */
423 FD_SET(backend
->fd
, &rfds
);
426 signal(SIGALRM
, alarm_handler
);
427 alarm(sleep_msec
/ 1000);
432 struct timeval tv
= {.tv_sec
= sleep_msec
/ 1000,
433 .tv_usec
= (sleep_msec
% 1000) * 1000};
435 int ret
= select(backend
->fd
+ 1, &rfds
, NULL
, NULL
, &tv
);
442 GP_BackendExit(backend
);
454 GP_BackendPoll(backend
);
459 int main(int argc
, char *argv
[])
461 GP_Context
*context
= NULL
;
462 const char *backend_opts
= "X11";
464 struct loader_params params
= {NULL
, 0, 0, 0, NULL
};
465 int opt
, debug_level
= 0;
466 GP_PixelType emul_type
= GP_PIXEL_UNKNOWN
;
468 params
.image_cache
= image_cache_create(0);
470 while ((opt
= getopt(argc
, argv
, "b:cd:e:fIPs:r:")) != -1) {
473 params
.show_info
= 1;
476 params
.show_progress
= 1;
482 sleep_sec
= atoi(optarg
);
485 resampling_method
= GP_INTERP_CUBIC_INT
;
488 debug_level
= atoi(optarg
);
491 emul_type
= GP_PixelTypeByName(optarg
);
493 if (emul_type
== GP_PIXEL_UNKNOWN
) {
494 fprintf(stderr
, "Invalid pixel type '%s'\n", optarg
);
499 if (!strcmp(optarg
, "90"))
501 else if (!strcmp(optarg
, "180"))
503 else if (!strcmp(optarg
, "270"))
506 backend_opts
= optarg
;
509 fprintf(stderr
, "Invalid paramter '%c'\n", opt
);
513 GP_SetDebugLevel(debug_level
);
515 signal(SIGINT
, sighandler
);
516 signal(SIGSEGV
, sighandler
);
517 signal(SIGBUS
, sighandler
);
518 signal(SIGABRT
, sighandler
);
520 init_backend(backend_opts
);
522 if (emul_type
!= GP_PIXEL_UNKNOWN
)
523 backend
= GP_BackendVirtualInit(backend
, emul_type
, GP_BACKEND_CALL_EXIT
);
525 context
= backend
->context
;
527 GP_EventSetScreenSize(context
->w
, context
->h
);
529 black_pixel
= GP_ColorToContextPixel(GP_COL_BLACK
, context
);
530 white_pixel
= GP_ColorToContextPixel(GP_COL_WHITE
, context
);
532 GP_Fill(context
, black_pixel
);
533 GP_BackendFlip(backend
);
538 params
.show_progress_once
= 1;
539 show_image(¶ms
, argv
[argf
]);
542 /* wait for event or a timeout */
543 if (wait_for_event(sleep_sec
* 1000)) {
548 show_image(¶ms
, argv
[argn
]);
551 /* Read and parse events */
554 while (GP_EventGet(&ev
)) {
560 if (ev
.code
!= GP_EV_KEY_DOWN
)
563 switch (ev
.val
.key
.key
) {
565 params
.show_info
= !params
.show_info
;
567 params
.show_progress_once
= 1;
568 show_image(¶ms
, NULL
);
571 params
.show_progress
= !params
.show_progress
;
578 params
.show_progress_once
= 1;
579 show_image(¶ms
, NULL
);
584 GP_BackendExit(backend
);
594 params
.show_progress_once
= 1;
595 show_image(¶ms
, argv
[argn
]);
597 case GP_KEY_BACKSPACE
:
605 params
.show_progress_once
= 1;
606 show_image(¶ms
, argv
[argn
]);
609 resize_backend_and_blit(¶ms
);
615 case GP_EV_SYS_RESIZE
:
616 GP_BackendResize(backend
, ev
.val
.sys
.w
, ev
.val
.sys
.h
);
617 GP_Fill(backend
->context
, 0);
618 params
.show_progress_once
= 1;
619 show_image(¶ms
, NULL
);
622 GP_BackendExit(backend
);
631 GP_BackendExit(backend
);