spiv: A few fixes for spiv.
[gfxprim.git] / demos / spiv / spiv.c
blob5d0dcab6b5a87bb22fc963a92fc8e2eaacd11baa
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 "cpu_timer.h"
41 static GP_Pixel black_pixel;
42 static GP_Pixel white_pixel;
44 static GP_Backend *backend = NULL;
46 /* image loader thread */
47 static int abort_flag = 0;
48 static int rotate = 0;
49 static int show_progress = 0;
50 static int resampling_method = GP_INTERP_LINEAR_LF_INT;
52 static int image_loader_callback(GP_ProgressCallback *self)
54 static GP_Size size = 0;
55 GP_Context *c = backend->context;
57 if (abort_flag)
58 return 1;
60 if (!show_progress)
61 return 0;
63 char buf[100];
65 snprintf(buf, sizeof(buf), "%s ... %-3.1f%%",
66 (const char*)self->priv, self->percentage);
68 int align = GP_ALIGN_CENTER|GP_VALIGN_ABOVE;
70 GP_TextClear(c, NULL, c->w/2, c->h - 4, align,
71 black_pixel, GP_MAX(size, GP_TextWidth(NULL, buf)));
73 GP_Text(c, NULL, c->w/2, c->h - 4, align,
74 white_pixel, black_pixel, buf);
76 size = GP_TextWidth(NULL, buf);
78 GP_BackendUpdateRect(backend, c->w/2 - size/2 - 1, c->h - 4,
79 c->w/2 + size/2 + 1, c->h - 4 - GP_TextHeight(NULL));
81 return 0;
84 struct loader_params {
85 const char *img_path;
86 int show_progress;
87 int show_progress_once;
88 int show_info;
90 /* cached loaded image */
91 GP_Context *img;
94 static float calc_img_size(uint32_t img_w, uint32_t img_h,
95 uint32_t src_w, uint32_t src_h)
97 float w_rat = 1.00 * src_w / img_w;
98 float h_rat = 1.00 * src_h / img_h;
100 return GP_MIN(w_rat, h_rat);
103 static const char *img_name(const char *img_path)
105 int i, len = strlen(img_path);
107 for (i = len - 1; i > 0; i--) {
108 if (img_path[i] == '/')
109 return &img_path[i+1];
112 return img_path;
115 static void set_caption(const char *path, float rat)
117 char buf[256];
119 snprintf(buf, sizeof(buf), "Spiv ~ %s 1:%3.3f", img_name(path), rat);
121 GP_BackendSetCaption(backend, buf);
125 * Loads image
127 int load_image(struct loader_params *params)
129 struct cpu_timer timer;
130 GP_Context *img, *context = backend->context;
132 if (params->img != NULL) {
133 fprintf(stderr, "Image cached!\n");
134 return 0;
137 GP_ProgressCallback callback = {.callback = image_loader_callback,
138 .priv = "Loading image"};
140 show_progress = params->show_progress || params->show_progress_once;
141 params->show_progress_once = 0;
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 1;
154 cpu_timer_stop(&timer);
156 params->img = img;
158 return 0;
162 * This function tries to resize spiv window
163 * and if succedes blits the image directly to the screen.
165 static int resize_backend_and_blit(GP_Context *img,
166 struct loader_params *params)
168 if (GP_BackendResize(backend, img->w, img->h))
169 return 1;
171 GP_Blit_Raw(img, 0, 0, img->w, img->h, backend->context, 0, 0);
172 GP_BackendFlip(backend);
173 set_caption(params->img_path, 1);
175 return 0;
178 static void *image_loader(void *ptr)
180 struct loader_params *params = ptr;
181 struct cpu_timer timer;
182 struct cpu_timer sum_timer;
183 GP_Context *img, *context = backend->context;
184 GP_ProgressCallback callback = {.callback = image_loader_callback};
186 cpu_timer_start(&sum_timer, "sum");
188 /* Load Image */
189 if (load_image(params))
190 return NULL;
192 img = params->img;
195 if (img->w < 320 && img->h < 240) {
196 if (!resize_backend_and_blit(img, params))
197 return NULL;
201 /* Figure out rotation */
202 GP_Size w, h;
204 switch (rotate) {
205 case 0:
206 case 180:
207 default:
208 w = context->w;
209 h = context->h;
210 break;
211 case 90:
212 case 270:
213 w = context->h;
214 h = context->w;
215 break;
218 float rat = calc_img_size(img->w, img->h, w, h);
220 w = img->w;
221 h = img->h;
223 /* Workaround */
224 if (img->pixel_type != GP_PIXEL_RGB888) {
225 GP_Context *tmp = GP_ContextConvert(img, GP_PIXEL_RGB888);
226 GP_ContextFree(img);
227 img = tmp;
230 GP_Context *ret;
232 /* Do low pass filter */
233 if (resampling_method != GP_INTERP_LINEAR_LF_INT) {
234 if (rat < 1) {
235 cpu_timer_start(&timer, "Blur");
236 callback.priv = "Blurring Image";
237 if (GP_FilterGaussianBlur(img, img, 0.4/rat, 0.4/rat,
238 &callback) == NULL)
239 return NULL;
240 cpu_timer_stop(&timer);
244 cpu_timer_start(&timer, "Resampling");
245 callback.priv = "Resampling Image";
246 ret = GP_FilterResize(img, NULL, resampling_method, img->w * rat, img->h * rat, &callback);
247 cpu_timer_stop(&timer);
249 if (ret == NULL)
250 return NULL;
252 switch (rotate) {
253 case 0:
254 break;
255 case 90:
256 callback.priv = "Rotating image (90)";
257 img = GP_FilterRotate90Alloc(ret, &callback);
258 break;
259 case 180:
260 callback.priv = "Rotating image (180)";
261 img = GP_FilterRotate180Alloc(ret, &callback);
262 break;
263 case 270:
264 callback.priv = "Rotating image (270)";
265 img = GP_FilterRotate270Alloc(ret, &callback);
266 break;
269 if (rotate) {
270 GP_ContextFree(ret);
271 ret = img;
274 if (img == NULL)
275 return NULL;
277 uint32_t cx = (context->w - ret->w)/2;
278 uint32_t cy = (context->h - ret->h)/2;
280 cpu_timer_start(&timer, "Blitting");
281 GP_Blit_Raw(ret, 0, 0, ret->w, ret->h, context, cx, cy);
282 cpu_timer_stop(&timer);
283 GP_ContextFree(ret);
285 /* clean up the rest of the display */
286 GP_FillRectXYWH(context, 0, 0, cx, context->h, black_pixel);
287 GP_FillRectXYWH(context, 0, 0, context->w, cy, black_pixel);
288 GP_FillRectXYWH(context, ret->w+cx, 0, context->w - ret->w - cx, context->h, black_pixel);
289 GP_FillRectXYWH(context, 0, ret->h+cy, context->w, context->h - ret->h - cy, black_pixel);
291 cpu_timer_stop(&sum_timer);
293 set_caption(params->img_path, rat);
295 if (!params->show_info) {
296 GP_BackendFlip(backend);
297 return NULL;
300 GP_Size th = GP_TextHeight(NULL);
302 GP_Print(context, NULL, 11, 11, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
303 black_pixel, white_pixel, "%ux%u", w, h);
305 GP_Print(context, NULL, 10, 10, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
306 white_pixel, black_pixel, "%ux%u", w, h);
308 GP_Print(context, NULL, 11, 13 + th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
309 black_pixel, white_pixel, "1:%3.3f", rat);
311 GP_Print(context, NULL, 10, 12 + th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
312 white_pixel, black_pixel, "1:%3.3f", rat);
314 GP_Print(context, NULL, 11, 15 + 2 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
315 black_pixel, white_pixel, "%s", img_name(params->img_path));
317 GP_Print(context, NULL, 10, 14 + 2 * th, GP_ALIGN_RIGHT|GP_VALIGN_BOTTOM,
318 white_pixel, black_pixel, "%s", img_name(params->img_path));
320 GP_BackendFlip(backend);
322 return NULL;
325 static pthread_t loader_thread = (pthread_t)0;
327 static void show_image(struct loader_params *params, const char *path)
329 int ret;
331 /* stop previous loader thread */
332 if (loader_thread) {
333 abort_flag = 1;
334 pthread_join(loader_thread, NULL);
335 loader_thread = (pthread_t)0;
336 abort_flag = 0;
339 /* invalidate cached image if path has changed */
340 if (params->img_path == NULL ||
341 (path != NULL && strcmp(params->img_path, path))) {
342 GP_ContextFree(params->img);
343 params->img = NULL;
344 params->img_path = path;
347 ret = pthread_create(&loader_thread, NULL, image_loader, (void*)params);
349 if (ret) {
350 fprintf(stderr, "Failed to start thread: %s\n", strerror(ret));
351 GP_BackendExit(backend);
352 exit(1);
356 static void sighandler(int signo)
358 if (backend != NULL)
359 GP_BackendExit(backend);
361 fprintf(stderr, "Got signal %i\n", signo);
363 exit(1);
366 static void init_backend(const char *backend_opts)
368 backend = GP_BackendInit(backend_opts, "Spiv", stderr);
370 if (backend == NULL) {
371 fprintf(stderr, "Failed to initalize backend '%s'\n", backend_opts);
372 exit(1);
376 int main(int argc, char *argv[])
378 GP_InputDriverLinux *drv = NULL;
379 GP_Context *context = NULL;
380 const char *input_dev = NULL;
381 const char *backend_opts = "X11";
382 int sleep_sec = -1;
383 struct loader_params params = {NULL, 0, 0, 0, .img = NULL};
384 int opt, debug_level = 0;
386 while ((opt = getopt(argc, argv, "b:cd:Ii:Ps:r:")) != -1) {
387 switch (opt) {
388 case 'I':
389 params.show_info = 1;
390 break;
391 case 'P':
392 params.show_progress = 1;
393 break;
394 case 'i':
395 input_dev = optarg;
396 break;
397 case 's':
398 sleep_sec = atoi(optarg);
399 break;
400 case 'c':
401 resampling_method = GP_INTERP_CUBIC_INT;
402 break;
403 case 'd':
404 debug_level = atoi(optarg);
405 break;
406 case 'r':
407 if (!strcmp(optarg, "90"))
408 rotate = 90;
409 else if (!strcmp(optarg, "180"))
410 rotate = 180;
411 else if (!strcmp(optarg, "270"))
412 rotate = 270;
413 case 'b':
414 backend_opts = optarg;
415 break;
416 default:
417 fprintf(stderr, "Invalid paramter '%c'\n", opt);
421 GP_SetDebugLevel(debug_level);
423 if (input_dev != NULL) {
424 drv = GP_InputDriverLinuxOpen(input_dev);
426 if (drv == NULL) {
427 fprintf(stderr, "Failed to initalize input device '%s'\n",
428 input_dev);
429 return 1;
433 signal(SIGINT, sighandler);
434 signal(SIGSEGV, sighandler);
435 signal(SIGBUS, sighandler);
436 signal(SIGABRT, sighandler);
438 init_backend(backend_opts);
440 context = backend->context;
442 GP_EventSetScreenSize(context->w, context->h);
444 black_pixel = GP_ColorToContextPixel(GP_COL_BLACK, context);
445 white_pixel = GP_ColorToContextPixel(GP_COL_WHITE, context);
447 GP_Fill(context, black_pixel);
448 GP_BackendFlip(backend);
450 int argf = optind;
451 int argn = argf;
453 params.show_progress_once = 1;
454 show_image(&params, argv[argf]);
456 for (;;) {
457 int ret;
459 if (drv != NULL) {
460 /* Initalize select */
461 fd_set rfds;
462 FD_ZERO(&rfds);
463 FD_SET(drv->fd, &rfds);
464 struct timeval tv = {.tv_sec = sleep_sec, .tv_usec = 0};
465 struct timeval *tvp = sleep_sec != -1 ? &tv : NULL;
467 ret = select(drv->fd + 1, &rfds, NULL, NULL, tvp);
469 tv.tv_sec = sleep_sec;
471 switch (ret) {
472 case -1:
473 GP_BackendExit(backend);
474 return 0;
475 break;
476 case 0:
477 argn++;
478 if (argn >= argc)
479 argn = argf;
481 show_image(&params, argv[argn]);
482 break;
483 default:
484 while (GP_InputDriverLinuxRead(drv));
487 FD_SET(drv->fd, &rfds);
488 } else {
489 if (sleep_sec != -1) {
490 sleep(sleep_sec);
492 argn++;
493 if (argn >= argc)
494 argn = argf;
496 show_image(&params, argv[argn]);
500 if (backend->Poll)
501 GP_BackendPoll(backend);
503 usleep(1000);
505 /* Read and parse events */
506 GP_Event ev;
508 while (GP_EventGet(&ev)) {
510 GP_EventDump(&ev);
512 switch (ev.type) {
513 case GP_EV_KEY:
514 if (ev.code != GP_EV_KEY_DOWN)
515 continue;
517 switch (ev.val.key.key) {
518 case GP_KEY_I:
519 params.show_info = !params.show_info;
521 params.show_progress_once = 1;
522 show_image(&params, NULL);
523 break;
524 case GP_KEY_P:
525 params.show_progress = !params.show_progress;
526 break;
527 case GP_KEY_R:
528 rotate += 90;
529 if (rotate > 270)
530 rotate = 0;
532 params.show_progress_once = 1;
533 show_image(&params, NULL);
534 break;
535 case GP_KEY_ESC:
536 case GP_KEY_ENTER:
537 case GP_KEY_Q:
538 GP_BackendExit(backend);
539 return 0;
540 break;
541 case GP_KEY_RIGHT:
542 case GP_KEY_UP:
543 case GP_KEY_SPACE:
544 argn++;
545 if (argn >= argc)
546 argn = argf;
548 params.show_progress_once = 1;
549 show_image(&params, argv[argn]);
550 break;
551 case GP_KEY_BACKSPACE:
552 case GP_KEY_LEFT:
553 case GP_KEY_DOWN:
554 argn--;
556 if (argn < argf)
557 argn = argc - 1;
559 params.show_progress_once = 1;
560 show_image(&params, argv[argn]);
561 break;
563 break;
564 case GP_EV_SYS:
565 switch (ev.code) {
566 case GP_EV_SYS_RESIZE:
567 GP_BackendResize(backend, ev.val.sys.w, ev.val.sys.h);
568 GP_Fill(backend->context, 0);
569 params.show_progress_once = 1;
570 show_image(&params, NULL);
571 break;
572 case GP_EV_SYS_QUIT:
573 GP_BackendExit(backend);
574 return 0;
575 break;
577 break;
582 GP_BackendExit(backend);
584 return 0;