]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/paint.c
paint: limit zoom to 12 steps
[plan9front.git] / sys / src / cmd / paint.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5 #include <keyboard.h>
6
7 char *filename;
8 int zoom = 1;
9 int brush = 1;
10 Point spos;             /* position on screen */
11 Point cpos;             /* position on canvas */
12 Image *canvas;
13 Image *ink;
14 Image *back;
15 Image *pal[16];         /* palette */
16 Rectangle palr;         /* palette rect on screen */
17 Rectangle penr;         /* pen size rect on screen */
18 enum {
19         NBRUSH = 10+1,
20 };
21
22 int nundo = 0;
23 Image *undo[1024];
24
25 int c64[] = {           /* c64 color palette */
26         0x000000,
27         0xFFFFFF,
28         0x68372B,
29         0x70A4B2,
30         0x6F3D86,
31         0x588D43,
32         0x352879,
33         0xB8C76F,
34         0x6F4F25,
35         0x433900,
36         0x9A6759,
37         0x444444,
38         0x6C6C6C,
39         0x9AD284,
40         0x6C5EB5,
41         0x959595,
42 };
43
44 /*
45  * A draw operation that touches only the area contained in bot but not in top.
46  * mp and sp get aligned with bot.min.
47  */
48 static void
49 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
50         Image *src, Point sp, Image *mask, Point mp, int op)
51 {
52         Rectangle r;
53         Point origin;
54         Point delta;
55
56         if(Dx(bot)*Dy(bot) == 0)
57                 return;
58
59         /* no points in bot - top */
60         if(rectinrect(bot, top))
61                 return;
62
63         /* bot - top ≡ bot */
64         if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
65                 gendrawop(dst, bot, src, sp, mask, mp, op);
66                 return;
67         }
68
69         origin = bot.min;
70         /* split bot into rectangles that don't intersect top */
71         /* left side */
72         if(bot.min.x < top.min.x){
73                 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
74                 delta = subpt(r.min, origin);
75                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
76                 bot.min.x = top.min.x;
77         }
78
79         /* right side */
80         if(bot.max.x > top.max.x){
81                 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
82                 delta = subpt(r.min, origin);
83                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
84                 bot.max.x = top.max.x;
85         }
86
87         /* top */
88         if(bot.min.y < top.min.y){
89                 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
90                 delta = subpt(r.min, origin);
91                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
92                 bot.min.y = top.min.y;
93         }
94
95         /* bottom */
96         if(bot.max.y > top.max.y){
97                 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
98                 delta = subpt(r.min, origin);
99                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
100                 bot.max.y = top.max.y;
101         }
102 }
103
104 int
105 alphachan(ulong chan)
106 {
107         for(; chan; chan >>= 8)
108                 if(TYPE(chan) == CAlpha)
109                         return 1;
110         return 0;
111 }
112
113 void
114 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
115 {
116         Rectangle dr;
117         Image *t;
118         Point a;
119         int w;
120
121         a = ZP;
122         if(r.min.x < d->r.min.x){
123                 sp.x += (d->r.min.x - r.min.x)/f;
124                 a.x = (d->r.min.x - r.min.x)%f;
125                 r.min.x = d->r.min.x;
126         }
127         if(r.min.y < d->r.min.y){
128                 sp.y += (d->r.min.y - r.min.y)/f;
129                 a.y = (d->r.min.y - r.min.y)%f;
130                 r.min.y = d->r.min.y;
131         }
132         rectclip(&r, d->r);
133         w = s->r.max.x - sp.x;
134         if(w > Dx(r))
135                 w = Dx(r);
136         dr = r;
137         dr.max.x = dr.min.x+w;
138         if(!alphachan(s->chan))
139                 b = nil;
140         if(f <= 1){
141                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
142                 gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
143                 return;
144         }
145         if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
146                 return;
147         for(; dr.min.y < r.max.y; dr.min.y++){
148                 dr.max.y = dr.min.y+1;
149                 draw(t, dr, s, nil, sp);
150                 if(++a.y == f){
151                         a.y = 0;
152                         sp.y++;
153                 }
154         }
155         dr = r;
156         for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
157                 dr.max.x = dr.min.x+1;
158                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
159                 gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
160                 for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
161                         dr.max.x = dr.min.x+1;
162                         gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
163                 }
164                 a.x = 0;
165         }
166         freeimage(t);
167 }
168
169 Point
170 s2c(Point p){
171         p = subpt(p, spos);
172         if(p.x < 0) p.x -= zoom-1;
173         if(p.y < 0) p.y -= zoom-1;
174         return addpt(divpt(p, zoom), cpos);
175 }
176
177 Point
178 c2s(Point p){
179         return addpt(mulpt(subpt(p, cpos), zoom), spos);
180 }
181
182 Rectangle
183 c2sr(Rectangle r){
184         return Rpt(c2s(r.min), c2s(r.max));
185 }
186
187 void
188 update(Rectangle *rp){
189         if(canvas==nil)
190                 draw(screen, screen->r, back, nil, ZP);
191         else {
192                 if(rp == nil)
193                         rp = &canvas->r;
194                 gendrawdiff(screen, screen->r, c2sr(canvas->r), back, ZP, nil, ZP, SoverD);
195                 zoomdraw(screen, c2sr(*rp), ZR, back, canvas, rp->min, zoom);
196         }
197         flushimage(display, 1);
198 }
199
200 void
201 expand(Rectangle r)
202 {
203         Rectangle nr;
204         Image *tmp;
205
206         if(canvas==nil){
207                 if((canvas = allocimage(display, r, screen->chan, 0, DNofill)) == nil)
208                         sysfatal("allocimage: %r");
209                 draw(canvas, canvas->r, back, nil, ZP);
210                 return;
211         }
212         nr = canvas->r;
213         combinerect(&nr, r);
214         if(eqrect(nr, canvas->r))
215                 return;
216         if((tmp = allocimage(display, nr, canvas->chan, 0, DNofill)) == nil)
217                 return;
218         draw(tmp, canvas->r, canvas, nil, canvas->r.min);
219         gendrawdiff(tmp, tmp->r, canvas->r, back, ZP, nil, ZP, SoverD);
220         freeimage(canvas);
221         canvas = tmp;
222 }
223
224 void
225 save(Rectangle r, int mark)
226 {
227         Image *tmp;
228         int x;
229
230         if(mark){
231                 x = nundo++ % nelem(undo);
232                 if(undo[x])
233                         freeimage(undo[x]);
234                 undo[x] = nil;
235         }
236         if(canvas==nil || nundo<0)
237                 return;
238         if(!rectclip(&r, canvas->r))
239                 return;
240         if((tmp = allocimage(display, r, canvas->chan, 0, DNofill)) == nil)
241                 return;
242         draw(tmp, r, canvas, nil, r.min);
243         x = nundo++ % nelem(undo);
244         if(undo[x])
245                 freeimage(undo[x]);
246         undo[x] = tmp;
247 }
248
249 void
250 restore(int n)
251 {
252         Image *tmp;
253         int x;
254
255         while(nundo > 0){
256                 if(n-- == 0)
257                         return;
258                 x = --nundo % nelem(undo);
259                 if((tmp = undo[x]) == nil)
260                         return;
261                 undo[x] = nil;
262                 expand(tmp->r);
263                 draw(canvas, tmp->r, tmp, nil, tmp->r.min);
264                 update(&tmp->r);
265                 freeimage(tmp);
266         }
267 }
268
269 typedef struct {
270         Rectangle       r;
271         Rectangle       r0;
272         Image*          dst;
273
274         int             yscan;  /* current scanline */
275         int             wscan;  /* bscan width in bytes */
276         Image*          iscan;  /* scanline image */
277         uchar*          bscan;  /* scanline buffer */
278
279         int             nmask;  /* size of bmask in bytes */
280         int             wmask;  /* width of bmask in bytes */
281         Image*          imask;  /* mask image */
282         uchar*          bmask;  /* mask buffer */
283
284         int             ncmp;
285         uchar           bcmp[4];
286 } Filldata;
287
288 void
289 fillscan(Filldata *f, Point p0)
290 {
291         int x, y;
292         uchar *b;
293
294         x = p0.x;
295         y = p0.y;
296         b = f->bmask + y*f->wmask;
297         if(b[x/8] & 0x80>>(x%8))
298                 return;
299
300         if(f->yscan != y){
301                 draw(f->iscan, f->iscan->r, f->dst, nil, Pt(f->r.min.x, f->r.min.y+y));
302                 if(unloadimage(f->iscan, f->iscan->r, f->bscan, f->wscan) < 0)
303                         return;
304                 f->yscan = y;
305         }
306
307         for(x = p0.x; x >= 0; x--){
308                 if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
309                         break;
310                 b[x/8] |= 0x80>>(x%8);
311         }
312         for(x = p0.x+1; x < f->r0.max.x; x++){
313                 if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
314                         break;
315                 b[x/8] |= 0x80>>(x%8);
316         }
317
318         y = p0.y-1;
319         if(y >= 0){
320                 for(x = p0.x; x >= 0; x--){
321                         if((b[x/8] & 0x80>>(x%8)) == 0)
322                                 break;
323                         fillscan(f, Pt(x, y));
324                 }
325                 for(x = p0.x+1; x < f->r0.max.x; x++){
326                         if((b[x/8] & 0x80>>(x%8)) == 0)
327                                 break;
328                         fillscan(f, Pt(x, y));
329                 }
330         }
331
332         y = p0.y+1;
333         if(y < f->r0.max.y){
334                 for(x = p0.x; x >= 0; x--){
335                         if((b[x/8] & 0x80>>(x%8)) == 0)
336                                 break;
337                         fillscan(f, Pt(x, y));
338                 }
339                 for(x = p0.x+1; x < f->r0.max.x; x++){
340                         if((b[x/8] & 0x80>>(x%8)) == 0)
341                                 break;
342                         fillscan(f, Pt(x, y));
343                 }
344         }
345 }
346
347 void
348 floodfill(Image *dst, Rectangle r, Point p, Image *src)
349 {
350         Filldata f;
351
352         if(!rectclip(&r, dst->r))
353                 return;
354         if(!ptinrect(p, r))
355                 return;
356         memset(&f, 0, sizeof(f));
357         f.dst = dst;
358         f.r = r;
359         f.r0 = rectsubpt(r, r.min);
360         f.wmask = bytesperline(f.r0, 1);
361         f.nmask = f.wmask*f.r0.max.y;
362         if((f.bmask = mallocz(f.nmask, 1)) == nil)
363                 goto out;
364         if((f.imask = allocimage(display, f.r0, GREY1, 0, DNofill)) == nil)
365                 goto out;
366
367         r = f.r0;
368         r.max.y = 1;
369         if((f.iscan = allocimage(display, r, RGB24, 0, DNofill)) == nil)
370                 goto out;
371         f.yscan = -1;
372         f.wscan = bytesperline(f.iscan->r, f.iscan->depth);
373         if((f.bscan = mallocz(f.wscan, 0)) == nil)
374                 goto out;
375
376         r = Rect(0,0,1,1);
377         f.ncmp = (f.iscan->depth+7) / 8;
378         draw(f.iscan, r, dst, nil, p);
379         if(unloadimage(f.iscan, r, f.bcmp, sizeof(f.bcmp)) < 0)
380                 goto out;
381
382         fillscan(&f, subpt(p, f.r.min));
383
384         loadimage(f.imask, f.imask->r, f.bmask, f.nmask);
385         draw(f.dst, f.r, src, f.imask, f.imask->r.min);
386 out:
387         free(f.bmask);
388         free(f.bscan);
389         if(f.iscan)
390                 freeimage(f.iscan);
391         if(f.imask)
392                 freeimage(f.imask);
393 }
394
395 void
396 translate(Point d)
397 {
398         Rectangle r, nr;
399
400         if(canvas==nil || d.x==0 && d.y==0)
401                 return;
402         r = c2sr(canvas->r);
403         nr = rectaddpt(r, d);
404         rectclip(&r, screen->clipr);
405         draw(screen, rectaddpt(r, d), screen, nil, r.min);
406         zoomdraw(screen, nr, rectaddpt(r, d), back, canvas, canvas->r.min, zoom);
407         gendrawdiff(screen, screen->r, nr, back, ZP, nil, ZP, SoverD);
408         spos = addpt(spos, d);
409         flushimage(display, 1);
410 }
411
412 void
413 setzoom(Point o, int z)
414 {
415         if(z < 1)
416                 return;
417         cpos = s2c(o);
418         spos = o;
419         zoom = z;
420         update(nil);
421 }
422
423 void
424 center(void)
425 {
426         cpos = ZP;
427         if(canvas)
428                 cpos = addpt(canvas->r.min, 
429                         divpt(subpt(canvas->r.max, canvas->r.min), 2));
430         spos = addpt(screen->r.min,
431                 divpt(subpt(screen->r.max, screen->r.min), 2));
432         update(nil);
433 }
434
435 void
436 drawpal(void)
437 {
438         Rectangle r, rr;
439         int i;
440
441         r = screen->r;
442         r.min.y = r.max.y - 20;
443         replclipr(screen, 0, r);
444
445         penr = r;
446         penr.min.x = r.max.x - NBRUSH*Dy(r);
447
448         palr = r;
449         palr.max.x = penr.min.x;
450
451         r = penr;
452         draw(screen, r, back, nil, ZP);
453         for(i=0; i<10; i++){
454                 r.max.x = penr.min.x + (i+1)*Dx(penr) / NBRUSH;
455                 rr = r;
456                 if(i == brush)
457                         rr.min.y += Dy(r)/3;
458                 fillellipse(screen, addpt(rr.min, divpt(subpt(rr.max, rr.min), 2)), i, i, ink, ZP);
459                 r.min.x = r.max.x;
460         }
461         r.max.x = penr.min.x + (i+1)*Dx(penr) / NBRUSH;
462         rr = r;
463         if(i == brush)
464                 rr.min.y += Dy(r)/3;
465         draw(screen, rr, ink, nil, ZP);
466
467         r = palr;
468         for(i=1; i<=nelem(pal); i++){
469                 r.max.x = palr.min.x + i*Dx(palr) / nelem(pal);
470                 rr = r;
471                 if(ink == pal[i-1])
472                         rr.min.y += Dy(r)/3;
473                 draw(screen, rr, pal[i-1], nil, ZP);
474                 gendrawdiff(screen, r, rr, back, ZP, nil, ZP, SoverD);
475                 r.min.x = r.max.x;
476         }
477
478         r = screen->r;
479         r.max.y -= Dy(palr);
480         replclipr(screen, 0, r);
481 }
482
483 int
484 hitpal(Mouse m)
485 {
486         if(ptinrect(m.xy, penr)){
487                 if(m.buttons & 7){
488                         brush = ((m.xy.x - penr.min.x) * NBRUSH) / Dx(penr);
489                         drawpal();
490                 }
491                 return 1;
492         }
493         if(ptinrect(m.xy, palr)){
494                 Image *col;
495
496                 col = pal[(m.xy.x - palr.min.x) * nelem(pal) / Dx(palr)];
497                 switch(m.buttons & 7){
498                 case 1:
499                         ink = col;
500                         drawpal();
501                         break;
502                 case 2:
503                         back = col;
504                         drawpal();
505                         update(nil);
506                         break;
507                 }
508                 return 1;
509         }
510         return 0;
511 }
512
513 void
514 catch(void *, char *msg)
515 {
516         if(strstr(msg, "closed pipe"))
517                 noted(NCONT);
518         noted(NDFLT);
519 }
520
521 int
522 pipeline(char *fmt, ...)
523 {
524         char buf[1024];
525         va_list a;
526         int p[2];
527
528         va_start(a, fmt);
529         vsnprint(buf, sizeof(buf), fmt, a);
530         va_end(a);
531         if(pipe(p) < 0)
532                 return -1;
533         switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG)){
534         case -1:
535                 close(p[0]);
536                 close(p[1]);
537                 return -1;
538         case 0:
539                 close(p[1]);
540                 dup(p[0], 0);
541                 dup(p[0], 1);
542                 close(p[0]);
543                 execl("/bin/rc", "rc", "-c", buf, nil);
544                 exits("exec");
545         }
546         close(p[0]);
547         return p[1];
548 }
549
550 void
551 usage(void)
552 {
553         fprint(2, "usage: %s [ file ]\n", argv0);
554         exits("usage");
555 }
556
557 void
558 main(int argc, char *argv[])
559 {
560         char *s, buf[1024];
561         Rectangle r;
562         Image *img;
563         int i, fd;
564         Event e;
565         Mouse m;
566         Point p, d;
567
568         ARGBEGIN {
569         default:
570                 usage();
571         } ARGEND;
572
573         if(argc == 1)
574                 filename = strdup(argv[0]);
575         else if(argc != 0)
576                 usage();        
577
578         if(initdraw(0, 0, "paint") < 0)
579                 sysfatal("initdraw: %r");
580
581         if(filename){
582                 if((fd = open(filename, OREAD)) < 0)
583                         sysfatal("open: %r");
584                 if((canvas = readimage(display, fd, 0)) == nil)
585                         sysfatal("readimage: %r");
586                 close(fd);
587         }
588
589         /* palette initialization */
590         for(i=0; i<nelem(pal); i++){
591                 pal[i] = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1,
592                         c64[i % nelem(c64)]<<8 | 0xFF);
593                 if(pal[i] == nil)
594                         sysfatal("allocimage: %r");
595         }
596         ink = pal[0];
597         back = pal[1];
598         drawpal();
599         center();
600
601         einit(Emouse | Ekeyboard);
602
603         notify(catch);
604         for(;;) {
605                 switch(event(&e)){
606                 case Emouse:
607                         if(hitpal(e.mouse))
608                                 continue;
609
610                         img = ink;
611                         switch(e.mouse.buttons & 7){
612                         case 2:
613                                 img = back;
614                                 /* no break */
615                         case 1:
616                                 p = s2c(e.mouse.xy);
617                                 if(brush >= 10){
618                                         /* flood fill brush */
619                                         if(canvas == nil || !ptinrect(p, canvas->r)){
620                                                 back = img;
621                                                 drawpal();
622                                                 update(nil);
623                                                 break;
624                                         }
625                                         r = canvas->r;
626                                         save(r, 1);
627                                         floodfill(canvas, r, p, img);
628                                         update(&r);
629
630                                         /* wait for mouse release */
631                                         while(event(&e) == Emouse && (e.mouse.buttons & 7) != 0)
632                                                 ;
633                                         break;
634                                 }
635                                 r = Rect(p.x-brush, p.y-brush, p.x+brush+1, p.y+brush+1);
636                                 expand(r);
637                                 save(r, 1);
638                                 fillellipse(canvas, p, brush, brush, img, ZP);
639                                 update(&r);
640                                 for(;;){
641                                         m = e.mouse;
642                                         if(event(&e) != Emouse)
643                                                 break;
644                                         if((e.mouse.buttons ^ m.buttons) & 7)
645                                                 break;
646                                         d = s2c(e.mouse.xy);
647                                         if(eqpt(d, p))
648                                                 continue;
649                                         r = canonrect(Rpt(p, d));
650                                         r.min.x -= brush;
651                                         r.min.y -= brush;
652                                         r.max.x += brush+1;
653                                         r.max.y += brush+1;
654                                         expand(r);
655                                         save(r, 0);
656                                         line(canvas, p, d, Enddisc, Enddisc, brush, img, ZP);
657                                         update(&r);
658                                         p = d;
659                                 }
660                                 break;
661                         case 4:
662                                 for(;;){
663                                         m = e.mouse;
664                                         if(event(&e) != Emouse)
665                                                 break;
666                                         if((e.mouse.buttons & 7) != 4)
667                                                 break;
668                                         translate(subpt(e.mouse.xy, m.xy));
669                                 }
670                                 break;
671                         }
672                         break;
673                 case Ekeyboard:
674                         switch(e.kbdc){
675                         case Kesc:
676                                 zoom = 1;
677                                 center();
678                                 break;
679                         case '+':
680                                 if(zoom < 0x1000)
681                                         setzoom(e.mouse.xy, zoom*2);
682                                 break;
683                         case '-':
684                                 if(zoom > 1)
685                                         setzoom(e.mouse.xy, zoom/2);
686                                 break;
687                         case 'c':
688                                 if(canvas == nil)
689                                         break;
690                                 save(canvas->r, 1);
691                                 freeimage(canvas);
692                                 canvas = nil;
693                                 update(nil);
694                                 break;
695                         case 'u':
696                                 restore(16);
697                                 break;
698                         case 'f':
699                                 brush = 10;
700                                 drawpal();
701                                 break;
702                         case '0': case '1': case '2': case '3': case '4':
703                         case '5': case '6': case '7': case '8': case '9':
704                                 brush = e.kbdc - '0';
705                                 drawpal();
706                                 break;
707                         default:
708                                 if(e.kbdc == Kdel)
709                                         e.kbdc = 'q';
710                                 buf[0] = 0;
711                                 if(filename && (e.kbdc == 'r' || e.kbdc == 'w'))
712                                         snprint(buf, sizeof(buf), "%C %s", e.kbdc, filename);
713                                 else if(e.kbdc > 0x20 && e.kbdc < 0x7f)
714                                         snprint(buf, sizeof(buf), "%C", e.kbdc);
715                                 if(eenter("Cmd", buf, sizeof(buf), &e.mouse) <= 0)
716                                         break;
717                                 if(strcmp(buf, "q") == 0)
718                                         exits(nil);
719                                 s = buf+1;
720                                 while(*s == ' ' || *s == '\t')
721                                         s++;
722                                 if(*s == 0)
723                                         break;
724                                 switch(buf[0]){
725                                 case 'r':
726                                         if((fd = open(s, OREAD)) < 0){
727                                         Error:
728                                                 snprint(buf, sizeof(buf), "%r");
729                                                 eenter(buf, nil, 0, &e.mouse);
730                                                 break;
731                                         }
732                                         free(filename);
733                                         filename = strdup(s);
734                                 Readimage:
735                                         unlockdisplay(display);
736                                         img = readimage(display, fd, 1);
737                                         close(fd);
738                                         lockdisplay(display);
739                                         if(img == nil){
740                                                 werrstr("readimage: %r");
741                                                 goto Error;
742                                         }
743                                         if(canvas){
744                                                 save(canvas->r, 1);
745                                                 freeimage(canvas);
746                                         }
747                                         canvas = img;
748                                         center();
749                                         break;
750                                 case 'w':
751                                         if((fd = create(s, OWRITE, 0660)) < 0)
752                                                 goto Error;
753                                         free(filename);
754                                         filename = strdup(s);
755                                 Writeimage:
756                                         if(canvas)
757                                         if(writeimage(fd, canvas, 0) < 0){
758                                                 close(fd);
759                                                 werrstr("writeimage: %r");
760                                                 goto Error;
761                                         }
762                                         close(fd);
763                                         break;
764                                 case '<':
765                                         if((fd = pipeline("%s", s)) < 0)
766                                                 goto Error;
767                                         goto Readimage;
768                                 case '>':
769                                         if((fd = pipeline("%s", s)) < 0)
770                                                 goto Error;
771                                         goto Writeimage;
772                                 case '|':
773                                         if(canvas == nil)
774                                                 break;
775                                         if((fd = pipeline("%s", s)) < 0)
776                                                 goto Error;
777                                         switch(rfork(RFMEM|RFPROC|RFFDG)){
778                                         case -1:
779                                                 close(fd);
780                                                 werrstr("rfork: %r");
781                                                 goto Error;
782                                         case 0:
783                                                 writeimage(fd, canvas, 1);
784                                                 exits(nil);
785                                         }
786                                         goto Readimage;
787                                 }
788                                 break;
789                         }
790                         break;
791                 }
792         }
793 }
794
795 void
796 eresized(int)
797 {
798         if(getwindow(display, Refnone) < 0)
799                 sysfatal("resize failed");
800         drawpal();
801         update(nil);
802 }