lel

farbfeld image viewer
git clone git@git.2f30.org/lel.git
Log | Files | Refs | README | LICENSE

lel.c (11907B)


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