Introduction
Introduction Statistics Contact Development Disclaimer Help
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 }
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.