char *filename;
int zoom = 1;
-int thick = 1;
+int brush = 1;
Point spos; /* position on screen */
Point cpos; /* position on canvas */
Image *canvas;
Rectangle palr; /* palette rect on screen */
Rectangle penr; /* pen size rect on screen */
+enum {
+ NBRUSH = 10+1,
+};
+
int nundo = 0;
Image *undo[1024];
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.
if((tmp = undo[x]) == nil)
return;
undo[x] = nil;
- expand(tmp->r);
- draw(canvas, tmp->r, tmp, nil, tmp->r.min);
- update(&tmp->r);
- freeimage(tmp);
+ 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)
{
replclipr(screen, 0, r);
penr = r;
- penr.min.x = r.max.x - 10*Dy(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<10; i++){
- r.max.x = penr.min.x + (i+1)*Dx(penr) / 10;
+ for(i=0; i<NBRUSH; i++){
+ r.max.x = penr.min.x + (i+1)*Dx(penr) / NBRUSH;
rr = r;
- if(i == thick)
+ if(i == brush)
rr.min.y += Dy(r)/3;
- fillellipse(screen, addpt(rr.min, divpt(subpt(rr.max, rr.min), 2)), i, i, ink, ZP);
+ 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;
}
{
if(ptinrect(m.xy, penr)){
if(m.buttons & 7){
- thick = ((m.xy.x - penr.min.x) * 10) / Dx(penr);
+ brush = ((m.xy.x - penr.min.x) * NBRUSH) / Dx(penr);
drawpal();
}
return 1;
va_end(a);
if(pipe(p) < 0)
return -1;
- switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG)){
+ switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFREND)){
case -1:
close(p[0]);
close(p[1]);
/* no break */
case 1:
p = s2c(e.mouse.xy);
- r = Rect(p.x-thick, p.y-thick, p.x+thick+1, p.y+thick+1);
+ 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);
- fillellipse(canvas, p, thick, thick, img, ZP);
+ strokedraw(canvas, Rpt(p, p), img, brush);
update(&r);
for(;;){
m = e.mouse;
d = s2c(e.mouse.xy);
if(eqpt(d, p))
continue;
- r = canonrect(Rpt(p, d));
- r.min.x -= thick;
- r.min.y -= thick;
- r.max.x += thick+1;
- r.max.y += thick+1;
+ r = strokerect(Rpt(p, d), brush);
expand(r);
save(r, 0);
- line(canvas, p, d, Enddisc, Enddisc, thick, img, ZP);
+ strokedraw(canvas, Rpt(p, d), img, brush);
update(&r);
p = d;
}
center();
break;
case '+':
- setzoom(e.mouse.xy, zoom*2);
+ if(zoom < 0x1000)
+ setzoom(e.mouse.xy, zoom*2);
break;
case '-':
- setzoom(e.mouse.xy, zoom/2);
+ if(zoom > 1)
+ setzoom(e.mouse.xy, zoom/2);
break;
- case 'f':
+ case 'c':
if(canvas == nil)
break;
save(canvas->r, 1);
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':
- thick = e.kbdc - '0';
+ brush = e.kbdc - '0';
drawpal();
break;
default: