10 Point spos; /* position on screen */
11 Point cpos; /* position on canvas */
15 Image *pal[16]; /* palette */
16 Rectangle palr; /* palette rect on screen */
17 Rectangle penr; /* pen size rect on screen */
26 int c64[] = { /* c64 color palette */
46 * get bounding rectnagle for stroke from r.min to r.max with
47 * specified brush (size).
50 strokerect(Rectangle r, int brush)
53 return Rect(r.min.x-brush, r.min.y-brush, r.max.x+brush+1, r.max.y+brush+1);
57 * draw stroke from r.min to r.max to dst with color ink and
61 strokedraw(Image *dst, Rectangle r, Image *ink, int brush)
63 if(!eqpt(r.min, r.max))
64 line(dst, r.min, r.max, Enddisc, Enddisc, brush, ink, ZP);
65 fillellipse(dst, r.max, brush, brush, ink, ZP);
69 * A draw operation that touches only the area contained in bot but not in top.
70 * mp and sp get aligned with bot.min.
73 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
74 Image *src, Point sp, Image *mask, Point mp, int op)
80 if(Dx(bot)*Dy(bot) == 0)
83 /* no points in bot - top */
84 if(rectinrect(bot, top))
88 if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
89 gendrawop(dst, bot, src, sp, mask, mp, op);
94 /* split bot into rectangles that don't intersect top */
96 if(bot.min.x < top.min.x){
97 r = Rect(bot.min.x, bot.min.y, top.min.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.min.x = top.min.x;
104 if(bot.max.x > top.max.x){
105 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
106 delta = subpt(r.min, origin);
107 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
108 bot.max.x = top.max.x;
112 if(bot.min.y < top.min.y){
113 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
114 delta = subpt(r.min, origin);
115 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
116 bot.min.y = top.min.y;
120 if(bot.max.y > top.max.y){
121 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
122 delta = subpt(r.min, origin);
123 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
124 bot.max.y = top.max.y;
129 alphachan(ulong chan)
131 for(; chan; chan >>= 8)
132 if(TYPE(chan) == CAlpha)
138 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
146 if(r.min.x < d->r.min.x){
147 sp.x += (d->r.min.x - r.min.x)/f;
148 a.x = (d->r.min.x - r.min.x)%f;
149 r.min.x = d->r.min.x;
151 if(r.min.y < d->r.min.y){
152 sp.y += (d->r.min.y - r.min.y)/f;
153 a.y = (d->r.min.y - r.min.y)%f;
154 r.min.y = d->r.min.y;
157 w = s->r.max.x - sp.x;
161 dr.max.x = dr.min.x+w;
162 if(!alphachan(s->chan))
165 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
166 gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
169 if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
171 for(; dr.min.y < r.max.y; dr.min.y++){
172 dr.max.y = dr.min.y+1;
173 draw(t, dr, s, nil, sp);
180 for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
181 dr.max.x = dr.min.x+1;
182 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
183 gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
184 for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
185 dr.max.x = dr.min.x+1;
186 gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
196 if(p.x < 0) p.x -= zoom-1;
197 if(p.y < 0) p.y -= zoom-1;
198 return addpt(divpt(p, zoom), cpos);
203 return addpt(mulpt(subpt(p, cpos), zoom), spos);
208 return Rpt(c2s(r.min), c2s(r.max));
212 update(Rectangle *rp){
214 draw(screen, screen->r, back, nil, ZP);
218 gendrawdiff(screen, screen->r, c2sr(canvas->r), back, ZP, nil, ZP, SoverD);
219 zoomdraw(screen, c2sr(*rp), ZR, back, canvas, rp->min, zoom);
221 flushimage(display, 1);
231 if((canvas = allocimage(display, r, screen->chan, 0, DNofill)) == nil)
232 sysfatal("allocimage: %r");
233 draw(canvas, canvas->r, back, nil, ZP);
238 if(eqrect(nr, canvas->r))
240 if((tmp = allocimage(display, nr, canvas->chan, 0, DNofill)) == nil)
242 draw(tmp, canvas->r, canvas, nil, canvas->r.min);
243 gendrawdiff(tmp, tmp->r, canvas->r, back, ZP, nil, ZP, SoverD);
249 save(Rectangle r, int mark)
255 x = nundo++ % nelem(undo);
260 if(canvas==nil || nundo<0)
262 if(!rectclip(&r, canvas->r))
264 if((tmp = allocimage(display, r, canvas->chan, 0, DNofill)) == nil)
266 draw(tmp, r, canvas, nil, r.min);
267 x = nundo++ % nelem(undo);
282 x = --nundo % nelem(undo);
283 if((tmp = undo[x]) == nil)
286 if(canvas == nil || canvas->chan != tmp->chan){
292 draw(canvas, tmp->r, tmp, nil, tmp->r.min);
304 int yscan; /* current scanline */
305 int wscan; /* bscan width in bytes */
306 Image* iscan; /* scanline image */
307 uchar* bscan; /* scanline buffer */
309 int nmask; /* size of bmask in bytes */
310 int wmask; /* width of bmask in bytes */
311 Image* imask; /* mask image */
312 uchar* bmask; /* mask buffer */
319 fillscan(Filldata *f, Point p0)
326 b = f->bmask + y*f->wmask;
327 if(b[x/8] & 0x80>>(x%8))
331 draw(f->iscan, f->iscan->r, f->dst, nil, Pt(f->r.min.x, f->r.min.y+y));
332 if(unloadimage(f->iscan, f->iscan->r, f->bscan, f->wscan) < 0)
337 for(x = p0.x; x >= 0; x--){
338 if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
340 b[x/8] |= 0x80>>(x%8);
342 for(x = p0.x+1; x < f->r0.max.x; x++){
343 if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
345 b[x/8] |= 0x80>>(x%8);
350 for(x = p0.x; x >= 0; x--){
351 if((b[x/8] & 0x80>>(x%8)) == 0)
353 fillscan(f, Pt(x, y));
355 for(x = p0.x+1; x < f->r0.max.x; x++){
356 if((b[x/8] & 0x80>>(x%8)) == 0)
358 fillscan(f, Pt(x, y));
364 for(x = p0.x; x >= 0; x--){
365 if((b[x/8] & 0x80>>(x%8)) == 0)
367 fillscan(f, Pt(x, y));
369 for(x = p0.x+1; x < f->r0.max.x; x++){
370 if((b[x/8] & 0x80>>(x%8)) == 0)
372 fillscan(f, Pt(x, y));
378 floodfill(Image *dst, Rectangle r, Point p, Image *src)
382 if(!rectclip(&r, dst->r))
386 memset(&f, 0, sizeof(f));
389 f.r0 = rectsubpt(r, r.min);
390 f.wmask = bytesperline(f.r0, 1);
391 f.nmask = f.wmask*f.r0.max.y;
392 if((f.bmask = mallocz(f.nmask, 1)) == nil)
394 if((f.imask = allocimage(display, f.r0, GREY1, 0, DNofill)) == nil)
399 if((f.iscan = allocimage(display, r, RGB24, 0, DNofill)) == nil)
402 f.wscan = bytesperline(f.iscan->r, f.iscan->depth);
403 if((f.bscan = mallocz(f.wscan, 0)) == nil)
407 f.ncmp = (f.iscan->depth+7) / 8;
408 draw(f.iscan, r, dst, nil, p);
409 if(unloadimage(f.iscan, r, f.bcmp, sizeof(f.bcmp)) < 0)
412 fillscan(&f, subpt(p, f.r.min));
414 loadimage(f.imask, f.imask->r, f.bmask, f.nmask);
415 draw(f.dst, f.r, src, f.imask, f.imask->r.min);
430 if(canvas==nil || d.x==0 && d.y==0)
433 nr = rectaddpt(r, d);
434 rectclip(&r, screen->clipr);
435 draw(screen, rectaddpt(r, d), screen, nil, r.min);
436 zoomdraw(screen, nr, rectaddpt(r, d), back, canvas, canvas->r.min, zoom);
437 gendrawdiff(screen, screen->r, nr, back, ZP, nil, ZP, SoverD);
438 spos = addpt(spos, d);
439 flushimage(display, 1);
443 setzoom(Point o, int z)
458 cpos = addpt(canvas->r.min,
459 divpt(subpt(canvas->r.max, canvas->r.min), 2));
460 spos = addpt(screen->r.min,
461 divpt(subpt(screen->r.max, screen->r.min), 2));
472 r.min.y = r.max.y - 20;
473 replclipr(screen, 0, r);
476 penr.min.x = r.max.x - NBRUSH*Dy(r);
479 palr.max.x = penr.min.x;
482 draw(screen, r, back, nil, ZP);
483 for(i=0; i<NBRUSH; i++){
484 r.max.x = penr.min.x + (i+1)*Dx(penr) / NBRUSH;
489 /* last is special brush for fill draw */
490 draw(screen, rr, ink, nil, ZP);
492 rr.min = addpt(rr.min, divpt(subpt(rr.max, rr.min), 2));
494 strokedraw(screen, rr, ink, i);
500 for(i=1; i<=nelem(pal); i++){
501 r.max.x = palr.min.x + i*Dx(palr) / nelem(pal);
505 draw(screen, rr, pal[i-1], nil, ZP);
506 gendrawdiff(screen, r, rr, back, ZP, nil, ZP, SoverD);
512 replclipr(screen, 0, r);
518 if(ptinrect(m.xy, penr)){
520 brush = ((m.xy.x - penr.min.x) * NBRUSH) / Dx(penr);
525 if(ptinrect(m.xy, palr)){
528 col = pal[(m.xy.x - palr.min.x) * nelem(pal) / Dx(palr)];
529 switch(m.buttons & 7){
546 catch(void *, char *msg)
548 if(strstr(msg, "closed pipe"))
554 pipeline(char *fmt, ...)
561 vsnprint(buf, sizeof(buf), fmt, a);
565 switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFREND)){
575 execl("/bin/rc", "rc", "-c", buf, nil);
585 fprint(2, "usage: %s [ file ]\n", argv0);
590 main(int argc, char *argv[])
606 filename = strdup(argv[0]);
610 if(initdraw(0, 0, "paint") < 0)
611 sysfatal("initdraw: %r");
614 if((fd = open(filename, OREAD)) < 0)
615 sysfatal("open: %r");
616 if((canvas = readimage(display, fd, 0)) == nil)
617 sysfatal("readimage: %r");
621 /* palette initialization */
622 for(i=0; i<nelem(pal); i++){
623 pal[i] = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1,
624 c64[i % nelem(c64)]<<8 | 0xFF);
626 sysfatal("allocimage: %r");
633 einit(Emouse | Ekeyboard);
643 switch(e.mouse.buttons & 7){
649 if(brush == NBRUSH-1){
650 /* flood fill brush */
651 if(canvas == nil || !ptinrect(p, canvas->r)){
659 floodfill(canvas, r, p, img);
662 /* wait for mouse release */
663 while(event(&e) == Emouse && (e.mouse.buttons & 7) != 0)
667 r = strokerect(Rpt(p, p), brush);
670 strokedraw(canvas, Rpt(p, p), img, brush);
674 if(event(&e) != Emouse)
676 if((e.mouse.buttons ^ m.buttons) & 7)
681 r = strokerect(Rpt(p, d), brush);
684 strokedraw(canvas, Rpt(p, d), img, brush);
692 if(event(&e) != Emouse)
694 if((e.mouse.buttons & 7) != 4)
696 translate(subpt(e.mouse.xy, m.xy));
709 setzoom(e.mouse.xy, zoom*2);
713 setzoom(e.mouse.xy, zoom/2);
730 case '0': case '1': case '2': case '3': case '4':
731 case '5': case '6': case '7': case '8': case '9':
732 brush = e.kbdc - '0';
739 if(filename && (e.kbdc == 'r' || e.kbdc == 'w'))
740 snprint(buf, sizeof(buf), "%C %s", e.kbdc, filename);
741 else if(e.kbdc > 0x20 && e.kbdc < 0x7f)
742 snprint(buf, sizeof(buf), "%C", e.kbdc);
743 if(eenter("Cmd", buf, sizeof(buf), &e.mouse) <= 0)
745 if(strcmp(buf, "q") == 0)
748 while(*s == ' ' || *s == '\t')
754 if((fd = open(s, OREAD)) < 0){
756 snprint(buf, sizeof(buf), "%r");
757 eenter(buf, nil, 0, &e.mouse);
761 filename = strdup(s);
763 unlockdisplay(display);
764 img = readimage(display, fd, 1);
766 lockdisplay(display);
768 werrstr("readimage: %r");
779 if((fd = create(s, OWRITE, 0660)) < 0)
782 filename = strdup(s);
785 if(writeimage(fd, canvas, 0) < 0){
787 werrstr("writeimage: %r");
793 if((fd = pipeline("%s", s)) < 0)
797 if((fd = pipeline("%s", s)) < 0)
803 if((fd = pipeline("%s", s)) < 0)
805 switch(rfork(RFMEM|RFPROC|RFFDG)){
808 werrstr("rfork: %r");
811 writeimage(fd, canvas, 1);
826 if(getwindow(display, Refnone) < 0)
827 sysfatal("resize failed");