spiv: Make the original image cache larger.
[gfxprim.git] / demos / spiv / spiv.c
blob72d85952338a0d2fc64cf09ccfb652e78f64e629
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;
89 int show_progress;
90 int show_progress_once;
91 int show_info;
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];
116 return img_path;
119 static void set_caption(const char *path, float rat)
121 char buf[256];
123 snprintf(buf, sizeof(buf), "Spiv ~ %s 1:%3.3f", img_name(path), rat);
125 GP_BackendSetCaption(backend, buf);
129 * Loads image
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 */
142 if (img == NULL) {
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);
152 return NULL;
155 /* Workaround */
156 if (img->pixel_type != GP_PIXEL_RGB888) {
157 GP_Context *tmp = GP_ContextConvert(img, GP_PIXEL_RGB888);
158 GP_ContextFree(img);
159 img = tmp;
162 image_cache_put(params->img_orig_cache, img, params->img_path, 0, 0);
164 cpu_timer_stop(&timer);
167 return img;
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);
180 if (img != NULL)
181 return img;
183 /* Otherwise load image and resize it */
184 if ((img = load_image(params, 1)) == NULL)
185 return 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);
194 if (res == NULL)
195 return NULL;
197 img = res;
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 */
208 GP_ContextFree(res);
210 if (img == NULL)
211 return NULL;
213 image_cache_put(params->img_resized_cache, img, params->img_path, cookie, resampling_method);
215 return img;
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))
227 return 1;
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);
233 return 0;
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 */
249 GP_Size w, h;
251 switch (rotate) {
252 case 0:
253 case 180:
254 default:
255 w = context->w;
256 h = context->h;
257 break;
258 case 90:
259 case 270:
260 w = context->h;
261 h = context->w;
262 break;
265 if ((img = load_image(params, 0)) == NULL)
266 return NULL;
268 float rat = calc_img_size(img->w, img->h, w, h);
270 w = img->w;
271 h = img->h;
273 img = load_resized_image(params, w * rat + 0.5, h * rat + 0.5, rat);
275 if (img == NULL)
276 return NULL;
278 image_cache_print(params->img_resized_cache);
279 image_cache_print(params->img_orig_cache);
281 switch (rotate) {
282 case 0:
283 break;
284 case 90:
285 callback.priv = "Rotating image (90)";
286 img = GP_FilterRotate90_Alloc(img, &callback);
287 break;
288 case 180:
289 callback.priv = "Rotating image (180)";
290 img = GP_FilterRotate180_Alloc(img, &callback);
291 break;
292 case 270:
293 callback.priv = "Rotating image (270)";
294 img = GP_FilterRotate270_Alloc(img, &callback);
295 break;
298 if (img == NULL)
299 return NULL;
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");
308 if (dithering) {
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);
313 } else {
314 GP_Blit_Raw(img, 0, 0, img->w, img->h, context, cx, cy);
317 cpu_timer_stop(&timer);
319 if (rotate)
320 GP_ContextFree(img);
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);
334 return NULL;
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);
359 return NULL;
362 static pthread_t loader_thread = (pthread_t)0;
364 static void show_image(struct loader_params *params, const char *path)
366 int ret;
368 /* stop previous loader thread */
369 if (loader_thread) {
370 abort_flag = 1;
371 pthread_join(loader_thread, NULL);
372 loader_thread = (pthread_t)0;
373 abort_flag = 0;
376 if (path != NULL)
377 params->img_path = path;
379 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
381 if (ret) {
382 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
383 GP_BackendExit(backend);
384 exit(1);
388 static void sighandler(int signo)
390 if (backend != NULL)
391 GP_BackendExit(backend);
393 fprintf(stderr, "Got signal %i\n", signo);
395 exit(1);
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);
404 exit(1);
408 static int alarm_fired = 0;
410 static void alarm_handler(int signo)
412 alarm_fired = 1;
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);
422 usleep(10000);
423 return 0;
426 /* We can't sleep on backend fd, because the backend doesn't export it. */
427 if (backend->fd < 0) {
428 GP_BackendPoll(backend);
429 usleep(10000);
431 sleep_msec_count += 10;
433 if (sleep_msec_count >= sleep_msec) {
434 sleep_msec_count = 0;
435 return 1;
438 return 0;
441 /* Initalize select */
442 fd_set rfds;
443 FD_ZERO(&rfds);
445 FD_SET(backend->fd, &rfds);
447 if (!alarm_set) {
448 signal(SIGALRM, alarm_handler);
449 alarm(sleep_msec / 1000);
450 alarm_fired = 0;
451 alarm_set = 1;
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);
459 switch (ret) {
460 case -1:
461 if (errno == EINTR)
462 return 1;
464 GP_BackendExit(backend);
465 exit(1);
466 break;
467 case 0:
468 if (alarm_fired) {
469 alarm_set = 0;
470 return 1;
473 return 0;
474 break;
475 default:
476 GP_BackendPoll(backend);
477 return 0;
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";
504 int sleep_sec = -1;
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) {
510 switch (opt) {
511 case 'I':
512 params.show_info = 1;
513 break;
514 case 'P':
515 params.show_progress = 1;
516 break;
517 case 'f':
518 dithering = 1;
519 break;
520 case 's':
521 sleep_sec = atoi(optarg);
522 break;
523 case 'c':
524 resampling_method = GP_INTERP_CUBIC_INT;
525 break;
526 case 'd':
527 debug_level = atoi(optarg);
528 break;
529 case 'e':
530 emul_type = GP_PixelTypeByName(optarg);
532 if (emul_type == GP_PIXEL_UNKNOWN) {
533 fprintf(stderr, "Invalid pixel type '%s'\n", optarg);
534 return 1;
536 break;
537 case 'r':
538 if (!strcmp(optarg, "90"))
539 rotate = 90;
540 else if (!strcmp(optarg, "180"))
541 rotate = 180;
542 else if (!strcmp(optarg, "270"))
543 rotate = 270;
544 case 'b':
545 backend_opts = optarg;
546 break;
547 default:
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(&params);
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);
576 int argf = optind;
577 int argn = argf;
579 params.show_progress_once = 1;
580 show_image(&params, argv[argf]);
582 for (;;) {
583 /* wait for event or a timeout */
584 if (wait_for_event(sleep_sec * 1000)) {
585 argn++;
586 if (argn >= argc)
587 argn = argf;
589 show_image(&params, argv[argn]);
592 /* Read and parse events */
593 GP_Event ev;
595 while (GP_EventGet(&ev)) {
597 // GP_EventDump(&ev);
599 switch (ev.type) {
600 case GP_EV_KEY:
601 if (ev.code != GP_EV_KEY_DOWN)
602 continue;
604 switch (ev.val.key.key) {
605 case GP_KEY_I:
606 params.show_info = !params.show_info;
608 params.show_progress_once = 1;
609 show_image(&params, NULL);
610 break;
611 case GP_KEY_P:
612 params.show_progress = !params.show_progress;
613 break;
614 case GP_KEY_R:
615 rotate += 90;
616 if (rotate > 270)
617 rotate = 0;
619 params.show_progress_once = 1;
620 show_image(&params, NULL);
621 break;
622 case GP_KEY_D:
623 image_cache_drop(params.img_resized_cache);
624 image_cache_drop(params.img_orig_cache);
625 break;
626 case GP_KEY_ESC:
627 case GP_KEY_ENTER:
628 case GP_KEY_Q:
629 GP_BackendExit(backend);
630 return 0;
631 break;
632 case GP_KEY_PAGE_UP:
633 argn+=10;
634 //TODO
635 if (argn >= argc)
636 argn = argf;
638 params.show_progress_once = 1;
639 show_image(&params, argv[argn]);
640 break;
641 case GP_KEY_PAGE_DOWN:
642 argn-=10;
643 //TODO
644 if (argn < argf)
645 argn = argc - 1 ;
647 params.show_progress_once = 1;
648 show_image(&params, argv[argn]);
649 break;
650 case GP_KEY_RIGHT:
651 case GP_KEY_UP:
652 case GP_KEY_SPACE:
653 argn++;
654 if (argn >= argc)
655 argn = argf;
657 params.show_progress_once = 1;
658 show_image(&params, argv[argn]);
659 break;
660 case GP_KEY_BACKSPACE:
661 case GP_KEY_LEFT:
662 case GP_KEY_DOWN:
663 argn--;
665 if (argn < argf)
666 argn = argc - 1;
668 params.show_progress_once = 1;
669 show_image(&params, argv[argn]);
670 break;
671 case GP_KEY_1:
672 resize_backend_and_blit(&params);
673 break;
675 break;
676 case GP_EV_SYS:
677 switch (ev.code) {
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(&params, NULL);
683 break;
684 case GP_EV_SYS_QUIT:
685 GP_BackendExit(backend);
686 return 0;
687 break;
689 break;
694 GP_BackendExit(backend);
696 return 0;