プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Gupta-Sproullアルゴリズムの実装

ちょっと実装を行わないといけないことになったので、実装してみました。言語はJAVAです。

今回、アルゴリズムで使用した色のテーブルはGURGE.COMのAdam Doppelt氏が作成したのを利用しました。

読みやすさ優先の為、高速動作とかは特に考えていません。また、端点処理は含んでいません。
GuptaSproullCanvasインターフェースは指定した点に渡された濃度値で色を描画しろ、という命令です。

interface GuptaSproullCanvas{
    public void setColor(int x,int y,int alph);

}

class GuptaSproull{
    /*
     * GuptaSproull8ビットテーブル作成 Adam Doppelt氏
     * GURGE.COM (URL:http://gurge.com/blog/2006/11/29/gupta-sproull-antialiased-lines/)
     */

    static int[] table=
    { 0xc7, 0xc6, 0xc2, 0xbc,
        0xb3, 0xa9, 0x9c, 0x8e,
        0x7f, 0x70, 0x62, 0x54,
        0x46, 0x3a, 0x2f, 0x25,
        0x1c, 0x15, 0x0e, 0x09,
        0x05, 0x03, 0x01, 0x00 };


    int x0,y0,x1,y1;//始点終点
    int xl,yl;//abs(x0-x1),abs(y0-y1)
    boolean straight;//水平方向または垂直方向に一直線かどうか
    boolean vertical;//垂直方向に多く進むかどうか

    public GuptaSproull(Point from,Point to) {
        x0=from.x;
        x1=to.x;
        y0=from.y;
        y1=to.y;
        xl=x1-x0<0? -x1+x0:x1-x0;
        yl=y1-y0<0? -y1+y0:y1-y0;
        straight = yl==0 ||xl==0;
        vertical = straight?xl==0:yl>xl;
    }

    public void draw(GuptaSproullCanvas g){
        int xadd=x1-x0>0?1:-1;//座標更新時に正負どっちに進むか
        int yadd=y1-y0>0?1:-1;

        int x,y;

        if(straight){//垂直か水平に一直線
            if(vertical){
                for(y=y0;y!=y1;y+=yadd)g.setColor(x0, y, 0xff);
            }else{
                for(x=x0;x!=x1;x+=xadd)g.setColor(x, y0, 0xff);
            }
            return;
        }

        if(vertical){//tanθ>1
            int xweight=0;
            x=x0;
            for(y=y0;y!=y1;y+=yadd){//座標更新は単なるDDA
                g.setColor(x, y, getAlph(distance(x, y)));
                g.setColor(x+1, y, getAlph(distance(x+1, y)));
                g.setColor(x-1, y, getAlph(distance(x-1, y)));
                xweight+=xl;
                if(xweight*2 >= yl){//四捨五入を考慮
                    xweight-=yl;
                    x+=xadd;
                }
            }
            //最後に終点を描画
            g.setColor(x, y, getAlph(distance(x, y)));
            g.setColor(x+1, y, getAlph(distance(x+1, y)));
            g.setColor(x-1, y, getAlph(distance(x-1, y)));
        }else{
            int yweight=0;
            y=y0;

            for(x=x0;x!=x1;x+=xadd){
                g.setColor(x, y, getAlph(distance(x, y)));
                g.setColor(x, y+1, getAlph(distance(x, y+1)));
                g.setColor(x, y-1, getAlph(distance(x, y-1)));
                yweight+=yl;

                if(yweight*2 >= xl){
                    yweight-=xl;
                    y+=yadd;
                }
            }

            //最後に終点を描画
            g.setColor(x, y, getAlph(distance(x, y)));
            g.setColor(x, y+1, getAlph(distance(x, y+1)));
            g.setColor(x, y-1, getAlph(distance(x, y-1)));
        }
    }

    private double distance(int x,int y){
        return Math.abs((y1-y0)*x-(x1-x0)*y-y1*x0+x1*y0)/Math.hypot((x1-x0), (y1-y0));
    }

    private int getAlph(double distance){
        if(distance>=1)return 0;
        return table[(int)(distance*table.length)];
    }
}