spiv: Add image cache.
[gfxprim.git] / demos / spiv / spiv.c
blob26eb45bc4b4cf2a898b7757db1b8ead5aedd01a2
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-2012 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 "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;
59 if (abort_flag)
60 return 1;
62 if (!show_progress)
63 return 0;
65 char buf[100];
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));
83 return 0;
86 struct loader_params {
87 const char *img_path;
88 int show_progress;
89 int show_progress_once;
90 int show_info;
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];
114 return img_path;
117 static void set_caption(const char *path, float rat)
119 char buf[256];
121 snprintf(buf, sizeof(buf), "Spiv ~ %s 1:%3.3f", img_name(path), rat);
123 GP_BackendSetCaption(backend, buf);
127 * Loads image
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 */
140 if (img == NULL) {
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);
153 return NULL;
156 /* Workaround */
157 if (img->pixel_type != GP_PIXEL_RGB888) {
158 GP_Context *tmp = GP_ContextConvert(img, GP_PIXEL_RGB888);
159 GP_ContextFree(img);
160 img = tmp;
163 image_cache_put(params->image_cache, img, params->img_path, 0);
165 cpu_timer_stop(&timer);
168 return img;
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))
180 return 1;
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);
186 return 0;
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");
199 /* Load Image */
200 img = load_image(params);
202 if (img == NULL)
203 return NULL;
206 if (img->w < 320 && img->h < 240) {
207 if (!resize_backend_and_blit(img, params))
208 return NULL;
212 /* Figure out rotation */
213 GP_Size w, h;
215 switch (rotate) {
216 case 0:
217 case 180:
218 default:
219 w = context->w;
220 h = context->h;
221 break;
222 case 90:
223 case 270:
224 w = context->h;
225 h = context->w;
226 break;
229 float rat = calc_img_size(img->w, img->h, w, h);
231 w = img->w;
232 h = img->h;
234 GP_Context *ret;
236 /* Do low pass filter */
237 if (resampling_method != GP_INTERP_LINEAR_LF_INT) {
238 if (rat < 1) {
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,
243 &callback) == NULL)
244 return NULL;
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);
254 if (ret == NULL)
255 return NULL;
257 switch (rotate) {
258 case 0:
259 break;
260 case 90:
261 callback.priv = "Rotating image (90)";
262 img = GP_FilterRotate90_Alloc(ret, &callback);
263 break;
264 case 180:
265 callback.priv = "Rotating image (180)";
266 img = GP_FilterRotate180_Alloc(ret, &callback);
267 break;
268 case 270:
269 callback.priv = "Rotating image (270)";
270 img = GP_FilterRotate270_Alloc(ret, &callback);
271 break;
274 if (rotate) {
275 GP_ContextFree(ret);
276 ret = img;
279 if (img == NULL)
280 return NULL;
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");
289 if (dithering) {
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);
294 } else {
295 GP_Blit_Raw(ret, 0, 0, ret->w, ret->h, context, cx, cy);
298 cpu_timer_stop(&timer);
299 GP_ContextFree(ret);
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);
313 return NULL;
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);
338 return NULL;
341 static pthread_t loader_thread = (pthread_t)0;
343 static void show_image(struct loader_params *params, const char *path)
345 int ret;
347 /* stop previous loader thread */
348 if (loader_thread) {
349 abort_flag = 1;
350 pthread_join(loader_thread, NULL);
351 loader_thread = (pthread_t)0;
352 abort_flag = 0;
355 if (path != NULL)
356 params->img_path = path;
358 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
360 if (ret) {
361 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
362 GP_BackendExit(backend);
363 exit(1);
367 static void sighandler(int signo)
369 if (backend != NULL)
370 GP_BackendExit(backend);
372 fprintf(stderr, "Got signal %i\n", signo);
374 exit(1);
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);
383 exit(1);
387 static int alarm_fired = 0;
389 static void alarm_handler(int signo)
391 alarm_fired = 1;
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);
401 return 0;
404 /* We can't sleep on backend fd, because the backend doesn't export it. */
405 if (backend->fd < 0) {
406 GP_BackendPoll(backend);
407 usleep(10000);
409 sleep_msec_count += 10;
411 if (sleep_msec_count >= sleep_msec) {
412 sleep_msec_count = 0;
413 return 1;
416 return 0;
419 /* Initalize select */
420 fd_set rfds;
421 FD_ZERO(&rfds);
423 FD_SET(backend->fd, &rfds);
425 if (!alarm_set) {
426 signal(SIGALRM, alarm_handler);
427 alarm(sleep_msec / 1000);
428 alarm_fired = 0;
429 alarm_set = 1;
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);
437 switch (ret) {
438 case -1:
439 if (errno == EINTR)
440 return 1;
442 GP_BackendExit(backend);
443 exit(1);
444 break;
445 case 0:
446 if (alarm_fired) {
447 alarm_set = 0;
448 return 1;
451 return 0;
452 break;
453 default:
454 GP_BackendPoll(backend);
455 return 0;
459 int main(int argc, char *argv[])
461 GP_Context *context = NULL;
462 const char *backend_opts = "X11";
463 int sleep_sec = -1;
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) {
471 switch (opt) {
472 case 'I':
473 params.show_info = 1;
474 break;
475 case 'P':
476 params.show_progress = 1;
477 break;
478 case 'f':
479 dithering = 1;
480 break;
481 case 's':
482 sleep_sec = atoi(optarg);
483 break;
484 case 'c':
485 resampling_method = GP_INTERP_CUBIC_INT;
486 break;
487 case 'd':
488 debug_level = atoi(optarg);
489 break;
490 case 'e':
491 emul_type = GP_PixelTypeByName(optarg);
493 if (emul_type == GP_PIXEL_UNKNOWN) {
494 fprintf(stderr, "Invalid pixel type '%s'\n", optarg);
495 return 1;
497 break;
498 case 'r':
499 if (!strcmp(optarg, "90"))
500 rotate = 90;
501 else if (!strcmp(optarg, "180"))
502 rotate = 180;
503 else if (!strcmp(optarg, "270"))
504 rotate = 270;
505 case 'b':
506 backend_opts = optarg;
507 break;
508 default:
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);
535 int argf = optind;
536 int argn = argf;
538 params.show_progress_once = 1;
539 show_image(&params, argv[argf]);
541 for (;;) {
542 /* wait for event or a timeout */
543 if (wait_for_event(sleep_sec * 1000)) {
544 argn++;
545 if (argn >= argc)
546 argn = argf;
548 show_image(&params, argv[argn]);
551 /* Read and parse events */
552 GP_Event ev;
554 while (GP_EventGet(&ev)) {
556 GP_EventDump(&ev);
558 switch (ev.type) {
559 case GP_EV_KEY:
560 if (ev.code != GP_EV_KEY_DOWN)
561 continue;
563 switch (ev.val.key.key) {
564 case GP_KEY_I:
565 params.show_info = !params.show_info;
567 params.show_progress_once = 1;
568 show_image(&params, NULL);
569 break;
570 case GP_KEY_P:
571 params.show_progress = !params.show_progress;
572 break;
573 case GP_KEY_R:
574 rotate += 90;
575 if (rotate > 270)
576 rotate = 0;
578 params.show_progress_once = 1;
579 show_image(&params, NULL);
580 break;
581 case GP_KEY_ESC:
582 case GP_KEY_ENTER:
583 case GP_KEY_Q:
584 GP_BackendExit(backend);
585 return 0;
586 break;
587 case GP_KEY_RIGHT:
588 case GP_KEY_UP:
589 case GP_KEY_SPACE:
590 argn++;
591 if (argn >= argc)
592 argn = argf;
594 params.show_progress_once = 1;
595 show_image(&params, argv[argn]);
596 break;
597 case GP_KEY_BACKSPACE:
598 case GP_KEY_LEFT:
599 case GP_KEY_DOWN:
600 argn--;
602 if (argn < argf)
603 argn = argc - 1;
605 params.show_progress_once = 1;
606 show_image(&params, argv[argn]);
607 break;
608 case GP_KEY_1:
609 resize_backend_and_blit(&params);
610 break;
612 break;
613 case GP_EV_SYS:
614 switch (ev.code) {
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(&params, NULL);
620 break;
621 case GP_EV_SYS_QUIT:
622 GP_BackendExit(backend);
623 return 0;
624 break;
626 break;
631 GP_BackendExit(backend);
633 return 0;