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 }