ref: d9a05ced4b49ce56600d206adab35bd5a5eeee89
author: qwx <qwx@sciops.net>
date: Sat Dec 4 16:39:46 EST 2021
import the one true paint(1)
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,6 @@
+</$objtype/mkfile
+BIN=$home/bin/$objtype
+TARG=prez
+OFILES=prez.$O
+HFILES=
+</sys/src/cmd/mkone
--- /dev/null
+++ b/prez.c
@@ -1,0 +1,847 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+
+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 */
+
+enum {
+ NBRUSH = 10+1,
+};
+
+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)
+{
+ 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
+alphachan(ulong chan)
+{
+ for(; chan; chan >>= 8)
+ if(TYPE(chan) == CAlpha)
+ return 1;
+ return 0;
+}
+
+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);
+ }
+ flushimage(display, 1);
+}
+
+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)
+{
+ int i;
+ u32int c;
+ char buf[16], *e;
+
+ 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;
+
+ i = (m.xy.x - palr.min.x) * nelem(pal) / Dx(palr);
+ col = pal[i];
+ switch(m.buttons & 7){
+ case 1:
+ ink = col;
+ drawpal();
+ break;
+ case 2:
+ back = col;
+ drawpal();
+ update(nil);
+ break;
+ case 4:
+ snprint(buf, sizeof(buf), "%06x", c64[i]);
+ if(eenter("Hex", buf, sizeof(buf), &m) == 6){
+ c = strtoll(buf, &e, 16);
+ if(*e == 0){
+ c64[i] = c;
+ freeimage(pal[i]);
+ pal[i] = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1, c<<8|0xff);
+ drawpal();
+ }
+ }
+ break;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+void
+catch(void *, char *msg)
+{
+ if(strstr(msg, "closed pipe"))
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+int
+pipeline(char *fmt, ...)
+{
+ char buf[1024];
+ va_list a;
+ int p[2];
+
+ va_start(a, fmt);
+ vsnprint(buf, sizeof(buf), fmt, a);
+ va_end(a);
+ if(pipe(p) < 0)
+ return -1;
+ 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;
+ Mouse m;
+ Point p, d;
+
+ ARGBEGIN {
+ default:
+ 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);
+ }
+
+ /* 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(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:
+ 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;
+ 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;
+ if(strcmp(buf, "q") == 0)
+ exits(nil);
+ s = buf+1;
+ while(*s == ' ' || *s == '\t')
+ s++;
+ if(*s == 0)
+ break;
+ 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;
+ 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;
+ 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);
+}