Blob


1 /*
2 This code was taken from 9front repository (https://code.9front.org/hg/plan9front).
3 It is subject to license from 9front, below is a reproduction of the license.
5 Copyright (c) 20XX 9front
7 Permission is hereby granted, free of charge, to any person obtaining a copy
8 of this software and associated documentation files (the "Software"), to deal
9 in the Software without restriction, including without limitation the rights
10 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 copies of the Software, and to permit persons to whom the Software is
12 furnished to do so, subject to the following conditions:
14 The above copyright notice and this permission notice shall be included in all
15 copies or substantial portions of the Software.
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 SOFTWARE.
24 */
25 #include <u.h>
26 #include <libc.h>
27 #include <draw.h>
28 #include <event.h>
29 #include <keyboard.h>
31 /* additional libdraw function needed - defined here to avoid API change */
32 extern int eenter(char*, char*, int, Mouse*);
34 char *filename;
35 int zoom = 1;
36 int brush = 1;
37 Point spos; /* position on screen */
38 Point cpos; /* position on canvas */
39 Image *canvas;
40 Image *ink;
41 Image *back;
42 Image *pal[16]; /* palette */
43 Rectangle palr; /* palette rect on screen */
44 Rectangle penr; /* pen size rect on screen */
46 enum {
47 NBRUSH = 10+1,
48 };
50 int nundo = 0;
51 Image *undo[1024];
53 int c64[] = { /* c64 color palette */
54 0x000000,
55 0xFFFFFF,
56 0x68372B,
57 0x70A4B2,
58 0x6F3D86,
59 0x588D43,
60 0x352879,
61 0xB8C76F,
62 0x6F4F25,
63 0x433900,
64 0x9A6759,
65 0x444444,
66 0x6C6C6C,
67 0x9AD284,
68 0x6C5EB5,
69 0x959595,
70 };
72 /*
73 * get bounding rectnagle for stroke from r.min to r.max with
74 * specified brush (size).
75 */
76 static Rectangle
77 strokerect(Rectangle r, int brush)
78 {
79 r = canonrect(r);
80 return Rect(r.min.x-brush, r.min.y-brush, r.max.x+brush+1, r.max.y+brush+1);
81 }
83 /*
84 * draw stroke from r.min to r.max to dst with color ink and
85 * brush (size).
86 */
87 static void
88 strokedraw(Image *dst, Rectangle r, Image *ink, int brush)
89 {
90 if(!eqpt(r.min, r.max))
91 line(dst, r.min, r.max, Enddisc, Enddisc, brush, ink, ZP);
92 fillellipse(dst, r.max, brush, brush, ink, ZP);
93 }
95 /*
96 * A draw operation that touches only the area contained in bot but not in top.
97 * mp and sp get aligned with bot.min.
98 */
99 static void
100 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
101 Image *src, Point sp, Image *mask, Point mp, int op)
103 Rectangle r;
104 Point origin;
105 Point delta;
107 if(Dx(bot)*Dy(bot) == 0)
108 return;
110 /* no points in bot - top */
111 if(rectinrect(bot, top))
112 return;
114 /* bot - top ≡ bot */
115 if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
116 gendrawop(dst, bot, src, sp, mask, mp, op);
117 return;
120 origin = bot.min;
121 /* split bot into rectangles that don't intersect top */
122 /* left side */
123 if(bot.min.x < top.min.x){
124 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
125 delta = subpt(r.min, origin);
126 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
127 bot.min.x = top.min.x;
130 /* right side */
131 if(bot.max.x > top.max.x){
132 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
133 delta = subpt(r.min, origin);
134 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
135 bot.max.x = top.max.x;
138 /* top */
139 if(bot.min.y < top.min.y){
140 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
141 delta = subpt(r.min, origin);
142 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
143 bot.min.y = top.min.y;
146 /* bottom */
147 if(bot.max.y > top.max.y){
148 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
149 delta = subpt(r.min, origin);
150 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
151 bot.max.y = top.max.y;
155 int
156 alphachan(ulong chan)
158 for(; chan; chan >>= 8)
159 if(TYPE(chan) == CAlpha)
160 return 1;
161 return 0;
164 void
165 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
167 Rectangle dr;
168 Image *t;
169 Point a;
170 int w;
172 a = ZP;
173 if(r.min.x < d->r.min.x){
174 sp.x += (d->r.min.x - r.min.x)/f;
175 a.x = (d->r.min.x - r.min.x)%f;
176 r.min.x = d->r.min.x;
178 if(r.min.y < d->r.min.y){
179 sp.y += (d->r.min.y - r.min.y)/f;
180 a.y = (d->r.min.y - r.min.y)%f;
181 r.min.y = d->r.min.y;
183 rectclip(&r, d->r);
184 w = s->r.max.x - sp.x;
185 if(w > Dx(r))
186 w = Dx(r);
187 dr = r;
188 dr.max.x = dr.min.x+w;
189 if(!alphachan(s->chan))
190 b = nil;
191 if(f <= 1){
192 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
193 gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
194 return;
196 if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
197 return;
198 for(; dr.min.y < r.max.y; dr.min.y++){
199 dr.max.y = dr.min.y+1;
200 draw(t, dr, s, nil, sp);
201 if(++a.y == f){
202 a.y = 0;
203 sp.y++;
206 dr = r;
207 for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
208 dr.max.x = dr.min.x+1;
209 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
210 gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
211 for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
212 dr.max.x = dr.min.x+1;
213 gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
215 a.x = 0;
217 freeimage(t);
220 Point
221 s2c(Point p){
222 p = subpt(p, spos);
223 if(p.x < 0) p.x -= zoom-1;
224 if(p.y < 0) p.y -= zoom-1;
225 return addpt(divpt(p, zoom), cpos);
228 Point
229 c2s(Point p){
230 return addpt(mulpt(subpt(p, cpos), zoom), spos);
233 Rectangle
234 c2sr(Rectangle r){
235 return Rpt(c2s(r.min), c2s(r.max));
238 void
239 update(Rectangle *rp){
240 if(canvas==nil)
241 draw(screen, screen->r, back, nil, ZP);
242 else {
243 if(rp == nil)
244 rp = &canvas->r;
245 gendrawdiff(screen, screen->r, c2sr(canvas->r), back, ZP, nil, ZP, SoverD);
246 zoomdraw(screen, c2sr(*rp), ZR, back, canvas, rp->min, zoom);
248 flushimage(display, 1);
251 void
252 expand(Rectangle r)
254 Rectangle nr;
255 Image *tmp;
257 if(canvas==nil){
258 if((canvas = allocimage(display, r, screen->chan, 0, DNofill)) == nil)
259 sysfatal("allocimage: %r");
260 draw(canvas, canvas->r, back, nil, ZP);
261 return;
263 nr = canvas->r;
264 combinerect(&nr, r);
265 if(eqrect(nr, canvas->r))
266 return;
267 if((tmp = allocimage(display, nr, canvas->chan, 0, DNofill)) == nil)
268 return;
269 draw(tmp, canvas->r, canvas, nil, canvas->r.min);
270 gendrawdiff(tmp, tmp->r, canvas->r, back, ZP, nil, ZP, SoverD);
271 freeimage(canvas);
272 canvas = tmp;
275 void
276 save(Rectangle r, int mark)
278 Image *tmp;
279 int x;
281 if(mark){
282 x = nundo++ % nelem(undo);
283 if(undo[x])
284 freeimage(undo[x]);
285 undo[x] = nil;
287 if(canvas==nil || nundo<0)
288 return;
289 if(!rectclip(&r, canvas->r))
290 return;
291 if((tmp = allocimage(display, r, canvas->chan, 0, DNofill)) == nil)
292 return;
293 draw(tmp, r, canvas, nil, r.min);
294 x = nundo++ % nelem(undo);
295 if(undo[x])
296 freeimage(undo[x]);
297 undo[x] = tmp;
300 void
301 restore(int n)
303 Image *tmp;
304 int x;
306 while(nundo > 0){
307 if(n-- == 0)
308 return;
309 x = --nundo % nelem(undo);
310 if((tmp = undo[x]) == nil)
311 return;
312 undo[x] = nil;
313 if(canvas == nil || canvas->chan != tmp->chan){
314 freeimage(canvas);
315 canvas = tmp;
316 update(nil);
317 } else {
318 expand(tmp->r);
319 draw(canvas, tmp->r, tmp, nil, tmp->r.min);
320 update(&tmp->r);
321 freeimage(tmp);
326 typedef struct {
327 Rectangle r;
328 Rectangle r0;
329 Image* dst;
331 int yscan; /* current scanline */
332 int wscan; /* bscan width in bytes */
333 Image* iscan; /* scanline image */
334 uchar* bscan; /* scanline buffer */
336 int nmask; /* size of bmask in bytes */
337 int wmask; /* width of bmask in bytes */
338 Image* imask; /* mask image */
339 uchar* bmask; /* mask buffer */
341 int ncmp;
342 uchar bcmp[4];
343 } Filldata;
345 void
346 fillscan(Filldata *f, Point p0)
348 int x, y;
349 uchar *b;
351 x = p0.x;
352 y = p0.y;
353 b = f->bmask + y*f->wmask;
354 if(b[x/8] & 0x80>>(x%8))
355 return;
357 if(f->yscan != y){
358 draw(f->iscan, f->iscan->r, f->dst, nil, Pt(f->r.min.x, f->r.min.y+y));
359 if(unloadimage(f->iscan, f->iscan->r, f->bscan, f->wscan) < 0)
360 return;
361 f->yscan = y;
364 for(x = p0.x; x >= 0; x--){
365 if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
366 break;
367 b[x/8] |= 0x80>>(x%8);
369 for(x = p0.x+1; x < f->r0.max.x; x++){
370 if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
371 break;
372 b[x/8] |= 0x80>>(x%8);
375 y = p0.y-1;
376 if(y >= 0){
377 for(x = p0.x; x >= 0; x--){
378 if((b[x/8] & 0x80>>(x%8)) == 0)
379 break;
380 fillscan(f, Pt(x, y));
382 for(x = p0.x+1; x < f->r0.max.x; x++){
383 if((b[x/8] & 0x80>>(x%8)) == 0)
384 break;
385 fillscan(f, Pt(x, y));
389 y = p0.y+1;
390 if(y < f->r0.max.y){
391 for(x = p0.x; x >= 0; x--){
392 if((b[x/8] & 0x80>>(x%8)) == 0)
393 break;
394 fillscan(f, Pt(x, y));
396 for(x = p0.x+1; x < f->r0.max.x; x++){
397 if((b[x/8] & 0x80>>(x%8)) == 0)
398 break;
399 fillscan(f, Pt(x, y));
404 void
405 floodfill(Image *dst, Rectangle r, Point p, Image *src)
407 Filldata f;
409 if(!rectclip(&r, dst->r))
410 return;
411 if(!ptinrect(p, r))
412 return;
413 memset(&f, 0, sizeof(f));
414 f.dst = dst;
415 f.r = r;
416 f.r0 = rectsubpt(r, r.min);
417 f.wmask = bytesperline(f.r0, 1);
418 f.nmask = f.wmask*f.r0.max.y;
419 if((f.bmask = mallocz(f.nmask, 1)) == nil)
420 goto out;
421 if((f.imask = allocimage(display, f.r0, GREY1, 0, DNofill)) == nil)
422 goto out;
424 r = f.r0;
425 r.max.y = 1;
426 if((f.iscan = allocimage(display, r, RGB24, 0, DNofill)) == nil)
427 goto out;
428 f.yscan = -1;
429 f.wscan = bytesperline(f.iscan->r, f.iscan->depth);
430 if((f.bscan = mallocz(f.wscan, 0)) == nil)
431 goto out;
433 r = Rect(0,0,1,1);
434 f.ncmp = (f.iscan->depth+7) / 8;
435 draw(f.iscan, r, dst, nil, p);
436 if(unloadimage(f.iscan, r, f.bcmp, sizeof(f.bcmp)) < 0)
437 goto out;
439 fillscan(&f, subpt(p, f.r.min));
441 loadimage(f.imask, f.imask->r, f.bmask, f.nmask);
442 draw(f.dst, f.r, src, f.imask, f.imask->r.min);
443 out:
444 free(f.bmask);
445 free(f.bscan);
446 if(f.iscan)
447 freeimage(f.iscan);
448 if(f.imask)
449 freeimage(f.imask);
452 void
453 translate(Point d)
455 Rectangle r, nr;
457 if(canvas==nil || d.x==0 && d.y==0)
458 return;
459 r = c2sr(canvas->r);
460 nr = rectaddpt(r, d);
461 rectclip(&r, screen->clipr);
462 draw(screen, rectaddpt(r, d), screen, nil, r.min);
463 zoomdraw(screen, nr, rectaddpt(r, d), back, canvas, canvas->r.min, zoom);
464 gendrawdiff(screen, screen->r, nr, back, ZP, nil, ZP, SoverD);
465 spos = addpt(spos, d);
466 flushimage(display, 1);
469 void
470 setzoom(Point o, int z)
472 if(z < 1)
473 return;
474 cpos = s2c(o);
475 spos = o;
476 zoom = z;
477 update(nil);
480 void
481 center(void)
483 cpos = ZP;
484 if(canvas)
485 cpos = addpt(canvas->r.min,
486 divpt(subpt(canvas->r.max, canvas->r.min), 2));
487 spos = addpt(screen->r.min,
488 divpt(subpt(screen->r.max, screen->r.min), 2));
489 update(nil);
492 void
493 drawpal(void)
495 Rectangle r, rr;
496 int i;
498 r = screen->r;
499 r.min.y = r.max.y - 20;
500 replclipr(screen, 0, r);
502 penr = r;
503 penr.min.x = r.max.x - NBRUSH*Dy(r);
505 palr = r;
506 palr.max.x = penr.min.x;
508 r = penr;
509 draw(screen, r, back, nil, ZP);
510 for(i=0; i<NBRUSH; i++){
511 r.max.x = penr.min.x + (i+1)*Dx(penr) / NBRUSH;
512 rr = r;
513 if(i == brush)
514 rr.min.y += Dy(r)/3;
515 if(i == NBRUSH-1){
516 /* last is special brush for fill draw */
517 draw(screen, rr, ink, nil, ZP);
518 } else {
519 rr.min = addpt(rr.min, divpt(subpt(rr.max, rr.min), 2));
520 rr.max = rr.min;
521 strokedraw(screen, rr, ink, i);
523 r.min.x = r.max.x;
526 r = palr;
527 for(i=1; i<=nelem(pal); i++){
528 r.max.x = palr.min.x + i*Dx(palr) / nelem(pal);
529 rr = r;
530 if(ink == pal[i-1])
531 rr.min.y += Dy(r)/3;
532 draw(screen, rr, pal[i-1], nil, ZP);
533 gendrawdiff(screen, r, rr, back, ZP, nil, ZP, SoverD);
534 r.min.x = r.max.x;
537 r = screen->r;
538 r.max.y -= Dy(palr);
539 replclipr(screen, 0, r);
542 int
543 hitpal(Mouse m)
545 if(ptinrect(m.xy, penr)){
546 if(m.buttons & 7){
547 brush = ((m.xy.x - penr.min.x) * NBRUSH) / Dx(penr);
548 drawpal();
550 return 1;
552 if(ptinrect(m.xy, palr)){
553 Image *col;
555 col = pal[(m.xy.x - palr.min.x) * nelem(pal) / Dx(palr)];
556 switch(m.buttons & 7){
557 case 1:
558 ink = col;
559 drawpal();
560 break;
561 case 2:
562 back = col;
563 drawpal();
564 update(nil);
565 break;
567 return 1;
569 return 0;
572 void
573 catch(void * _, char *msg)
575 USED(_);
576 if(strstr(msg, "closed pipe"))
577 noted(NCONT);
578 noted(NDFLT);
581 int
582 pipeline(char *fmt, ...)
584 char buf[1024];
585 va_list a;
586 int p[2];
588 va_start(a, fmt);
589 vsnprint(buf, sizeof(buf), fmt, a);
590 va_end(a);
591 if(pipe(p) < 0)
592 return -1;
593 switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG)){ // RFEND not available in libc port
594 case -1:
595 close(p[0]);
596 close(p[1]);
597 return -1;
598 case 0:
599 close(p[1]);
600 dup(p[0], 0);
601 dup(p[0], 1);
602 close(p[0]);
603 execl("/bin/rc", "rc", "-c", buf, nil);
604 exits("exec");
606 close(p[0]);
607 return p[1];
610 void
611 usage(void)
613 fprint(2, "usage: %s [ file ]\n", argv0);
614 exits("usage");
617 void
618 main(int argc, char *argv[])
620 char *s, buf[1024];
621 Rectangle r;
622 Image *img;
623 int i, fd;
624 Event e;
625 Mouse m;
626 Point p, d;
628 ARGBEGIN {
629 default:
630 usage();
631 } ARGEND;
633 if(argc == 1)
634 filename = strdup(argv[0]);
635 else if(argc != 0)
636 usage();
638 if(initdraw(0, 0, "paint") < 0)
639 sysfatal("initdraw: %r");
641 if(filename){
642 if((fd = open(filename, OREAD)) < 0)
643 sysfatal("open: %r");
644 if((canvas = readimage(display, fd, 0)) == nil)
645 sysfatal("readimage: %r");
646 close(fd);
649 /* palette initialization */
650 for(i=0; i<nelem(pal); i++){
651 pal[i] = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1,
652 c64[i % nelem(c64)]<<8 | 0xFF);
653 if(pal[i] == nil)
654 sysfatal("allocimage: %r");
656 ink = pal[0];
657 back = pal[1];
658 drawpal();
659 center();
661 einit(Emouse | Ekeyboard);
663 notify(catch);
664 for(;;) {
665 switch(event(&e)){
666 case Emouse:
667 if(hitpal(e.mouse))
668 continue;
670 img = ink;
671 switch(e.mouse.buttons & 7){
672 case 2:
673 img = back;
674 /* no break */
675 case 1:
676 p = s2c(e.mouse.xy);
677 if(brush == NBRUSH-1){
678 /* flood fill brush */
679 if(canvas == nil || !ptinrect(p, canvas->r)){
680 back = img;
681 drawpal();
682 update(nil);
683 break;
685 r = canvas->r;
686 save(r, 1);
687 floodfill(canvas, r, p, img);
688 update(&r);
690 /* wait for mouse release */
691 while(event(&e) == Emouse && (e.mouse.buttons & 7) != 0)
693 break;
695 r = strokerect(Rpt(p, p), brush);
696 expand(r);
697 save(r, 1);
698 strokedraw(canvas, Rpt(p, p), img, brush);
699 update(&r);
700 for(;;){
701 m = e.mouse;
702 if(event(&e) != Emouse)
703 break;
704 if((e.mouse.buttons ^ m.buttons) & 7)
705 break;
706 d = s2c(e.mouse.xy);
707 if(eqpt(d, p))
708 continue;
709 r = strokerect(Rpt(p, d), brush);
710 expand(r);
711 save(r, 0);
712 strokedraw(canvas, Rpt(p, d), img, brush);
713 update(&r);
714 p = d;
716 break;
717 case 4:
718 for(;;){
719 m = e.mouse;
720 if(event(&e) != Emouse)
721 break;
722 if((e.mouse.buttons & 7) != 4)
723 break;
724 translate(subpt(e.mouse.xy, m.xy));
726 break;
728 break;
729 case Ekeyboard:
730 switch(e.kbdc){
731 case Kesc:
732 zoom = 1;
733 center();
734 break;
735 case '+':
736 if(zoom < 0x1000)
737 setzoom(e.mouse.xy, zoom*2);
738 break;
739 case '-':
740 if(zoom > 1)
741 setzoom(e.mouse.xy, zoom/2);
742 break;
743 case 'c':
744 if(canvas == nil)
745 break;
746 save(canvas->r, 1);
747 freeimage(canvas);
748 canvas = nil;
749 update(nil);
750 break;
751 case 'u':
752 restore(16);
753 break;
754 case 'f':
755 brush = NBRUSH-1;
756 drawpal();
757 break;
758 case '0': case '1': case '2': case '3': case '4':
759 case '5': case '6': case '7': case '8': case '9':
760 brush = e.kbdc - '0';
761 drawpal();
762 break;
763 default:
764 if(e.kbdc == Kdel)
765 e.kbdc = 'q';
766 buf[0] = 0;
767 if(filename && (e.kbdc == 'r' || e.kbdc == 'w'))
768 snprint(buf, sizeof(buf), "%C %s", e.kbdc, filename);
769 else if(e.kbdc > 0x20 && e.kbdc < 0x7f)
770 snprint(buf, sizeof(buf), "%C", e.kbdc);
771 if(eenter("Cmd", buf, sizeof(buf), &e.mouse) <= 0)
772 break;
773 if(strcmp(buf, "q") == 0)
774 exits(nil);
775 s = buf+1;
776 while(*s == ' ' || *s == '\t')
777 s++;
778 if(*s == 0)
779 break;
780 switch(buf[0]){
781 case 'r':
782 if((fd = open(s, OREAD)) < 0){
783 Error:
784 snprint(buf, sizeof(buf), "%r");
785 eenter(buf, nil, 0, &e.mouse);
786 break;
788 free(filename);
789 filename = strdup(s);
790 Readimage:
791 unlockdisplay(display);
792 img = readimage(display, fd, 1);
793 close(fd);
794 lockdisplay(display);
795 if(img == nil){
796 werrstr("readimage: %r");
797 goto Error;
799 if(canvas){
800 save(canvas->r, 1);
801 freeimage(canvas);
803 canvas = img;
804 center();
805 break;
806 case 'w':
807 if((fd = create(s, OWRITE, 0660)) < 0)
808 goto Error;
809 free(filename);
810 filename = strdup(s);
811 Writeimage:
812 if(canvas)
813 if(writeimage(fd, canvas, 0) < 0){
814 close(fd);
815 werrstr("writeimage: %r");
816 goto Error;
818 close(fd);
819 break;
820 case '<':
821 if((fd = pipeline("%s", s)) < 0)
822 goto Error;
823 goto Readimage;
824 case '>':
825 if((fd = pipeline("%s", s)) < 0)
826 goto Error;
827 goto Writeimage;
828 case '|':
829 if(canvas == nil)
830 break;
831 if((fd = pipeline("%s", s)) < 0)
832 goto Error;
833 switch(rfork(RFMEM|RFPROC|RFFDG)){
834 case -1:
835 close(fd);
836 werrstr("rfork: %r");
837 goto Error;
838 case 0:
839 writeimage(fd, canvas, 1);
840 exits(nil);
842 goto Readimage;
844 break;
846 break;
851 void
852 eresized(int _)
854 USED(_);
855 if(getwindow(display, Refnone) < 0)
856 sysfatal("resize failed");
857 drawpal();
858 update(nil);