| #include <u.h> |
| #include <libc.h> |
| #include <draw.h> |
| #include <memdraw.h> |
| |
| /* |
| * ellipse(dst, c, a, b, t, src, sp) |
| * draws an ellipse centered at c with semiaxes a,b>=0 |
| * and semithickness t>=0, or filled if t<0. point sp |
| * in src maps to c in dst |
| * |
| * very thick skinny ellipses are brushed with circles (slow) |
| * others are approximated by filling between 2 ellipses |
| * criterion for very thick when b<a: t/b > 0.5*x/(1-x) |
| * where x = b/a |
| */ |
| |
| typedef struct Param Param; |
| typedef struct State State; |
| |
| static void bellipse(int, State*, Param*); |
| static void erect(int, int, int, int, Param*); |
| static void eline(int, int, int, int, Param*); |
| |
| struct Param { |
| Memimage *dst; |
| Memimage *src; |
| Point c; |
| int t; |
| Point sp; |
| Memimage *disc; |
| int op; |
| }; |
| |
| /* |
| * denote residual error by e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2 |
| * e(x,y) = 0 on ellipse, e(x,y) < 0 inside, e(x,y) > 0 outside |
| */ |
| |
| struct State { |
| int a; |
| int x; |
| vlong a2; /* a^2 */ |
| vlong b2; /* b^2 */ |
| vlong b2x; /* b^2 * x */ |
| vlong a2y; /* a^2 * y */ |
| vlong c1; |
| vlong c2; /* test criteria */ |
| vlong ee; /* ee = e(x+1/2,y-1/2) - (a^2+b^2)/4 */ |
| vlong dxe; |
| vlong dye; |
| vlong d2xe; |
| vlong d2ye; |
| }; |
| |
| static |
| State* |
| newstate(State *s, int a, int b) |
| { |
| s->x = 0; |
| s->a = a; |
| s->a2 = (vlong)(a*a); |
| s->b2 = (vlong)(b*b); |
| s->b2x = (vlong)0; |
| s->a2y = s->a2*(vlong)b; |
| s->c1 = -((s->a2>>2) + (vlong)(a&1) + s->b2); |
| s->c2 = -((s->b2>>2) + (vlong)(b&1)); |
| s->ee = -s->a2y; |
| s->dxe = (vlong)0; |
| s->dye = s->ee<<1; |
| s->d2xe = s->b2<<1; |
| s->d2ye = s->a2<<1; |
| return s; |
| } |
| |
| /* |
| * return x coord of rightmost pixel on next scan line |
| */ |
| static |
| int |
| step(State *s) |
| { |
| while(s->x < s->a) { |
| if(s->ee+s->b2x <= s->c1 || /* e(x+1,y-1/2) <= 0 */ |
| s->ee+s->a2y <= s->c2) { /* e(x+1/2,y) <= 0 (rare) */ |
| s->dxe += s->d2xe; |
| s->ee += s->dxe; |
| s->b2x += s->b2; |
| s->x++; |
| continue; |
| } |
| s->dye += s->d2ye; |
| s->ee += s->dye; |
| s->a2y -= s->a2; |
| if(s->ee-s->a2y <= s->c2) { /* e(x+1/2,y-1) <= 0 */ |
| s->dxe += s->d2xe; |
| s->ee += s->dxe; |
| s->b2x += s->b2; |
| return s->x++; |
| } |
| break; |
| } |
| return s->x; |
| } |
| |
| void |
| memellipse(Memimage *dst, Point c, int a, int b, int t, Memimage *src, Point sp, int op) |
| { |
| State in, out; |
| int y, inb, inx, outx, u; |
| Param p; |
| |
| if(a < 0) |
| a = -a; |
| if(b < 0) |
| b = -b; |
| p.dst = dst; |
| p.src = src; |
| p.c = c; |
| p.t = t; |
| p.sp = subpt(sp, c); |
| p.disc = nil; |
| p.op = op; |
| |
| u = (t<<1)*(a-b); |
| if(b<a && u>b*b || a<b && -u>a*a) { |
| /* if(b<a&&(t<<1)>b*b/a || a<b&&(t<<1)>a*a/b) # very thick */ |
| bellipse(b, newstate(&in, a, b), &p); |
| return; |
| } |
| |
| if(t < 0) { |
| inb = -1; |
| newstate(&out, a, y = b); |
| } else { |
| inb = b - t; |
| newstate(&out, a+t, y = b+t); |
| } |
| if(t > 0) |
| newstate(&in, a-t, inb); |
| inx = 0; |
| for( ; y>=0; y--) { |
| outx = step(&out); |
| if(y > inb) { |
| erect(-outx, y, outx, y, &p); |
| if(y != 0) |
| erect(-outx, -y, outx, -y, &p); |
| continue; |
| } |
| if(t > 0) { |
| inx = step(&in); |
| if(y == inb) |
| inx = 0; |
| } else if(inx > outx) |
| inx = outx; |
| erect(inx, y, outx, y, &p); |
| if(y != 0) |
| erect(inx, -y, outx, -y, &p); |
| erect(-outx, y, -inx, y, &p); |
| if(y != 0) |
| erect(-outx, -y, -inx, -y, &p); |
| inx = outx + 1; |
| } |
| } |
| |
| static Point p00 = {0, 0}; |
| |
| /* |
| * a brushed ellipse |
| */ |
| static |
| void |
| bellipse(int y, State *s, Param *p) |
| { |
| int t, ox, oy, x, nx; |
| |
| t = p->t; |
| p->disc = allocmemimage(Rect(-t,-t,t+1,t+1), GREY1); |
| if(p->disc == nil) |
| return; |
| memfillcolor(p->disc, DTransparent); |
| memellipse(p->disc, p00, t, t, -1, memopaque, p00, p->op); |
| oy = y; |
| ox = 0; |
| nx = x = step(s); |
| do { |
| while(nx==x && y-->0) |
| nx = step(s); |
| y++; |
| eline(-x,-oy,-ox, -y, p); |
| eline(ox,-oy, x, -y, p); |
| eline(-x, y,-ox, oy, p); |
| eline(ox, y, x, oy, p); |
| ox = x+1; |
| x = nx; |
| y--; |
| oy = y; |
| } while(oy > 0); |
| } |
| |
| /* |
| * a rectangle with closed (not half-open) coordinates expressed |
| * relative to the center of the ellipse |
| */ |
| static |
| void |
| erect(int x0, int y0, int x1, int y1, Param *p) |
| { |
| Rectangle r; |
| |
| /* print("R %d,%d %d,%d\n", x0, y0, x1, y1); */ |
| r = Rect(p->c.x+x0, p->c.y+y0, p->c.x+x1+1, p->c.y+y1+1); |
| memdraw(p->dst, r, p->src, addpt(p->sp, r.min), memopaque, p00, p->op); |
| } |
| |
| /* |
| * a brushed point similarly specified |
| */ |
| static |
| void |
| epoint(int x, int y, Param *p) |
| { |
| Point p0; |
| Rectangle r; |
| |
| /* print("P%d %d,%d\n", p->t, x, y); */ |
| p0 = Pt(p->c.x+x, p->c.y+y); |
| r = Rpt(addpt(p0, p->disc->r.min), addpt(p0, p->disc->r.max)); |
| memdraw(p->dst, r, p->src, addpt(p->sp, r.min), p->disc, p->disc->r.min, p->op); |
| } |
| |
| /* |
| * a brushed horizontal or vertical line similarly specified |
| */ |
| static |
| void |
| eline(int x0, int y0, int x1, int y1, Param *p) |
| { |
| /* print("L%d %d,%d %d,%d\n", p->t, x0, y0, x1, y1); */ |
| if(x1 > x0+1) |
| erect(x0+1, y0-p->t, x1-1, y1+p->t, p); |
| else if(y1 > y0+1) |
| erect(x0-p->t, y0+1, x1+p->t, y1-1, p); |
| epoint(x0, y0, p); |
| if(x1-x0 || y1-y0) |
| epoint(x1, y1, p); |
| } |