lel.c - lel - Farbfeld image viewer
(HTM) git clone git://git.codemadness.org/lel
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
lel.c (12001B)
---
1 /* See LICENSE file for copyright and license details. */
2
3 #include <errno.h>
4 #include <signal.h>
5 #include <stdarg.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <time.h>
11 #include <limits.h>
12 #include <unistd.h>
13
14 #include <X11/Xlib.h>
15 #include <X11/Xutil.h>
16 #include <X11/keysym.h>
17
18 #include "arg.h"
19 char *argv0;
20
21 #define APP_NAME "lel"
22 #define HEADER_FORMAT "farbfeld########"
23
24 /* Image status flags. */
25 enum { NONE = 0, LOADED = 1, SCALED = 2, DRAWN = 4 };
26 /* View mode. */
27 enum { ASPECT = 0, FULL_ASPECT, FULL_STRETCH };
28
29 struct img {
30 char *filename;
31 FILE *fp;
32 int state;
33 int width;
34 int height;
35 uint8_t *buf;
36 struct view {
37 int panxoffset;
38 int panyoffset;
39 float zoomfact;
40 } view;
41 };
42
43 static struct img *imgs;
44 static struct img *cimg;
45 static size_t nimgs;
46 static int viewmode = ASPECT;
47 static char *wintitle = APP_NAME;
48 static char *bgcolor = "#000000";
49 static XImage *ximg = NULL;
50 static Drawable xpix = 0;
51 static Display *dpy = NULL;
52 static Colormap cmap;
53 static Window win;
54 static GC gc;
55 static XColor bg;
56 static int screen, xfd;
57 static int running = 1;
58 static int winwidth = 0, winheight = 0;
59 static int winx, winy, reqwinwidth = 320, reqwinheight = 240;
60 static float zoominc = 0.25;
61 static int tflag;
62 static int wflag;
63 static int hflag;
64
65 static void
66 die(const char *fmt, ...)
67 {
68 va_list ap;
69
70 va_start(ap, fmt);
71 vfprintf(stderr, fmt, ap);
72 va_end(ap);
73
74 if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
75 fputc(' ', stderr);
76 perror(NULL);
77 }
78 exit(1);
79 }
80
81 static void
82 usage(void)
83 {
84 die("%s", APP_NAME " " VERSION "\n\n"
85 "usage: " APP_NAME " [OPTIONS...] [FILE]\n"
86 " -a Full window, keep aspect ratio\n"
87 " -f Full window, stretch (no aspect)\n"
88 " -w <w> Window width\n"
89 " -h <h> Window height\n"
90 " -x <x> Window x position\n"
91 " -y <y> Window y position\n"
92 " -t <title> Use title\n"
93 " -v Print version and exit\n");
94 }
95
96 static int
97 ff_open(struct img *img)
98 {
99 uint8_t hdr[17];
100
101 if (img->state & LOADED)
102 return 0;
103
104 if (fread(hdr, 1, strlen(HEADER_FORMAT), img->fp) != strlen(HEADER_FORMAT))
105 return -1;
106
107 if (memcmp(hdr, "farbfeld", 8))
108 return -1;
109
110 img->width = ((uint32_t)hdr[8] << 24) | (hdr[9] << 16) | (hdr[10] << 8) | (hdr[11] << 0);
111 img->height = ((uint32_t)hdr[12] << 24) | (hdr[13] << 16) | (hdr[14] << 8) | (hdr[15] << 0);
112 if (img->width <= 0 || img->height <= 0)
113 return -1;
114
115 if (img->width > (INT_MAX/4)/img->height) /* w*h*4 would overflow `int` */
116 return -1;
117
118 if (!(img->buf = malloc(img->width * img->height * 4)))
119 die("malloc:");
120
121 return 0;
122 }
123
124 static int
125 ff_read(struct img *img)
126 {
127 int i, j, off, row_len;
128 uint16_t *row;
129
130 if (img->state & LOADED)
131 return 0;
132
133 row_len = img->width * strlen("RRGGBBAA");
134 if (!(row = malloc(row_len)))
135 return -1;
136
137 for (off = 0, i = 0; i < img->height; ++i) {
138 if (fread(row, 1, (size_t)row_len, img->fp) != (size_t)row_len) {
139 free(row);
140 die("unexpected EOF or row-skew at %d\n", i);
141 }
142 for (j = 0; j < row_len / 2; j += 4, off += 4) {
143 img->buf[off] = row[j];
144 img->buf[off + 1] = row[j + 1];
145 img->buf[off + 2] = row[j + 2];
146 img->buf[off + 3] = row[j + 3];
147 }
148 }
149 free(row);
150
151 img->state |= LOADED;
152
153 return 0;
154 }
155
156 static void
157 ff_close(struct img *img)
158 {
159 img->state &= ~LOADED;
160 rewind(img->fp);
161 free(img->buf);
162 }
163
164 /* NOTE: will be removed later, for debugging alpha mask */
165 #if 0
166 static void
167 normalsize(char *newbuf)
168 {
169 unsigned int x, y, soff = 0, doff = 0;
170
171 for (y = 0; y < cimg->height; y++) {
172 for (x = 0; x < cimg->width; x++, soff += 4, doff += 4) {
173 newbuf[doff+0] = cimg->buf[soff+2];
174 newbuf[doff+1] = cimg->buf[soff+1];
175 newbuf[doff+2] = cimg->buf[soff+0];
176 newbuf[doff+3] = cimg->buf[soff+3];
177 }
178 }
179 }
180 #endif
181
182 static void
183 loadimg(void)
184 {
185 if (ff_open(cimg))
186 die("can't open image (invalid format?)\n");
187 if (ff_read(cimg))
188 die("can't read image\n");
189 if (!wflag)
190 reqwinwidth = cimg->width;
191 if (!hflag)
192 reqwinheight = cimg->height;
193 if (!tflag)
194 wintitle = cimg->filename;
195 }
196
197 static void
198 reloadimg(void)
199 {
200 loadimg();
201 XResizeWindow(dpy, win, reqwinwidth, reqwinheight);
202 XStoreName(dpy, win, wintitle);
203 XFlush(dpy);
204 }
205
206 static void
207 nextimg(void)
208 {
209 struct img *tmp = cimg;
210
211 cimg++;
212 if (cimg >= &imgs[nimgs])
213 cimg = &imgs[0];
214 if (tmp != cimg) {
215 ff_close(tmp);
216 reloadimg();
217 }
218 }
219
220 static void
221 previmg(void)
222 {
223 struct img *tmp = cimg;
224
225 cimg--;
226 if (cimg < &imgs[0])
227 cimg = &imgs[nimgs - 1];
228 if (tmp != cimg) {
229 ff_close(tmp);
230 reloadimg();
231 }
232 }
233
234 /* scales imgbuf data to newbuf (ximg->data), nearest neighbour. */
235 static void
236 scale(unsigned int width, unsigned int height, unsigned int bytesperline,
237 char *newbuf)
238 {
239 unsigned char *ibuf;
240 unsigned int jdy, dx, bufx, x, y;
241 float a = 0.0f;
242
243 jdy = bytesperline / 4 - width;
244 dx = (cimg->width << 10) / width;
245 for (y = 0; y < height; y++) {
246 bufx = cimg->width / width;
247 ibuf = &cimg->buf[y * cimg->height / height * cimg->width * 4];
248
249 for (x = 0; x < width; x++) {
250 a = (ibuf[(bufx >> 10)*4+3]) / 255.0f;
251 *newbuf++ = (ibuf[(bufx >> 10)*4+2] * a) + (bg.blue * (1 - a));
252 *newbuf++ = (ibuf[(bufx >> 10)*4+1] * a) + (bg.green * (1 - a));
253 *newbuf++ = (ibuf[(bufx >> 10)*4+0] * a) + (bg.red * (1 - a));
254 newbuf++;
255 bufx += dx;
256 }
257 newbuf += jdy;
258 }
259 }
260
261 static void
262 ximage(unsigned int newwidth, unsigned int newheight)
263 {
264 int depth;
265
266 /* destroy previous image */
267 if (ximg) {
268 XDestroyImage(ximg);
269 ximg = NULL;
270 }
271 depth = DefaultDepth(dpy, screen);
272 if (depth >= 24) {
273 if (xpix)
274 XFreePixmap(dpy, xpix);
275 xpix = XCreatePixmap(dpy, win, winwidth, winheight, depth);
276 ximg = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0,
277 NULL, newwidth, newheight, 32, 0);
278 ximg->data = malloc(ximg->bytes_per_line * ximg->height);
279 scale(ximg->width, ximg->height, ximg->bytes_per_line, ximg->data);
280 XInitImage(ximg);
281 } else {
282 die("this program does not yet support display depths < 24\n");
283 }
284 }
285
286 static void
287 scaleview(void)
288 {
289 switch(viewmode) {
290 case FULL_STRETCH:
291 ximage(winwidth, winheight);
292 break;
293 case FULL_ASPECT:
294 if (winwidth * cimg->height > winheight * cimg->width)
295 ximage(cimg->width * winheight / cimg->height, winheight);
296 else
297 ximage(winwidth, cimg->height * winwidth / cimg->width);
298 break;
299 case ASPECT:
300 default:
301 ximage(cimg->width * cimg->view.zoomfact, cimg->height * cimg->view.zoomfact);
302 break;
303 }
304 cimg->state |= SCALED;
305 }
306
307 static void
308 draw(void)
309 {
310 int xoffset = 0, yoffset = 0;
311
312 if (viewmode != FULL_STRETCH) {
313 /* center vertical, horizontal */
314 xoffset = (winwidth - ximg->width) / 2;
315 yoffset = (winheight - ximg->height) / 2;
316 /* pan offset */
317 xoffset -= cimg->view.panxoffset;
318 yoffset -= cimg->view.panyoffset;
319 }
320 XSetForeground(dpy, gc, bg.pixel);
321 XFillRectangle(dpy, xpix, gc, 0, 0, winwidth, winheight);
322 XPutImage(dpy, xpix, gc, ximg, 0, 0, xoffset, yoffset, ximg->width, ximg->height);
323 XCopyArea(dpy, xpix, win, gc, 0, 0, winwidth, winheight, 0, 0);
324
325 XFlush(dpy);
326 cimg->state |= DRAWN;
327 }
328
329 static void
330 update(void)
331 {
332 if (!(cimg->state & LOADED))
333 return;
334 if (!(cimg->state & SCALED))
335 scaleview();
336 if (!(cimg->state & DRAWN))
337 draw();
338 }
339
340 static void
341 setview(int mode)
342 {
343 if (viewmode == mode)
344 return;
345 viewmode = mode;
346 cimg->state &= ~(DRAWN | SCALED);
347 update();
348 }
349
350 static void
351 pan(int x, int y)
352 {
353 cimg->view.panxoffset -= x;
354 cimg->view.panyoffset -= y;
355 cimg->state &= ~(DRAWN | SCALED);
356 update();
357 }
358
359 static void
360 inczoom(float f)
361 {
362 if ((cimg->view.zoomfact + f) <= 0)
363 return;
364 cimg->view.zoomfact += f;
365 cimg->state &= ~(DRAWN | SCALED);
366 update();
367 }
368
369 static void
370 zoom(float f)
371 {
372 if (f == cimg->view.zoomfact)
373 return;
374 cimg->view.zoomfact = f;
375 cimg->state &= ~(DRAWN | SCALED);
376 update();
377 }
378
379 static void
380 buttonpress(XEvent *ev)
381 {
382 switch(ev->xbutton.button) {
383 case Button4:
384 inczoom(zoominc);
385 break;
386 case Button5:
387 inczoom(-zoominc);
388 break;
389 }
390 }
391
392 static void
393 printname(void)
394 {
395 printf("%s\n", cimg->filename);
396 }
397
398 static void
399 keypress(XEvent *ev)
400 {
401 KeySym key;
402
403 key = XLookupKeysym(&ev->xkey, 0);
404 switch(key) {
405 case XK_Escape:
406 case XK_q:
407 running = 0;
408 break;
409 case XK_Left:
410 case XK_h:
411 pan(winwidth / 20, 0);
412 break;
413 case XK_Down:
414 case XK_j:
415 pan(0, -(winheight / 20));
416 break;
417 case XK_Up:
418 case XK_k:
419 pan(0, winheight / 20);
420 break;
421 case XK_Right:
422 case XK_l:
423 pan(-(winwidth / 20), 0);
424 break;
425 case XK_a:
426 setview(FULL_ASPECT);
427 break;
428 case XK_o:
429 setview(ASPECT);
430 break;
431 case XK_Return:
432 printname();
433 break;
434 case XK_f:
435 setview(FULL_STRETCH);
436 break;
437 case XK_KP_Add:
438 case XK_equal:
439 case XK_plus:
440 inczoom(zoominc);
441 break;
442 case XK_KP_Subtract:
443 case XK_underscore:
444 case XK_minus:
445 inczoom(-zoominc);
446 break;
447 case XK_3:
448 zoom(4.0);
449 break;
450 case XK_2:
451 zoom(2.0);
452 break;
453 case XK_1:
454 zoom(1.0);
455 break;
456 case XK_0:
457 zoom(1.0);
458 setview(ASPECT); /* fallthrough */
459 case XK_r:
460 cimg->view.panxoffset = 0;
461 cimg->view.panyoffset = 0;
462 cimg->state &= ~(DRAWN | SCALED);
463 update();
464 break;
465 case XK_n:
466 nextimg();
467 cimg->state &= ~(DRAWN | SCALED);
468 update();
469 break;
470 case XK_p:
471 previmg();
472 cimg->state &= ~(DRAWN | SCALED);
473 update();
474 break;
475 }
476 }
477
478 static void
479 handleevent(XEvent *ev)
480 {
481 XWindowAttributes attr;
482
483 switch(ev->type) {
484 case MapNotify:
485 if (!winwidth || !winheight) {
486 XGetWindowAttributes(ev->xmap.display, ev->xmap.window, &attr);
487 winwidth = attr.width;
488 winheight = attr.height;
489 }
490 break;
491 case ConfigureNotify:
492 if (winwidth != ev->xconfigure.width || winheight != ev->xconfigure.height) {
493 winwidth = ev->xconfigure.width;
494 winheight = ev->xconfigure.height;
495 cimg->state &= ~(SCALED);
496 }
497 break;
498 case Expose:
499 cimg->state &= ~(DRAWN);
500 update();
501 break;
502 case KeyPress:
503 keypress(ev);
504 break;
505 case ButtonPress:
506 buttonpress(ev);
507 break;
508 }
509 }
510
511 static void
512 setup(void)
513 {
514 XClassHint class = { APP_NAME, APP_NAME };
515
516 if (!(dpy = XOpenDisplay(NULL)))
517 die("can't open X display\n");
518 xfd = ConnectionNumber(dpy);
519 screen = DefaultScreen(dpy);
520
521 win = XCreateWindow(dpy, DefaultRootWindow(dpy), winx, winy, reqwinwidth, reqwinheight, 0,
522 DefaultDepth(dpy, screen), InputOutput,
523 CopyFromParent, 0, NULL);
524 gc = XCreateGC(dpy, win, 0, NULL);
525 cmap = DefaultColormap(dpy, screen);
526 if (!XAllocNamedColor(dpy, cmap, bgcolor, &bg, &bg))
527 die("cannot allocate color\n");
528 XStoreName(dpy, win, wintitle);
529 XSelectInput(dpy, win, StructureNotifyMask | ExposureMask | KeyPressMask |
530 ButtonPressMask);
531 XMapRaised(dpy, win);
532 XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, NULL, NULL, &class);
533 XFlush(dpy);
534 }
535
536 static void
537 run(void)
538 {
539 XEvent ev;
540
541 while (running && !XNextEvent(dpy, &ev))
542 handleevent(&ev);
543 }
544
545 int
546 main(int argc, char *argv[]) {
547 FILE *fp;
548 int i, j;
549
550 ARGBEGIN {
551 case 'a':
552 viewmode = FULL_ASPECT;
553 break;
554 case 'f':
555 viewmode = FULL_STRETCH;
556 break;
557 case 'h':
558 hflag = 1;
559 if (!(reqwinheight = atoi(EARGF(usage()))))
560 usage();
561 break;
562 case 't':
563 wintitle = EARGF(usage());
564 tflag = 1;
565 break;
566 case 'w':
567 wflag = 1;
568 if (!(reqwinwidth = atoi(EARGF(usage()))))
569 usage();
570 break;
571 case 'x':
572 winx = atoi(EARGF(usage()));
573 break;
574 case 'y':
575 winy = atoi(EARGF(usage()));
576 break;
577 default:
578 usage();
579 break;
580 } ARGEND;
581
582 if (!argc) {
583 imgs = calloc(1, sizeof(*imgs));
584 if (!imgs)
585 die("calloc:");
586 nimgs = 1;
587 imgs[0].filename = "<stdin>";
588 imgs[0].fp = stdin;
589 imgs[0].view.zoomfact = 1.0;
590 } else {
591 imgs = calloc(argc, sizeof(*imgs));
592 if (!imgs)
593 die("calloc:");
594 for (i = 0, j = 0; j < argc; j++) {
595 fp = fopen(argv[j], "rb");
596 if (!fp) {
597 fprintf(stderr, "can't open %s: %s\n", argv[j],
598 strerror(errno));
599 continue;
600 }
601 imgs[i].filename = argv[j];
602 imgs[i].fp = fp;
603 imgs[i].view.zoomfact = 1.0;
604 i++;
605 }
606 if (!i)
607 return 1;
608 nimgs = i;
609 }
610 cimg = imgs;
611
612 loadimg();
613 setup();
614 run();
615
616 return 0;
617 }