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 }