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.
4 
5 Copyright (c) 20XX 9front
6 
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:
13 
14 The above copyright notice and this permission notice shall be included in all
15 copies or substantial portions of the Software.
16 
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>
30 
31 /* additional libdraw function needed - defined here to avoid API change */
32 extern int             eenter(char*, char*, int, Mouse*);
33 
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 */
45 
46 enum {
47 	NBRUSH = 10+1,
48 };
49 
50 int nundo = 0;
51 Image *undo[1024];
52 
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 };
71 
72 /*
73  * get bounding rectnagle for stroke from r.min to r.max with
74  * specified brush (size).
75  */
76 static Rectangle
strokerect(Rectangle r,int brush)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 }
82 
83 /*
84  * draw stroke from r.min to r.max to dst with color ink and
85  * brush (size).
86  */
87 static void
strokedraw(Image * dst,Rectangle r,Image * ink,int brush)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 }
94 
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
gendrawdiff(Image * dst,Rectangle bot,Rectangle top,Image * src,Point sp,Image * mask,Point mp,int op)100 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
101 	Image *src, Point sp, Image *mask, Point mp, int op)
102 {
103 	Rectangle r;
104 	Point origin;
105 	Point delta;
106 
107 	if(Dx(bot)*Dy(bot) == 0)
108 		return;
109 
110 	/* no points in bot - top */
111 	if(rectinrect(bot, top))
112 		return;
113 
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;
118 	}
119 
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;
128 	}
129 
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;
136 	}
137 
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;
144 	}
145 
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;
152 	}
153 }
154 
155 int
alphachan(ulong chan)156 alphachan(ulong chan)
157 {
158 	for(; chan; chan >>= 8)
159 		if(TYPE(chan) == CAlpha)
160 			return 1;
161 	return 0;
162 }
163 
164 void
zoomdraw(Image * d,Rectangle r,Rectangle top,Image * b,Image * s,Point sp,int f)165 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
166 {
167 	Rectangle dr;
168 	Image *t;
169 	Point a;
170 	int w;
171 
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;
177 	}
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;
182 	}
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;
195 	}
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++;
204 		}
205 	}
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);
214 		}
215 		a.x = 0;
216 	}
217 	freeimage(t);
218 }
219 
220 Point
s2c(Point p)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);
226 }
227 
228 Point
c2s(Point p)229 c2s(Point p){
230 	return addpt(mulpt(subpt(p, cpos), zoom), spos);
231 }
232 
233 Rectangle
c2sr(Rectangle r)234 c2sr(Rectangle r){
235 	return Rpt(c2s(r.min), c2s(r.max));
236 }
237 
238 void
update(Rectangle * rp)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);
247 	}
248 	flushimage(display, 1);
249 }
250 
251 void
expand(Rectangle r)252 expand(Rectangle r)
253 {
254 	Rectangle nr;
255 	Image *tmp;
256 
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;
262 	}
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;
273 }
274 
275 void
save(Rectangle r,int mark)276 save(Rectangle r, int mark)
277 {
278 	Image *tmp;
279 	int x;
280 
281 	if(mark){
282 		x = nundo++ % nelem(undo);
283 		if(undo[x])
284 			freeimage(undo[x]);
285 		undo[x] = nil;
286 	}
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;
298 }
299 
300 void
restore(int n)301 restore(int n)
302 {
303 	Image *tmp;
304 	int x;
305 
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);
322 		}
323 	}
324 }
325 
326 typedef struct {
327 	Rectangle	r;
328 	Rectangle	r0;
329 	Image*		dst;
330 
331 	int		yscan;	/* current scanline */
332 	int		wscan;	/* bscan width in bytes */
333 	Image*		iscan;	/* scanline image */
334 	uchar*		bscan;	/* scanline buffer */
335 
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 */
340 
341 	int		ncmp;
342 	uchar		bcmp[4];
343 } Filldata;
344 
345 void
fillscan(Filldata * f,Point p0)346 fillscan(Filldata *f, Point p0)
347 {
348 	int x, y;
349 	uchar *b;
350 
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;
356 
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;
362 	}
363 
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);
368 	}
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);
373 	}
374 
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));
381 		}
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));
386 		}
387 	}
388 
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));
395 		}
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));
400 		}
401 	}
402 }
403 
404 void
floodfill(Image * dst,Rectangle r,Point p,Image * src)405 floodfill(Image *dst, Rectangle r, Point p, Image *src)
406 {
407 	Filldata f;
408 
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;
423 
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;
432 
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;
438 
439 	fillscan(&f, subpt(p, f.r.min));
440 
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);
450 }
451 
452 void
translate(Point d)453 translate(Point d)
454 {
455 	Rectangle r, nr;
456 
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);
467 }
468 
469 void
setzoom(Point o,int z)470 setzoom(Point o, int z)
471 {
472 	if(z < 1)
473 		return;
474 	cpos = s2c(o);
475 	spos = o;
476 	zoom = z;
477 	update(nil);
478 }
479 
480 void
center(void)481 center(void)
482 {
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);
490 }
491 
492 void
drawpal(void)493 drawpal(void)
494 {
495 	Rectangle r, rr;
496 	int i;
497 
498 	r = screen->r;
499 	r.min.y = r.max.y - 20;
500 	replclipr(screen, 0, r);
501 
502 	penr = r;
503 	penr.min.x = r.max.x - NBRUSH*Dy(r);
504 
505 	palr = r;
506 	palr.max.x = penr.min.x;
507 
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);
522 		}
523 		r.min.x = r.max.x;
524 	}
525 
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;
535 	}
536 
537 	r = screen->r;
538 	r.max.y -= Dy(palr);
539 	replclipr(screen, 0, r);
540 }
541 
542 int
hitpal(Mouse m)543 hitpal(Mouse m)
544 {
545 	if(ptinrect(m.xy, penr)){
546 		if(m.buttons & 7){
547 			brush = ((m.xy.x - penr.min.x) * NBRUSH) / Dx(penr);
548 			drawpal();
549 		}
550 		return 1;
551 	}
552 	if(ptinrect(m.xy, palr)){
553 		Image *col;
554 
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;
566 		}
567 		return 1;
568 	}
569 	return 0;
570 }
571 
572 void
catch(void * _,char * msg)573 catch(void * _, char *msg)
574 {
575 	USED(_);
576 	if(strstr(msg, "closed pipe"))
577 		noted(NCONT);
578 	noted(NDFLT);
579 }
580 
581 int
pipeline(char * fmt,...)582 pipeline(char *fmt, ...)
583 {
584 	char buf[1024];
585 	va_list a;
586 	int p[2];
587 
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");
605 	}
606 	close(p[0]);
607 	return p[1];
608 }
609 
610 void
usage(void)611 usage(void)
612 {
613 	fprint(2, "usage: %s [ file ]\n", argv0);
614 	exits("usage");
615 }
616 
617 void
main(int argc,char * argv[])618 main(int argc, char *argv[])
619 {
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;
627 
628 	ARGBEGIN {
629 	default:
630 		usage();
631 	} ARGEND;
632 
633 	if(argc == 1)
634 		filename = strdup(argv[0]);
635 	else if(argc != 0)
636 		usage();
637 
638 	if(initdraw(0, 0, "paint") < 0)
639 		sysfatal("initdraw: %r");
640 
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);
647 	}
648 
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");
655 	}
656 	ink = pal[0];
657 	back = pal[1];
658 	drawpal();
659 	center();
660 
661 	einit(Emouse | Ekeyboard);
662 
663 	notify(catch);
664 	for(;;) {
665 		switch(event(&e)){
666 		case Emouse:
667 			if(hitpal(e.mouse))
668 				continue;
669 
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;
684 					}
685 					r = canvas->r;
686 					save(r, 1);
687 					floodfill(canvas, r, p, img);
688 					update(&r);
689 
690 					/* wait for mouse release */
691 					while(event(&e) == Emouse && (e.mouse.buttons & 7) != 0)
692 						;
693 					break;
694 				}
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;
715 				}
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));
725 				}
726 				break;
727 			}
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;
787 					}
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;
798 					}
799 					if(canvas){
800 						save(canvas->r, 1);
801 						freeimage(canvas);
802 					}
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;
817 					}
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);
841 					}
842 					goto Readimage;
843 				}
844 				break;
845 			}
846 			break;
847 		}
848 	}
849 }
850 
851 void
eresized(int _)852 eresized(int _)
853 {
854 	USED(_);
855 	if(getwindow(display, Refnone) < 0)
856 		sysfatal("resize failed");
857 	drawpal();
858 	update(nil);
859 }
860