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 }