lel.c - lel - Farbfeld image viewer | |
git clone git://git.codemadness.org/lel | |
Log | |
Files | |
Refs | |
README | |
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(HEAD… | |
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[1… | |
111 img->height = ((uint32_t)hdr[12] << 24) | (hdr[13] << 16) | (hdr… | |
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 overflo… | |
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)r… | |
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->widt… | |
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.b… | |
252 *newbuf++ = (ibuf[(bufx >> 10)*4+1] * a) + (bg.g… | |
253 *newbuf++ = (ibuf[(bufx >> 10)*4+0] * a) + (bg.r… | |
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, dept… | |
276 ximg = XCreateImage(dpy, CopyFromParent, depth, Z… | |
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, x… | |
280 XInitImage(ximg); | |
281 } else { | |
282 die("this program does not yet support display depths < … | |
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, w… | |
296 else | |
297 ximage(winwidth, cimg->height * winwidth / cimg-… | |
298 break; | |
299 case ASPECT: | |
300 default: | |
301 ximage(cimg->width * cimg->view.zoomfact, cimg->height *… | |
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->wid… | |
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.… | |
487 winwidth = attr.width; | |
488 winheight = attr.height; | |
489 } | |
490 break; | |
491 case ConfigureNotify: | |
492 if (winwidth != ev->xconfigure.width || winheight != ev-… | |
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, req… | |
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 | KeyP… | |
530 ButtonPressMask); | |
531 XMapRaised(dpy, win); | |
532 XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, NULL, NULL, &cla… | |
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", a… | |
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 } |