lel

lel (farbfeld image viewer) 0.2 fork for fun
git clone git://jacobedwards.org/lel
Log | Files | Refs | README | LICENSE

lel.c (11572B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <arpa/inet.h>
      3 
      4 #include <errno.h>
      5 #include <fcntl.h>
      6 #include <fnmatch.h>
      7 #include <signal.h>
      8 #include <signal.h>
      9 #include <stdarg.h>
     10 #include <stdint.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <time.h>
     15 #include <unistd.h>
     16 
     17 #include <X11/Xlib.h>
     18 #include <X11/Xutil.h>
     19 #include <X11/keysym.h>
     20 
     21 #define LEN(X) (sizeof(X) / sizeof(*X))
     22 
     23 /* Image status flags. */
     24 enum { NONE = 0, LOADED = 1, SCALED = 2, DRAWN = 4 };
     25 
     26 struct img {
     27 	int state;
     28 	int width;
     29 	int height;
     30 	uint8_t *buf;
     31 	struct view {
     32 		int panxoffset;
     33 		int panyoffset;
     34 		float zoomfact;
     35 	} view;
     36 };
     37 
     38 struct filter {
     39 	char *pat;
     40 	char *bin;
     41 } filters[] = {
     42 	{ "*.ff.bz2", "bunzip2" },
     43 	{ "*.jpg", "jpg2ff" },
     44 	{ "*.png", "png2ff" }
     45 };
     46 
     47 /* X11 */
     48 static Colormap cmap;
     49 static Display *dpy;
     50 static Drawable xpix;
     51 static GC gc;
     52 static Window win;
     53 static XColor bg;
     54 static XImage *ximg;
     55 
     56 /* config */
     57 static char *bgcolor = "#000000";
     58 static float zoominc = 0.25;
     59 static int paninc = 20;
     60 
     61 static char **imgs;
     62 static int cimg;
     63 static int done;
     64 static int lastimg;
     65 static int nimgs;
     66 static int screen, xfd;
     67 static int winwidth, winheight;
     68 static struct img img;
     69 static void (*view)(unsigned int *, unsigned int *);
     70 
     71 static void
     72 warn(char *s)
     73 {
     74 	perror(s);
     75 }
     76 
     77 static void
     78 die(char *s)
     79 {
     80 	warn(s);
     81 	exit(1);
     82 }
     83 
     84 static int
     85 ff_open(struct img *img, FILE *fp)
     86 {
     87 	uint32_t hdr[4];
     88 
     89 	if (img->state & LOADED)
     90 		return 0;
     91 
     92 	errno = EFTYPE;
     93 	if (fread(hdr, sizeof(*hdr), 4, fp) != 4)
     94 		return -1;
     95 	if (memcmp(hdr, "farbfeld", 8))
     96 		return -1;
     97 
     98 	img->width = ntohl(hdr[2]);
     99 	img->height = ntohl(hdr[3]);
    100 	if (!img->width || !img->height)
    101 		return -1;
    102 
    103 	if (!(img->buf = malloc(img->width * img->height * 4)))
    104 		die("malloc");
    105 
    106 	return errno = 0;
    107 }
    108 
    109 static int
    110 ff_read(struct img *img, FILE *fp)
    111 {
    112 	uint16_t *row;
    113 	unsigned int i, j, size;
    114 
    115 	size = img->width * 4;
    116 	row = malloc(size * sizeof(*row));
    117 	if (row == NULL)
    118 		return -1;
    119 
    120 	for (i = 0; i < img->height; ++i) {
    121 		if (fread(row, sizeof(*row), size, fp) != size) {
    122 			errno = EFTYPE;
    123 			return -1;
    124 		}
    125 		for (j = 0; j < size; ++j)
    126 			img->buf[size * i + j] = row[j];
    127 	}
    128 	free(row);
    129 
    130 	img->state |= LOADED;
    131 
    132 	return 0;
    133 }
    134 
    135 static void
    136 ff_close(struct img *img)
    137 {
    138 	img->state &= ~LOADED;
    139 	if (img->buf) {
    140 		free(img->buf);
    141 		img->buf = NULL;
    142 	}
    143 }
    144 
    145 static int
    146 efork(void)
    147 {
    148 	pid_t pid;
    149 
    150 	if ((pid = fork()) == -1)
    151 		die("fork");
    152 	return pid;
    153 }
    154 
    155 static int
    156 filter(int d, char **argv)
    157 {
    158 	char buf[BUFSIZ];
    159 	int fds[4];
    160 	ssize_t len;
    161 
    162 	if (pipe(fds) || pipe(fds + 2))
    163 		return -1;
    164 
    165 	if (efork() == 0) {
    166 		if (dup2(fds[0], 0) == -1 || dup2(fds[3], 1) == -1)
    167 			die("dup2");
    168 		if (close(fds[1]) || close(fds[2]))
    169 			die("close");
    170 		execvp(*argv, argv);
    171 		die(*argv);
    172 	}
    173 
    174 	if (close(fds[0]) || close(fds[3])) {
    175 		close(fds[1]);
    176 		close(fds[2]);
    177 		return -1;
    178 	}
    179 
    180 	if (efork() == 0) {
    181 		while ((len = read(d, buf, sizeof(buf))) > 0) {
    182 			if (write(fds[1], buf, len) != len)
    183 				die("write");
    184 		}
    185 		if (len == -1)
    186 			die("read");
    187 		_exit(0);
    188 	}
    189 
    190 	close(fds[1]);
    191 	return fds[2];
    192 }
    193 
    194 static char *
    195 getbin(char *s)
    196 {
    197 	int i;
    198 
    199 	for (i = 0; i < LEN(filters); ++i) {
    200 		if (fnmatch(filters[i].pat, s, 0) == 0)
    201 			return filters[i].bin;
    202 	}
    203 	return NULL;
    204 }
    205 
    206 static void
    207 deleteimg(void)
    208 {
    209 	imgs[cimg] = NULL;
    210 	if (--nimgs <= 0)
    211 		exit(0);
    212 	nextimg();
    213 	if (imgs[cimg] == NULL)
    214 		previmg();
    215 }
    216 
    217 static void
    218 loadimg(void)
    219 {
    220 	int d;
    221 	int tmp;
    222 	FILE *fp;
    223 	char *argv[2];
    224 
    225 	d = open(imgs[cimg], O_RDONLY);
    226 	if (d == -1) {
    227 badimage:
    228 		warn(imgs[cimg]);
    229 		deleteimg();
    230 		return;
    231 	}
    232 
    233 	*argv = getbin(imgs[cimg]);
    234 	if (*argv) {
    235 		argv[1] = NULL;
    236 		tmp = filter(d, argv);
    237 		if (close(d) || tmp == -1)
    238 			goto badimage;
    239 		d = tmp;
    240 	}
    241 
    242 	fp = fdopen(d, "r");
    243 	if (fp == NULL || ff_open(&img, fp) || ff_read(&img, fp) || fclose(fp))
    244 		goto badimage;
    245 	img.state &= ~(DRAWN | SCALED);
    246 
    247 	XResizeWindow(dpy, win, img.width, img.height);
    248 	XStoreName(dpy, win, imgs[cimg]);
    249 	XFlush(dpy);
    250 }
    251 
    252 static void
    253 nextimg(void)
    254 {
    255 	if (cimg + 1 > lastimg)
    256 		return;
    257 	if (imgs[++cimg] == NULL) {
    258 		nextimg();
    259 	} else {
    260 		ff_close(&img);
    261 		loadimg();
    262 	}
    263 }
    264 
    265 static void
    266 previmg(void)
    267 {
    268 	if (cimg - 1 < 0)
    269 		return;
    270 	if (imgs[--cimg] == NULL) {
    271 		previmg();
    272 	} else {
    273 		ff_close(&img);
    274 		loadimg();
    275 	}
    276 }
    277 
    278 /* scales imgbuf data to newbuf (ximg->data), nearest neighbour. */
    279 static void
    280 scale(unsigned int width, unsigned int height, unsigned int bytesperline,
    281 	char *newbuf)
    282 {
    283 	unsigned char *ibuf;
    284 	unsigned int jdy, dx, bufx, x, y;
    285 	float a = 0.0f;
    286 
    287 	jdy = bytesperline / 4 - width;
    288 	dx = (img.width << 10) / width;
    289 	for (y = 0; y < height; y++) {
    290 		bufx = img.width / width;
    291 		ibuf = &img.buf[y * img.height / height * img.width * 4];
    292 
    293 		for (x = 0; x < width; x++) {
    294 			a = (ibuf[(bufx >> 10)*4+3]) / 255.0f;
    295 			*newbuf++ = (ibuf[(bufx >> 10)*4+2] * a) + (bg.blue * (1 - a));
    296 			*newbuf++ = (ibuf[(bufx >> 10)*4+1] * a) + (bg.green * (1 - a));
    297 			*newbuf++ = (ibuf[(bufx >> 10)*4+0] * a) + (bg.red * (1 - a));
    298 			newbuf++;
    299 			bufx += dx;
    300 		}
    301 		newbuf += jdy;
    302 	}
    303 }
    304 
    305 static void
    306 ximage(unsigned int newwidth, unsigned int newheight)
    307 {
    308 	int depth;
    309 
    310 	/* destroy previous image */
    311 	if (ximg) {
    312 		XDestroyImage(ximg);
    313 		ximg = NULL;
    314 	}
    315 	depth = DefaultDepth(dpy, screen);
    316 	if (depth < 24) {
    317 		die("this program does not yet support display depths < 24\n");
    318 	} else {
    319 		if (xpix)
    320 			XFreePixmap(dpy, xpix);
    321 		xpix = XCreatePixmap(dpy, win, winwidth, winheight, depth);
    322 		ximg = XCreateImage(dpy, CopyFromParent, depth,	ZPixmap, 0,
    323 		                    NULL, newwidth, newheight, 32, 0);
    324 		ximg->data = malloc(ximg->bytes_per_line * ximg->height);
    325 		scale(ximg->width, ximg->height, ximg->bytes_per_line, ximg->data);
    326 		XInitImage(ximg);
    327 	}
    328 }
    329 
    330 static void
    331 view_stretch(unsigned int *w, unsigned int *h)
    332 {
    333 	*w = winwidth;
    334 	*h = winheight;
    335 }
    336 
    337 static void
    338 view_height(unsigned int *w, unsigned int *h)
    339 {
    340 	*w = img.width * winheight / img.height;
    341 	*h = winheight;
    342 }
    343 
    344 static void
    345 view_width(unsigned int *w, unsigned int *h)
    346 {
    347 	*w = winwidth;
    348 	*h = img.height * winwidth / img.width;
    349 }
    350 
    351 static void
    352 view_full(unsigned int *w, unsigned int *h)
    353 {
    354 	*w = img.width;
    355 	*h = img.height;
    356 }
    357 
    358 static void
    359 view_aspect(unsigned int *w, unsigned int *h)
    360 {
    361 	if (winwidth * img.height > winheight * img.width)
    362 		view_width(w, h);
    363 	else
    364 		view_height(w, h);
    365 }
    366 
    367 static void
    368 view_downscale(unsigned int *w, unsigned int *h)
    369 {
    370 	if (img.width > winwidth || img.height > winheight)
    371 		view_fit(w, h);
    372 	else
    373 		view_full(w, h);
    374 }
    375 
    376 static void
    377 view_fit(unsigned int *w, unsigned int *h)
    378 {
    379 	if (winwidth * img.height > winheight * img.width)
    380 		view_height(w, h);
    381 	else
    382 		view_width(w, h);
    383 }
    384 
    385 static void
    386 scaleview(void)
    387 {
    388 	unsigned int w, h;
    389 
    390 	view(&w, &h);
    391 	ximage(w * img.view.zoomfact, h * img.view.zoomfact);
    392 	img.state |= SCALED;
    393 }
    394 
    395 static void
    396 draw(void)
    397 {
    398 	int xoffset, yoffset;
    399 
    400 	/* center vertical, horizontal */
    401 	xoffset = (winwidth - ximg->width) / 2;
    402 	yoffset = (winheight - ximg->height) / 2;
    403 
    404 	/* pan offset */
    405 	xoffset -= img.view.panxoffset;
    406 	yoffset -= img.view.panyoffset;
    407 
    408 	XSetForeground(dpy, gc, bg.pixel);
    409 	XFillRectangle(dpy, xpix, gc, 0, 0, winwidth, winheight);
    410 	XPutImage(dpy, xpix, gc, ximg, 0, 0, xoffset, yoffset, ximg->width, ximg->height);
    411 	XCopyArea(dpy, xpix, win, gc, 0, 0, winwidth, winheight, 0, 0);
    412 
    413 	XFlush(dpy);
    414 	img.state |= DRAWN;
    415 }
    416 
    417 static void
    418 update(void)
    419 {
    420 	if (!(img.state & LOADED))
    421 		return;
    422 	if (!(img.state & SCALED))
    423 		scaleview();
    424 	if (!(img.state & DRAWN))
    425 		draw();
    426 }
    427 
    428 static void
    429 setview(void (*newview)(unsigned int *, unsigned int *))
    430 {
    431 	if (view == newview)
    432 		return;
    433 	view = newview;
    434 	img.view.panyoffset = 0;
    435 	img.view.panxoffset = 0;
    436 	img.state &= ~(DRAWN | SCALED);
    437 }
    438 
    439 static void
    440 pan(int x, int y)
    441 {
    442 	img.view.panxoffset -= x;
    443 	img.view.panyoffset -= y;
    444 	img.state &= ~(DRAWN | SCALED);
    445 }
    446 
    447 static void
    448 panperc(int x, int y)
    449 {
    450 	pan(winwidth / 100 * x, winheight / 100 * y);
    451 }
    452 
    453 static void
    454 inczoom(float f)
    455 {
    456 	if ((img.view.zoomfact + f) <= 0)
    457 		return;
    458 	img.view.zoomfact += f;
    459 	img.state &= ~(DRAWN | SCALED);
    460 }
    461 
    462 static void
    463 zoom(float f)
    464 {
    465 	if (f == img.view.zoomfact)
    466 		return;
    467 	img.view.zoomfact = f;
    468 	img.state &= ~(DRAWN | SCALED);
    469 }
    470 
    471 static void
    472 buttonpress(XEvent *ev)
    473 {
    474 	switch(ev->xbutton.button) {
    475 	case Button4:
    476 		inczoom(zoominc);
    477 		break;
    478 	case Button5:
    479 		inczoom(-zoominc);
    480 		break;
    481 	}
    482 }
    483 
    484 static void
    485 printname(void)
    486 {
    487 	printf("%s\n", imgs[cimg]);
    488 }
    489 
    490 static void
    491 keypress(XEvent *ev)
    492 {
    493 	KeySym key;
    494 
    495 	key = XLookupKeysym(&ev->xkey, 0);
    496 	switch(key) {
    497 	case XK_Left:
    498 	case XK_h:
    499 		panperc(paninc, 0);
    500 		break;
    501 	case XK_Down:
    502 	case XK_j:
    503 		panperc(0, -paninc);
    504 		break;
    505 	case XK_Up:
    506 	case XK_k:
    507 		panperc(0, paninc);
    508 		break;
    509 	case XK_Right:
    510 	case XK_l:
    511 		panperc(-paninc, 0);
    512 		break;
    513 	case XK_KP_Add:
    514 	case XK_equal:
    515 	case XK_plus:
    516 	case XK_i:
    517 		inczoom(zoominc);
    518 		break;
    519 	case XK_KP_Subtract:
    520 	case XK_underscore:
    521 	case XK_minus:
    522 	case XK_o:
    523 		inczoom(-zoominc);
    524 		break;
    525 	case XK_0:
    526 		zoom(1.0); /* fallthrough */
    527 	case XK_r:
    528 		img.view.panxoffset = 0;
    529 		img.view.panyoffset = 0;
    530 		img.state &= ~(DRAWN | SCALED);
    531 		break;
    532 	case XK_n:
    533 		nextimg();
    534 		break;
    535 	case XK_p:
    536 		previmg();
    537 		break;
    538 	case XK_x:
    539 		deleteimg();
    540 		break;
    541 	case XK_a:
    542 		setview(view_aspect);
    543 		break;
    544 	case XK_s:
    545 		setview(view_stretch);
    546 		break;
    547 	case XK_d:
    548 		setview(view_full);
    549 		break;
    550 	case XK_f:
    551 		setview(view_fit);
    552 		break;
    553 	case XK_w:
    554 		setview(view_width);
    555 		break;
    556 	case XK_e:
    557 		setview(view_height);
    558 		break;
    559 	case XK_t:
    560 		setview(view_downscale);
    561 		break;
    562 	case XK_Escape:
    563 	case XK_q:
    564 		done = 1;
    565 		break;
    566 	case XK_Return:
    567 		printname();
    568 		break;
    569 	}
    570 	update();
    571 }
    572 
    573 static void
    574 handleevent(XEvent *ev)
    575 {
    576 	XWindowAttributes attr;
    577 
    578 	switch(ev->type) {
    579 	case MapNotify:
    580 		if (!winwidth || !winheight) {
    581 			XGetWindowAttributes(ev->xmap.display, ev->xmap.window, &attr);
    582 			winwidth = attr.width;
    583 			winheight = attr.height;
    584 		}
    585 		break;
    586 	case ConfigureNotify:
    587 		if (winwidth != ev->xconfigure.width || winheight != ev->xconfigure.height) {
    588 			winwidth = ev->xconfigure.width;
    589 			winheight = ev->xconfigure.height;
    590 			img.state &= ~(SCALED);
    591 		}
    592 		break;
    593 	case Expose:
    594 		img.state &= ~(DRAWN);
    595 		update();
    596 		break;
    597 	case KeyPress:
    598 		keypress(ev);
    599 		break;
    600 	case ButtonPress:
    601 		buttonpress(ev);
    602 		break;
    603 	}
    604 }
    605 
    606 static void
    607 setup(void)
    608 {
    609 	XClassHint class = { getprogname(), getprogname() };
    610 
    611 	if (!(dpy = XOpenDisplay(NULL)))
    612 		die("unable to open X display");
    613 	xfd = ConnectionNumber(dpy);
    614 	screen = DefaultScreen(dpy);
    615 
    616 	win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 720, 480, 0,
    617 	                    DefaultDepth(dpy, screen), InputOutput,
    618 	                    CopyFromParent, 0, NULL);
    619 	gc = XCreateGC(dpy, win, 0, NULL);
    620 	cmap = DefaultColormap(dpy, screen);
    621 	if (!XAllocNamedColor(dpy, cmap, bgcolor, &bg, &bg))
    622 		die("unable to allocate color");
    623 	XStoreName(dpy, win, imgs[cimg]);
    624 	XSelectInput(dpy, win, StructureNotifyMask | ExposureMask | KeyPressMask |
    625 	                       ButtonPressMask);
    626 	XMapRaised(dpy, win);
    627 	XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, NULL, NULL, &class);
    628 	XFlush(dpy);
    629 
    630 	signal(SIGCHLD, SIG_IGN);
    631 }
    632 
    633 static void
    634 run(void)
    635 {
    636 	XEvent ev;
    637 
    638 	while (!done && !XNextEvent(dpy, &ev))
    639 		handleevent(&ev);
    640 }
    641 
    642 int
    643 main(int argc, char *argv[])
    644 {
    645 	int c;
    646 
    647 	view = view_full;
    648 	while ((c = getopt(argc, argv, "f")) != -1) {
    649 		switch (c) {
    650 		case 'f':
    651 			view = view_fit;
    652 			break;
    653 		default:
    654 			fprintf(stderr, "usage: %s [-f] [file...]\n",
    655 			    getprogname());
    656 			exit(1);
    657 		}
    658 	}
    659 
    660 	img.view.zoomfact = 1.0;
    661 	imgs = argv + optind;
    662 	nimgs = argc - optind;
    663 	if (!nimgs) {
    664 		++nimgs;
    665 		*imgs = "/dev/stdin";
    666 	}
    667 	lastimg = nimgs - 1;
    668 
    669 	setup();
    670 	loadimg();
    671 	run();
    672 
    673 	return 0;
    674 }