]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/paint.c
devproc: can't wait for ourselfs to stop (thanks Shamar)
[plan9front.git] / sys / src / cmd / paint.c
index 1e4d95d477daa0efc928d6b61970b889afd8f740..4b2087de252ebf3738a7f366cb6d139cdcf4934a 100644 (file)
 #include <libc.h>
 #include <draw.h>
 #include <event.h>
+#include <keyboard.h>
 
-#define NCOLORS 6
+char *filename;
+int zoom = 1;
+int brush = 1;
+Point spos;            /* position on screen */
+Point cpos;            /* position on canvas */
+Image *canvas;
+Image *ink;
+Image *back;
+Image *pal[16];                /* palette */
+Rectangle palr;                /* palette rect on screen */
+Rectangle penr;                /* pen size rect on screen */
 
-Image  *colors[NCOLORS];
+enum {
+       NBRUSH = 10+1,
+};
 
-void
-eresized(int)
+int nundo = 0;
+Image *undo[1024];
+
+int c64[] = {          /* c64 color palette */
+       0x000000,
+       0xFFFFFF,
+       0x68372B,
+       0x70A4B2,
+       0x6F3D86,
+       0x588D43,
+       0x352879,
+       0xB8C76F,
+       0x6F4F25,
+       0x433900,
+       0x9A6759,
+       0x444444,
+       0x6C6C6C,
+       0x9AD284,
+       0x6C5EB5,
+       0x959595,
+};
+
+/*
+ * get bounding rectnagle for stroke from r.min to r.max with
+ * specified brush (size).
+ */
+static Rectangle
+strokerect(Rectangle r, int brush)
 {
-       if(getwindow(display, Refnone) < 0)
-               sysfatal("resize failed");
+       r = canonrect(r);
+       return Rect(r.min.x-brush, r.min.y-brush, r.max.x+brush+1, r.max.y+brush+1);
+}
+
+/*
+ * draw stroke from r.min to r.max to dst with color ink and
+ * brush (size).
+ */
+static void
+strokedraw(Image *dst, Rectangle r, Image *ink, int brush)
+{
+       if(!eqpt(r.min, r.max))
+               line(dst, r.min, r.max, Enddisc, Enddisc, brush, ink, ZP);
+       fillellipse(dst, r.max, brush, brush, ink, ZP);
+}
+
+/*
+ * A draw operation that touches only the area contained in bot but not in top.
+ * mp and sp get aligned with bot.min.
+ */
+static void
+gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
+       Image *src, Point sp, Image *mask, Point mp, int op)
+{
+       Rectangle r;
+       Point origin;
+       Point delta;
+
+       if(Dx(bot)*Dy(bot) == 0)
+               return;
+
+       /* no points in bot - top */
+       if(rectinrect(bot, top))
+               return;
+
+       /* bot - top ≡ bot */
+       if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
+               gendrawop(dst, bot, src, sp, mask, mp, op);
+               return;
+       }
+
+       origin = bot.min;
+       /* split bot into rectangles that don't intersect top */
+       /* left side */
+       if(bot.min.x < top.min.x){
+               r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
+               delta = subpt(r.min, origin);
+               gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+               bot.min.x = top.min.x;
+       }
+
+       /* right side */
+       if(bot.max.x > top.max.x){
+               r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
+               delta = subpt(r.min, origin);
+               gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+               bot.max.x = top.max.x;
+       }
+
+       /* top */
+       if(bot.min.y < top.min.y){
+               r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
+               delta = subpt(r.min, origin);
+               gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+               bot.min.y = top.min.y;
+       }
+
+       /* bottom */
+       if(bot.max.y > top.max.y){
+               r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
+               delta = subpt(r.min, origin);
+               gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+               bot.max.y = top.max.y;
+       }
 }
 
 int
-loadimg(char *name)
+alphachan(ulong chan)
 {
-       Image *b;
-       int fd;
+       for(; chan; chan >>= 8)
+               if(TYPE(chan) == CAlpha)
+                       return 1;
+       return 0;
+}
 
-       if((fd = open(name, OREAD)) < 0)
-               return -1;
-       if((b = readimage(display, fd, 0)) == nil){
-               close(fd);
-               return -1;
+void
+zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
+{
+       Rectangle dr;
+       Image *t;
+       Point a;
+       int w;
+
+       a = ZP;
+       if(r.min.x < d->r.min.x){
+               sp.x += (d->r.min.x - r.min.x)/f;
+               a.x = (d->r.min.x - r.min.x)%f;
+               r.min.x = d->r.min.x;
+       }
+       if(r.min.y < d->r.min.y){
+               sp.y += (d->r.min.y - r.min.y)/f;
+               a.y = (d->r.min.y - r.min.y)%f;
+               r.min.y = d->r.min.y;
+       }
+       rectclip(&r, d->r);
+       w = s->r.max.x - sp.x;
+       if(w > Dx(r))
+               w = Dx(r);
+       dr = r;
+       dr.max.x = dr.min.x+w;
+       if(!alphachan(s->chan))
+               b = nil;
+       if(f <= 1){
+               if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
+               gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
+               return;
+       }
+       if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
+               return;
+       for(; dr.min.y < r.max.y; dr.min.y++){
+               dr.max.y = dr.min.y+1;
+               draw(t, dr, s, nil, sp);
+               if(++a.y == f){
+                       a.y = 0;
+                       sp.y++;
+               }
+       }
+       dr = r;
+       for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
+               dr.max.x = dr.min.x+1;
+               if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
+               gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
+               for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
+                       dr.max.x = dr.min.x+1;
+                       gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
+               }
+               a.x = 0;
+       }
+       freeimage(t);
+}
+
+Point
+s2c(Point p){
+       p = subpt(p, spos);
+       if(p.x < 0) p.x -= zoom-1;
+       if(p.y < 0) p.y -= zoom-1;
+       return addpt(divpt(p, zoom), cpos);
+}
+
+Point
+c2s(Point p){
+       return addpt(mulpt(subpt(p, cpos), zoom), spos);
+}
+
+Rectangle
+c2sr(Rectangle r){
+       return Rpt(c2s(r.min), c2s(r.max));
+}
+
+void
+update(Rectangle *rp){
+       if(canvas==nil)
+               draw(screen, screen->r, back, nil, ZP);
+       else {
+               if(rp == nil)
+                       rp = &canvas->r;
+               gendrawdiff(screen, screen->r, c2sr(canvas->r), back, ZP, nil, ZP, SoverD);
+               zoomdraw(screen, c2sr(*rp), ZR, back, canvas, rp->min, zoom);
        }
-       draw(screen, screen->r, b, 0, b->r.min);
        flushimage(display, 1);
-       freeimage(b);
-       close(fd);
+}
+
+void
+expand(Rectangle r)
+{
+       Rectangle nr;
+       Image *tmp;
+
+       if(canvas==nil){
+               if((canvas = allocimage(display, r, screen->chan, 0, DNofill)) == nil)
+                       sysfatal("allocimage: %r");
+               draw(canvas, canvas->r, back, nil, ZP);
+               return;
+       }
+       nr = canvas->r;
+       combinerect(&nr, r);
+       if(eqrect(nr, canvas->r))
+               return;
+       if((tmp = allocimage(display, nr, canvas->chan, 0, DNofill)) == nil)
+               return;
+       draw(tmp, canvas->r, canvas, nil, canvas->r.min);
+       gendrawdiff(tmp, tmp->r, canvas->r, back, ZP, nil, ZP, SoverD);
+       freeimage(canvas);
+       canvas = tmp;
+}
+
+void
+save(Rectangle r, int mark)
+{
+       Image *tmp;
+       int x;
+
+       if(mark){
+               x = nundo++ % nelem(undo);
+               if(undo[x])
+                       freeimage(undo[x]);
+               undo[x] = nil;
+       }
+       if(canvas==nil || nundo<0)
+               return;
+       if(!rectclip(&r, canvas->r))
+               return;
+       if((tmp = allocimage(display, r, canvas->chan, 0, DNofill)) == nil)
+               return;
+       draw(tmp, r, canvas, nil, r.min);
+       x = nundo++ % nelem(undo);
+       if(undo[x])
+               freeimage(undo[x]);
+       undo[x] = tmp;
+}
+
+void
+restore(int n)
+{
+       Image *tmp;
+       int x;
+
+       while(nundo > 0){
+               if(n-- == 0)
+                       return;
+               x = --nundo % nelem(undo);
+               if((tmp = undo[x]) == nil)
+                       return;
+               undo[x] = nil;
+               if(canvas == nil || canvas->chan != tmp->chan){
+                       freeimage(canvas);
+                       canvas = tmp;
+                       update(nil);
+               } else {
+                       expand(tmp->r);
+                       draw(canvas, tmp->r, tmp, nil, tmp->r.min);
+                       update(&tmp->r);
+                       freeimage(tmp);
+               }
+       }
+}
+
+typedef struct {
+       Rectangle       r;
+       Rectangle       r0;
+       Image*          dst;
+
+       int             yscan;  /* current scanline */
+       int             wscan;  /* bscan width in bytes */
+       Image*          iscan;  /* scanline image */
+       uchar*          bscan;  /* scanline buffer */
+
+       int             nmask;  /* size of bmask in bytes */
+       int             wmask;  /* width of bmask in bytes */
+       Image*          imask;  /* mask image */
+       uchar*          bmask;  /* mask buffer */
+
+       int             ncmp;
+       uchar           bcmp[4];
+} Filldata;
+
+void
+fillscan(Filldata *f, Point p0)
+{
+       int x, y;
+       uchar *b;
+
+       x = p0.x;
+       y = p0.y;
+       b = f->bmask + y*f->wmask;
+       if(b[x/8] & 0x80>>(x%8))
+               return;
+
+       if(f->yscan != y){
+               draw(f->iscan, f->iscan->r, f->dst, nil, Pt(f->r.min.x, f->r.min.y+y));
+               if(unloadimage(f->iscan, f->iscan->r, f->bscan, f->wscan) < 0)
+                       return;
+               f->yscan = y;
+       }
+
+       for(x = p0.x; x >= 0; x--){
+               if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
+                       break;
+               b[x/8] |= 0x80>>(x%8);
+       }
+       for(x = p0.x+1; x < f->r0.max.x; x++){
+               if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
+                       break;
+               b[x/8] |= 0x80>>(x%8);
+       }
+
+       y = p0.y-1;
+       if(y >= 0){
+               for(x = p0.x; x >= 0; x--){
+                       if((b[x/8] & 0x80>>(x%8)) == 0)
+                               break;
+                       fillscan(f, Pt(x, y));
+               }
+               for(x = p0.x+1; x < f->r0.max.x; x++){
+                       if((b[x/8] & 0x80>>(x%8)) == 0)
+                               break;
+                       fillscan(f, Pt(x, y));
+               }
+       }
+
+       y = p0.y+1;
+       if(y < f->r0.max.y){
+               for(x = p0.x; x >= 0; x--){
+                       if((b[x/8] & 0x80>>(x%8)) == 0)
+                               break;
+                       fillscan(f, Pt(x, y));
+               }
+               for(x = p0.x+1; x < f->r0.max.x; x++){
+                       if((b[x/8] & 0x80>>(x%8)) == 0)
+                               break;
+                       fillscan(f, Pt(x, y));
+               }
+       }
+}
+
+void
+floodfill(Image *dst, Rectangle r, Point p, Image *src)
+{
+       Filldata f;
+
+       if(!rectclip(&r, dst->r))
+               return;
+       if(!ptinrect(p, r))
+               return;
+       memset(&f, 0, sizeof(f));
+       f.dst = dst;
+       f.r = r;
+       f.r0 = rectsubpt(r, r.min);
+       f.wmask = bytesperline(f.r0, 1);
+       f.nmask = f.wmask*f.r0.max.y;
+       if((f.bmask = mallocz(f.nmask, 1)) == nil)
+               goto out;
+       if((f.imask = allocimage(display, f.r0, GREY1, 0, DNofill)) == nil)
+               goto out;
+
+       r = f.r0;
+       r.max.y = 1;
+       if((f.iscan = allocimage(display, r, RGB24, 0, DNofill)) == nil)
+               goto out;
+       f.yscan = -1;
+       f.wscan = bytesperline(f.iscan->r, f.iscan->depth);
+       if((f.bscan = mallocz(f.wscan, 0)) == nil)
+               goto out;
+
+       r = Rect(0,0,1,1);
+       f.ncmp = (f.iscan->depth+7) / 8;
+       draw(f.iscan, r, dst, nil, p);
+       if(unloadimage(f.iscan, r, f.bcmp, sizeof(f.bcmp)) < 0)
+               goto out;
+
+       fillscan(&f, subpt(p, f.r.min));
+
+       loadimage(f.imask, f.imask->r, f.bmask, f.nmask);
+       draw(f.dst, f.r, src, f.imask, f.imask->r.min);
+out:
+       free(f.bmask);
+       free(f.bscan);
+       if(f.iscan)
+               freeimage(f.iscan);
+       if(f.imask)
+               freeimage(f.imask);
+}
+
+void
+translate(Point d)
+{
+       Rectangle r, nr;
+
+       if(canvas==nil || d.x==0 && d.y==0)
+               return;
+       r = c2sr(canvas->r);
+       nr = rectaddpt(r, d);
+       rectclip(&r, screen->clipr);
+       draw(screen, rectaddpt(r, d), screen, nil, r.min);
+       zoomdraw(screen, nr, rectaddpt(r, d), back, canvas, canvas->r.min, zoom);
+       gendrawdiff(screen, screen->r, nr, back, ZP, nil, ZP, SoverD);
+       spos = addpt(spos, d);
+       flushimage(display, 1);
+}
+
+void
+setzoom(Point o, int z)
+{
+       if(z < 1)
+               return;
+       cpos = s2c(o);
+       spos = o;
+       zoom = z;
+       update(nil);
+}
+
+void
+center(void)
+{
+       cpos = ZP;
+       if(canvas)
+               cpos = addpt(canvas->r.min, 
+                       divpt(subpt(canvas->r.max, canvas->r.min), 2));
+       spos = addpt(screen->r.min,
+               divpt(subpt(screen->r.max, screen->r.min), 2));
+       update(nil);
+}
+
+void
+drawpal(void)
+{
+       Rectangle r, rr;
+       int i;
+
+       r = screen->r;
+       r.min.y = r.max.y - 20;
+       replclipr(screen, 0, r);
+
+       penr = r;
+       penr.min.x = r.max.x - NBRUSH*Dy(r);
+
+       palr = r;
+       palr.max.x = penr.min.x;
+
+       r = penr;
+       draw(screen, r, back, nil, ZP);
+       for(i=0; i<NBRUSH; i++){
+               r.max.x = penr.min.x + (i+1)*Dx(penr) / NBRUSH;
+               rr = r;
+               if(i == brush)
+                       rr.min.y += Dy(r)/3;
+               if(i == NBRUSH-1){
+                       /* last is special brush for fill draw */
+                       draw(screen, rr, ink, nil, ZP);
+               } else {
+                       rr.min = addpt(rr.min, divpt(subpt(rr.max, rr.min), 2));
+                       rr.max = rr.min;
+                       strokedraw(screen, rr, ink, i);
+               }
+               r.min.x = r.max.x;
+       }
+
+       r = palr;
+       for(i=1; i<=nelem(pal); i++){
+               r.max.x = palr.min.x + i*Dx(palr) / nelem(pal);
+               rr = r;
+               if(ink == pal[i-1])
+                       rr.min.y += Dy(r)/3;
+               draw(screen, rr, pal[i-1], nil, ZP);
+               gendrawdiff(screen, r, rr, back, ZP, nil, ZP, SoverD);
+               r.min.x = r.max.x;
+       }
+
+       r = screen->r;
+       r.max.y -= Dy(palr);
+       replclipr(screen, 0, r);
+}
+
+int
+hitpal(Mouse m)
+{
+       if(ptinrect(m.xy, penr)){
+               if(m.buttons & 7){
+                       brush = ((m.xy.x - penr.min.x) * NBRUSH) / Dx(penr);
+                       drawpal();
+               }
+               return 1;
+       }
+       if(ptinrect(m.xy, palr)){
+               Image *col;
+
+               col = pal[(m.xy.x - palr.min.x) * nelem(pal) / Dx(palr)];
+               switch(m.buttons & 7){
+               case 1:
+                       ink = col;
+                       drawpal();
+                       break;
+               case 2:
+                       back = col;
+                       drawpal();
+                       update(nil);
+                       break;
+               }
+               return 1;
+       }
        return 0;
 }
 
+void
+catch(void *, char *msg)
+{
+       if(strstr(msg, "closed pipe"))
+               noted(NCONT);
+       noted(NDFLT);
+}
+
 int
-saveimg(char *name)
+pipeline(char *fmt, ...)
 {
-       int fd;
+       char buf[1024];
+       va_list a;
+       int p[2];
 
-       if((fd = create(name, OWRITE|OTRUNC, 0666)) < 0)
+       va_start(a, fmt);
+       vsnprint(buf, sizeof(buf), fmt, a);
+       va_end(a);
+       if(pipe(p) < 0)
                return -1;
-       writeimage(fd, screen, 0);
-       close(fd);
-       return 0;
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFREND)){
+       case -1:
+               close(p[0]);
+               close(p[1]);
+               return -1;
+       case 0:
+               close(p[1]);
+               dup(p[0], 0);
+               dup(p[0], 1);
+               close(p[0]);
+               execl("/bin/rc", "rc", "-c", buf, nil);
+               exits("exec");
+       }
+       close(p[0]);
+       return p[1];
+}
+
+void
+usage(void)
+{
+       fprint(2, "usage: %s [ file ]\n", argv0);
+       exits("usage");
 }
 
 void
 main(int argc, char *argv[])
 {
+       char *s, buf[1024];
+       Rectangle r;
+       Image *img;
+       int i, fd;
        Event e;
-       Point last;
-       int b = 1;
-       int c = 0;
-       int cn, f;
-       int haslast = 0;
-       char brush[128];
-       char color[NCOLORS];
-       char file[128];
-       char fill[NCOLORS];
-       
-       if(initdraw(0, 0, "paint") < 0){
-               fprint(2, "paint: initdraw failed: %r\n");
-               exits("initdraw");
-       }
-       einit(Emouse | Ekeyboard);
-       draw(screen, screen->r, display->white, 0, ZP);
-       flushimage(display, 1);
+       Mouse m;
+       Point p, d;
 
-       colors[0] = display->black;
-       colors[1] = display->white;
-       colors[2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DRed);
-       colors[3] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreen);
-       colors[4] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DBlue);
-       colors[5] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
-
-       ARGBEGIN{
-       default:
-               goto Usage;
-       }ARGEND
-       switch(argc){
+       ARGBEGIN {
        default:
-       Usage:
-               fprint(2, "Usage: [file]\n");
-               exits("usage");
-       case 0:
-               break;
-       case 1:
-               strncpy(file, argv[0], sizeof(argv[0]));
-               if(loadimg(file) < 0)
-                       sysfatal("%r");
-               break;
+               usage();
+       } ARGEND;
+
+       if(argc == 1)
+               filename = strdup(argv[0]);
+       else if(argc != 0)
+               usage();        
+
+       if(initdraw(0, 0, "paint") < 0)
+               sysfatal("initdraw: %r");
+
+       if(filename){
+               if((fd = open(filename, OREAD)) < 0)
+                       sysfatal("open: %r");
+               if((canvas = readimage(display, fd, 0)) == nil)
+                       sysfatal("readimage: %r");
+               close(fd);
        }
 
-       while(1){
+       /* palette initialization */
+       for(i=0; i<nelem(pal); i++){
+               pal[i] = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1,
+                       c64[i % nelem(c64)]<<8 | 0xFF);
+               if(pal[i] == nil)
+                       sysfatal("allocimage: %r");
+       }
+       ink = pal[0];
+       back = pal[1];
+       drawpal();
+       center();
+
+       einit(Emouse | Ekeyboard);
+
+       notify(catch);
+       for(;;) {
                switch(event(&e)){
                case Emouse:
-                       if(e.mouse.buttons & 1){
-                               if(haslast)
-                                       line(screen, last, e.mouse.xy, Enddisc, Enddisc, b, colors[c], ZP);
-                               else
-                                       fillellipse(screen, e.mouse.xy, b, b, colors[c], ZP);
-                               last = e.mouse.xy;
-                               haslast = 1;
-                               flushimage(display, 1);
-                       } else
-                               haslast = 0;
+                       if(hitpal(e.mouse))
+                               continue;
+
+                       img = ink;
+                       switch(e.mouse.buttons & 7){
+                       case 2:
+                               img = back;
+                               /* no break */
+                       case 1:
+                               p = s2c(e.mouse.xy);
+                               if(brush == NBRUSH-1){
+                                       /* flood fill brush */
+                                       if(canvas == nil || !ptinrect(p, canvas->r)){
+                                               back = img;
+                                               drawpal();
+                                               update(nil);
+                                               break;
+                                       }
+                                       r = canvas->r;
+                                       save(r, 1);
+                                       floodfill(canvas, r, p, img);
+                                       update(&r);
+
+                                       /* wait for mouse release */
+                                       while(event(&e) == Emouse && (e.mouse.buttons & 7) != 0)
+                                               ;
+                                       break;
+                               }
+                               r = strokerect(Rpt(p, p), brush);
+                               expand(r);
+                               save(r, 1);
+                               strokedraw(canvas, Rpt(p, p), img, brush);
+                               update(&r);
+                               for(;;){
+                                       m = e.mouse;
+                                       if(event(&e) != Emouse)
+                                               break;
+                                       if((e.mouse.buttons ^ m.buttons) & 7)
+                                               break;
+                                       d = s2c(e.mouse.xy);
+                                       if(eqpt(d, p))
+                                               continue;
+                                       r = strokerect(Rpt(p, d), brush);
+                                       expand(r);
+                                       save(r, 0);
+                                       strokedraw(canvas, Rpt(p, d), img, brush);
+                                       update(&r);
+                                       p = d;
+                               }
+                               break;
+                       case 4:
+                               for(;;){
+                                       m = e.mouse;
+                                       if(event(&e) != Emouse)
+                                               break;
+                                       if((e.mouse.buttons & 7) != 4)
+                                               break;
+                                       translate(subpt(e.mouse.xy, m.xy));
+                               }
+                               break;
+                       }
                        break;
                case Ekeyboard:
-                       if(e.kbdc == 'b'){
-                               if(eenter("Brush", brush, sizeof(brush), &e.mouse) <= 0)
+                       switch(e.kbdc){
+                       case Kesc:
+                               zoom = 1;
+                               center();
+                               break;
+                       case '+':
+                               if(zoom < 0x1000)
+                                       setzoom(e.mouse.xy, zoom*2);
+                               break;
+                       case '-':
+                               if(zoom > 1)
+                                       setzoom(e.mouse.xy, zoom/2);
+                               break;
+                       case 'c':
+                               if(canvas == nil)
                                        break;
-                               b = atoi(brush);
-                       }
-                       if(e.kbdc == 'c'){
-                               if(eenter("Color", color, sizeof(color), &e.mouse) <= 0)
+                               save(canvas->r, 1);
+                               freeimage(canvas);
+                               canvas = nil;
+                               update(nil);
+                               break;
+                       case 'u':
+                               restore(16);
+                               break;
+                       case 'f':
+                               brush = NBRUSH-1;
+                               drawpal();
+                               break;
+                       case '0': case '1': case '2': case '3': case '4':
+                       case '5': case '6': case '7': case '8': case '9':
+                               brush = e.kbdc - '0';
+                               drawpal();
+                               break;
+                       default:
+                               if(e.kbdc == Kdel)
+                                       e.kbdc = 'q';
+                               buf[0] = 0;
+                               if(filename && (e.kbdc == 'r' || e.kbdc == 'w'))
+                                       snprint(buf, sizeof(buf), "%C %s", e.kbdc, filename);
+                               else if(e.kbdc > 0x20 && e.kbdc < 0x7f)
+                                       snprint(buf, sizeof(buf), "%C", e.kbdc);
+                               if(eenter("Cmd", buf, sizeof(buf), &e.mouse) <= 0)
                                        break;
-                               cn = atoi(color);
-                               if(cn >= 0 && cn < NCOLORS)
-                                       c = cn;
-                       }
-                       if(e.kbdc == 'f'){
-                               if(eenter("Fill", fill, sizeof(fill), &e.mouse) <= 0)
+                               if(strcmp(buf, "q") == 0)
+                                       exits(nil);
+                               s = buf+1;
+                               while(*s == ' ' || *s == '\t')
+                                       s++;
+                               if(*s == 0)
                                        break;
-                               f = atoi(fill);
-                               if(f >= 0 && f < NCOLORS)
-                                       draw(screen, screen->r, colors[f], 0, ZP);
-                       }
-                       if(e.kbdc == 'o'){
-                               if(eenter("Open file", file, sizeof(file), &e.mouse) <= 0)
+                               switch(buf[0]){
+                               case 'r':
+                                       if((fd = open(s, OREAD)) < 0){
+                                       Error:
+                                               snprint(buf, sizeof(buf), "%r");
+                                               eenter(buf, nil, 0, &e.mouse);
+                                               break;
+                                       }
+                                       free(filename);
+                                       filename = strdup(s);
+                               Readimage:
+                                       unlockdisplay(display);
+                                       img = readimage(display, fd, 1);
+                                       close(fd);
+                                       lockdisplay(display);
+                                       if(img == nil){
+                                               werrstr("readimage: %r");
+                                               goto Error;
+                                       }
+                                       if(canvas){
+                                               save(canvas->r, 1);
+                                               freeimage(canvas);
+                                       }
+                                       canvas = img;
+                                       center();
                                        break;
-                               if(loadimg(file) < 0){
-                                       rerrstr(file, sizeof(file));
-                                       eenter(file, 0, 0, &e.mouse);
-                               }
-                       }
-                       if(e.kbdc == 'q')
-                               exits(nil);
-                       if(e.kbdc == 's'){
-                               if(eenter("Save to", file, sizeof(file), &e.mouse) <= 0)
+                               case 'w':
+                                       if((fd = create(s, OWRITE, 0660)) < 0)
+                                               goto Error;
+                                       free(filename);
+                                       filename = strdup(s);
+                               Writeimage:
+                                       if(canvas)
+                                       if(writeimage(fd, canvas, 0) < 0){
+                                               close(fd);
+                                               werrstr("writeimage: %r");
+                                               goto Error;
+                                       }
+                                       close(fd);
                                        break;
-                               if(saveimg(file) < 0){
-                                       rerrstr(file, sizeof(file));
-                                       eenter(file, 0, 0, &e.mouse);
+                               case '<':
+                                       if((fd = pipeline("%s", s)) < 0)
+                                               goto Error;
+                                       goto Readimage;
+                               case '>':
+                                       if((fd = pipeline("%s", s)) < 0)
+                                               goto Error;
+                                       goto Writeimage;
+                               case '|':
+                                       if(canvas == nil)
+                                               break;
+                                       if((fd = pipeline("%s", s)) < 0)
+                                               goto Error;
+                                       switch(rfork(RFMEM|RFPROC|RFFDG)){
+                                       case -1:
+                                               close(fd);
+                                               werrstr("rfork: %r");
+                                               goto Error;
+                                       case 0:
+                                               writeimage(fd, canvas, 1);
+                                               exits(nil);
+                                       }
+                                       goto Readimage;
                                }
+                               break;
                        }
                        break;
                }
        }
 }
+
+void
+eresized(int)
+{
+       if(getwindow(display, Refnone) < 0)
+               sysfatal("resize failed");
+       drawpal();
+       update(nil);
+}