spiv: Stop loader thread before resizing backend buffer.
[gfxprim.git] / demos / spiv / spiv.c
blob489188ef4c62dfa0b4db9afb84953aa3582ea203
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 show_progress = 0;
51 struct loader_params {
52 /* current image path */
53 const char *img_path;
54 /* current resize ratio */
55 float rat;
57 /* show loader progress */
58 long show_progress:1;
59 long show_progress_once:2;
60 /* show image info in topleft corner */
61 long show_info:3;
62 /* use nearest neighbour resampling first */
63 long show_nn_first:4;
64 /* use dithering when blitting to display */
65 long use_dithering:5;
66 /* use low pass before resampling */
67 long use_low_pass:6;
68 /* image orientation 0, 90, 180, 270 */
69 int rotate;
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;
83 if (abort_flag)
84 return 1;
86 if (!show_progress)
87 return 0;
89 char buf[100];
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));
107 return 0;
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];
129 return img_path;
132 static void set_caption(const char *path, float rat)
134 char buf[256];
136 snprintf(buf, sizeof(buf), "Spiv ~ %s 1:%3.3f", img_name(path), rat);
138 GP_BackendSetCaption(backend, buf);
142 * Loads image
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 */
155 if (img == NULL) {
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)
162 return NULL;
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);
169 return NULL;
172 /* Workaround */
173 if (img->pixel_type != GP_PIXEL_RGB888) {
174 GP_Context *tmp;
175 tmp = GP_ContextConvertAlloc(img, GP_PIXEL_RGB888);
176 GP_ContextFree(img);
177 img = tmp;
180 image_cache_put(params->img_orig_cache, img, params->img_path, 0, 0);
182 cpu_timer_stop(&timer);
185 return img;
189 * Updates display.
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) {
198 case 0:
199 break;
200 case 90:
201 callback.priv = "Rotating image (90)";
202 img = GP_FilterRotate90_Alloc(img, &callback);
203 break;
204 case 180:
205 callback.priv = "Rotating image (180)";
206 img = GP_FilterRotate180_Alloc(img, &callback);
207 break;
208 case 270:
209 callback.priv = "Rotating image (270)";
210 img = GP_FilterRotate270_Alloc(img, &callback);
211 break;
214 if (img == NULL)
215 return;
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);
229 } else {
230 GP_Blit_Raw(img, 0, 0, img->w, img->h, context, cx, cy);
233 cpu_timer_stop(&timer);
235 if (params->rotate)
236 GP_ContextFree(img);
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);
248 return;
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);
296 if (img != NULL)
297 return img;
299 /* Otherwise load image and resize it */
300 if ((img = load_image(params, 1)) == NULL)
301 return 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);
306 if (nn != NULL) {
307 update_display(params, nn);
308 GP_ContextFree(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,
319 &callback);
320 if (res == NULL)
321 return NULL;
323 img = res;
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);
337 img = i1;
338 cpu_timer_stop(&timer);
340 /* Free low passed context if needed */
341 GP_ContextFree(res);
343 if (img == NULL)
344 return NULL;
346 image_cache_put(params->img_resized_cache, img, params->img_path, cookie, key);
348 return img;
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))
360 return 1;
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);
366 return 0;
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 */
381 GP_Size w, h;
383 switch (params->rotate) {
384 case 0:
385 case 180:
386 default:
387 w = context->w;
388 h = context->h;
389 break;
390 case 90:
391 case 270:
392 w = context->h;
393 h = context->w;
394 break;
397 if ((img = load_image(params, 0)) == NULL)
398 return NULL;
400 params->rat = calc_img_size(img->w, img->h, w, h);
402 w = img->w;
403 h = img->h;
405 img = load_resized_image(params, w * params->rat + 0.5, h * params->rat + 0.5);
407 if (img == NULL)
408 return NULL;
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);
417 return NULL;
420 static pthread_t loader_thread = (pthread_t)0;
422 static void stop_loader(void)
424 if (loader_thread) {
425 abort_flag = 1;
426 pthread_join(loader_thread, NULL);
427 loader_thread = (pthread_t)0;
428 abort_flag = 0;
432 static void show_image(struct loader_params *params, const char *path)
434 int ret;
436 if (path != NULL)
437 params->img_path = path;
439 /* stop previous loader thread */
440 stop_loader();
442 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
444 if (ret) {
445 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
446 GP_BackendExit(backend);
447 exit(1);
451 static void sighandler(int signo)
453 if (backend != NULL)
454 GP_BackendExit(backend);
456 fprintf(stderr, "Got signal %i\n", signo);
458 exit(1);
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);
467 exit(1);
471 static int alarm_fired = 0;
473 static void alarm_handler(int signo)
475 alarm_fired = 1;
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);
485 usleep(10000);
486 return 0;
489 /* We can't sleep on backend fd, because the backend doesn't export it. */
490 if (backend->fd < 0) {
491 GP_BackendPoll(backend);
492 usleep(10000);
494 sleep_msec_count += 10;
496 if (sleep_msec_count >= sleep_msec) {
497 sleep_msec_count = 0;
498 return 1;
501 return 0;
504 /* Initalize select */
505 fd_set rfds;
506 FD_ZERO(&rfds);
508 FD_SET(backend->fd, &rfds);
510 if (!alarm_set) {
511 signal(SIGALRM, alarm_handler);
512 alarm(sleep_msec / 1000);
513 alarm_fired = 0;
514 alarm_set = 1;
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);
522 switch (ret) {
523 case -1:
524 if (errno == EINTR)
525 return 1;
527 GP_BackendExit(backend);
528 exit(1);
529 break;
530 case 0:
531 if (alarm_fired) {
532 alarm_set = 0;
533 return 1;
536 return 0;
537 break;
538 default:
539 GP_BackendPoll(backend);
540 return 0;
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";
570 int sleep_sec = -1;
571 int opt, debug_level = 0;
572 GP_PixelType emul_type = GP_PIXEL_UNKNOWN;
574 struct loader_params params = {
575 .img_path = NULL,
577 .show_progress = 0,
578 .show_progress_once = 0,
579 .show_info = 0,
580 .show_nn_first = 0,
581 .use_dithering = 0,
582 .rotate = 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) {
590 switch (opt) {
591 case 'I':
592 params.show_info = 1;
593 break;
594 case 'P':
595 params.show_progress = 1;
596 break;
597 case 'f':
598 params.use_dithering = 1;
599 break;
600 case 's':
601 sleep_sec = atoi(optarg);
602 break;
603 case 'c':
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;
609 break;
610 case 'd':
611 debug_level = atoi(optarg);
612 break;
613 case 'e':
614 emul_type = GP_PixelTypeByName(optarg);
616 if (emul_type == GP_PIXEL_UNKNOWN) {
617 fprintf(stderr, "Invalid pixel type '%s'\n", optarg);
618 return 1;
620 break;
621 case 'r':
622 if (!strcmp(optarg, "90"))
623 params.rotate = 90;
624 else if (!strcmp(optarg, "180"))
625 params.rotate = 180;
626 else if (!strcmp(optarg, "270"))
627 params.rotate = 270;
628 case 'b':
629 backend_opts = optarg;
630 break;
631 default:
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(&params);
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);
660 int argf = optind;
661 int argn = argf;
663 params.show_progress_once = 1;
664 show_image(&params, argv[argf]);
666 for (;;) {
667 /* wait for event or a timeout */
668 if (wait_for_event(sleep_sec * 1000)) {
669 argn++;
670 if (argn >= argc)
671 argn = argf;
673 show_image(&params, argv[argn]);
676 /* Read and parse events */
677 GP_Event ev;
679 while (GP_EventGet(&ev)) {
681 // GP_EventDump(&ev);
683 switch (ev.type) {
684 case GP_EV_KEY:
685 if (ev.code != GP_EV_KEY_DOWN)
686 continue;
688 switch (ev.val.key.key) {
689 case GP_KEY_I:
690 params.show_info = !params.show_info;
692 params.show_progress_once = 1;
693 show_image(&params, NULL);
694 break;
695 case GP_KEY_P:
696 params.show_progress = !params.show_progress;
697 break;
698 case GP_KEY_R:
699 params.rotate += 90;
700 if (params.rotate > 270)
701 params.rotate = 0;
703 params.show_progress_once = 1;
704 show_image(&params, NULL);
705 break;
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;
715 } else {
716 params.use_low_pass = 1;
717 params.show_nn_first = 1;
720 params.show_progress_once = 1;
721 show_image(&params, argv[argn]);
722 break;
723 case GP_KEY_LEFT_BRACE:
724 if (params.resampling_method == 0)
725 params.resampling_method = GP_INTERP_MAX;
726 else
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;
732 } else {
733 params.use_low_pass = 1;
734 params.show_nn_first = 1;
737 params.show_progress_once = 1;
738 show_image(&params, argv[argn]);
739 break;
740 case GP_KEY_L:
741 params.use_low_pass = !params.use_low_pass;
743 params.show_progress_once = 1;
744 show_image(&params, argv[argn]);
745 break;
746 case GP_KEY_D:
747 image_cache_drop(params.img_resized_cache);
748 image_cache_drop(params.img_orig_cache);
749 break;
750 case GP_KEY_ESC:
751 case GP_KEY_ENTER:
752 case GP_KEY_Q:
753 GP_BackendExit(backend);
754 return 0;
755 break;
756 case GP_KEY_PAGE_UP:
757 argn+=10;
758 //TODO
759 if (argn >= argc)
760 argn = argf;
762 params.show_progress_once = 1;
763 show_image(&params, argv[argn]);
764 break;
765 case GP_KEY_PAGE_DOWN:
766 argn-=10;
767 //TODO
768 if (argn < argf)
769 argn = argc - 1 ;
771 params.show_progress_once = 1;
772 show_image(&params, argv[argn]);
773 break;
774 case GP_KEY_RIGHT:
775 case GP_KEY_UP:
776 case GP_KEY_SPACE:
777 argn++;
778 if (argn >= argc)
779 argn = argf;
781 params.show_progress_once = 1;
782 show_image(&params, argv[argn]);
783 break;
784 case GP_KEY_BACKSPACE:
785 case GP_KEY_LEFT:
786 case GP_KEY_DOWN:
787 argn--;
789 if (argn < argf)
790 argn = argc - 1;
792 params.show_progress_once = 1;
793 show_image(&params, argv[argn]);
794 break;
795 case GP_KEY_1:
796 resize_backend_and_blit(&params);
797 break;
799 break;
800 case GP_EV_SYS:
801 switch (ev.code) {
802 case GP_EV_SYS_RESIZE:
803 /* stop loader thread before resizing backend buffer */
804 stop_loader();
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(&params, NULL);
809 break;
810 case GP_EV_SYS_QUIT:
811 GP_BackendExit(backend);
812 return 0;
813 break;
815 break;
820 GP_BackendExit(backend);
822 return 0;