Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <memlayer.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);
}