lel.c - lel - Farbfeld image viewer (HTM) git clone git://git.codemadness.org/lel (DIR) Log (DIR) Files (DIR) Refs (DIR) README (DIR) 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(HEADER_FORMAT)) 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[10] << 8) | (hdr[11] << 0); 111 img->height = ((uint32_t)hdr[12] << 24) | (hdr[13] << 16) | (hdr[14] << 8) | (hdr[15] << 0); 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 overflow `int` */ 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)row_len) { 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->width * 4]; 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.blue * (1 - a)); 252 *newbuf++ = (ibuf[(bufx >> 10)*4+1] * a) + (bg.green * (1 - a)); 253 *newbuf++ = (ibuf[(bufx >> 10)*4+0] * a) + (bg.red * (1 - a)); 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, depth); 276 ximg = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, 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, ximg->data); 280 XInitImage(ximg); 281 } else { 282 die("this program does not yet support display depths < 24\n"); 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, winheight); 296 else 297 ximage(winwidth, cimg->height * winwidth / cimg->width); 298 break; 299 case ASPECT: 300 default: 301 ximage(cimg->width * cimg->view.zoomfact, cimg->height * cimg->view.zoomfact); 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->width, ximg->height); 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.window, &attr); 487 winwidth = attr.width; 488 winheight = attr.height; 489 } 490 break; 491 case ConfigureNotify: 492 if (winwidth != ev->xconfigure.width || winheight != ev->xconfigure.height) { 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, reqwinwidth, reqwinheight, 0, 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 | KeyPressMask | 530 ButtonPressMask); 531 XMapRaised(dpy, win); 532 XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, NULL, NULL, &class); 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", argv[j], 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 }