13

Bresenham の円描画アルゴリズムの実装を作成しました。このアルゴリズムは、円の高度な対称性を利用しています(1 番目の八分円から点のみを計算し、対称性を利用して他の点を描画します)。したがって、私はそれが非常に速いと予想していました。グラフィックス プログラミング ブラック ブック、第 35 章のタイトルは「ブレゼンハムは速く、速いことは良い」であり、線描画アルゴリズムに関するものでしたが、円描画アルゴリズムも高速であると合理的に期待できました (原理は同じ)。

これが私のJava、スイングの実装です

public static void drawBresenhamsCircle(int r, double width, double height, Graphics g) {
    int x,y,d;
    y = r;
    x = 0;

    drawPoint(x, y, width, height,g);
    d = (3-2*(int)r);
    while (x <= y) {
        if (d <= 0) {
            d = d + (4*x + 6);
        } else {
            d = d + 4*(x-y) + 10;
            y--;
        }
        x++;

        drawPoint(x, y, width, height,g);

        drawPoint(-x, y, width, height,g);
        drawPoint(x, -y, width, height,g);

        drawPoint(-x, -y, width, height,g);
        drawPoint(y, x, width, height,g);
        drawPoint(-y, x, width, height,g);
        drawPoint(y, -x, width, height,g);

        drawPoint(-y, -x, width, height,g);
    }   
}

この方法では、次の方法を使用しますdrawPoint

public static void drawPoint(double x, double y,double width,double height, Graphics g) {
    double nativeX = getNativeX(x, width);
    double nativeY = getNativeY(y, height);
    g.fillRect((int)nativeX, (int)nativeY, 1, 1);
}

getNativeX と getNativeY の 2 つのメソッドを使用して、座標を画面の左上隅を起点とする座標系から、より古典的な軸方向でパネルの中央を原点とする座標系に切り替えます。

public static double getNativeX(double newX, double width) {
    return newX + (width/2);
}

public static double getNativeY(double newY, double height) {
    return (height/2) - newY;
}

x=R*Math.cos(angle)また、三角関数の公式 (および)に基づく円描画アルゴリズムのy= R*Math.sin(angle)実装と、標準の drawArc メソッド (Graphics オブジェクトで使用可能) の呼び出しを使用した 3 つ目の実装も作成しました。これらの追加の実装は、ブレゼンハムのアルゴリズムをそれらと比較することのみを目的としています。

次に、費やされた時間を適切に測定できるように、たくさんの円を描くメソッドを作成しました。Bresenhamのアルゴリズムを使用して多数の円を描くために使用する方法は次のとおりです

public static void drawABunchOfBresenhamsCircles(int numOfCircles, double width, double height, Graphics g) {
    double r = 5;
    double step = (300.0-5.0)/numOfCircles;

    for (int i = 1; i <= numOfCircles; i++) {
        drawBresenhamsCircle((int)r, width, height, g);
        r += step;
    }
}

最後に、使用している JPanel のペイント メソッドをオーバーライドして、多数の円を描画し、各タイプの描画にかかった時間を測定します。ペイント方法は次のとおりです。

public void paint(Graphics g) {
    Graphics2D g2D = (Graphics2D)g;

    g2D.setColor(Color.RED);

    long trigoStartTime = System.currentTimeMillis();
    drawABunchOfTrigonometricalCircles(1000, this.getWidth(), this.getHeight(), g);
    long trigoEndTime = System.currentTimeMillis();
    long trigoDelta = trigoEndTime - trigoStartTime;

    g2D.setColor(Color.BLUE);

    long bresenHamsStartTime = System.currentTimeMillis();
    drawABunchOfBresenhamsCircles(1000, this.getWidth(), this.getHeight(), g);
    long bresenHamsEndTime = System.currentTimeMillis();
    long bresenDelta = bresenHamsEndTime - bresenHamsStartTime;

    g2D.setColor(Color.GREEN);

    long standardStarTime = System.currentTimeMillis();
    drawABunchOfStandardCircles(1000, this.getWidth(), this.getHeight(),g);
    long standardEndTime = System.currentTimeMillis();
    long standardDelta = standardEndTime - standardStarTime;

    System.out.println("Trigo : " + trigoDelta  + " milliseconds");
    System.out.println("Bresenham :" + bresenDelta +  " milliseconds");
    System.out.println("Standard :" + standardDelta +  " milliseconds");
}

生成されるレンダリングの種類は次のとおりです (各タイプの 1000 個の円を描画)

Bresenham およびその他の方法

残念ながら、私の Bresenham の実装は非常に遅いです。私は多くの比較測定を行いましたが、ブレゼンハムの実装は よりGraphics.drawArcも遅いだけでなく、三角法のアプローチよりも遅いです。描かれたさまざまな数の円について、次の対策を見てください。

実装のどの部分がより時間がかかりますか? それを改善するために使用できる回避策はありますか?助けてくれてありがとう。

Bresenham と他の実装の比較

[EDITION] : @higuaro のリクエストに応じて、円を描くための三角法アルゴリズムを次に示します。

public static void drawTrigonometricalCircle (double r, double width, double height, Graphics g) {

    double x0 = 0;
    double y0 = 0;
    boolean isStart = true;

    for (double angle = 0; angle <= 2*Math.PI; angle = angle + Math.PI/36) {

        double x = r * Math.cos(angle);
        double y = r * Math.sin(angle);

        drawPoint((double)x, y, width, height, g);

        if (!isStart) {
            drawLine(x0,  y0, x, y, width, height, g);
        }

        isStart = false;

        x0 = x;
        y0 = y;
    }
}

そして、三角関数の円の束を描くために使用される方法

public static void drawABunchOfTrigonometricalCircles(int numOfCircles, double width, double height, Graphics g) {

    double r = 5;
    double step = (300.0-5.0)/numOfCircles;

    for (int i = 1; i <= numOfCircles; i++) {
        drawTrigonometricalCircle(r, width, height, g);
        r += step;
    }
}
4

3 に答える 3

4

あなたの Bresenham メソッドはそれ自体が遅いわけではなく、比較的遅いだけです。

Swing のdrawArc()実装はマシンに依存し、ネイティブ コードを使用します。Java を使用しても決して勝てないので、わざわざ試してはいけません。(実際、Java Bresenham メソッドが と比較して同じくらい高速であることに驚いています。これはdrawArc()、Java バイトコードを実行する仮想マシンの品質の証です。)

ただし、三角法は、ブレゼンハムと同等に比較していないため、不必要に高速です。

ステートメントの最後にあるこの操作のように、trig メソッドにはPI/36(~4.7 度)の角度分解能が設定されています。for

angle = angle + Math.PI/36  

一方、ブレゼンハム法は半径に依存し、ピクセルの変化ごとに値を計算します。各八分円がsqrt(2)ポイントを生成するので、それを掛けて8で割る2*Piと、同等の角度解像度が得られます。したがって、ブレゼンハム法と対等な立場に立つには、三角法には次のものが必要です。

resolution = 4 * r * Math.sqrt(2) / Math.PI;

ループの外側のどこかで、次のようにインクリメントしますfor

angle += resolution

ピクセルレベルの解像度に戻るので、実際にトリガー メソッドを改善して、その後の and への呼び出しと代入を切り取り、drawline不必要なキャストを排除し、さらに への呼び出しを減らすことができます。新しいメソッド全体を次に示します。x0y0Math

public static void drawTrigonometricalCircle (double r, double width, double height, 
    Graphics g) {

    double localPi = Math.PI;
    double resolution = 4 * r * Math.sqrt(2) / Math.PI;

    for (double angle = 0; angle <= localPi; angle += resolution) {
        double x = r * Math.cos(angle);
        double y = r * Math.sin(angle);
        drawPoint(x, y, width, height, g);
    }

}

trig メソッドは、 のサイズに応じて数桁の頻度で実行されるようになりましたr

私はあなたの結果を見てみたいです。

于 2015-04-08T02:41:40.483 に答える
3

あなたの問題は、ブレゼンハムのアルゴリズムが円のサイズに応じて可変数の反復を行うのに対し、三角法のアプローチは常に固定数の反復を行うことにあります。

これは、ブレゼンハムのアルゴリズムは常に滑らかな円を生成するのに対し、三角法のアプローチは半径が大きくなるにつれて見た目の悪い円を生成することも意味します。

より均一にするために、三角法のアプローチを変更して、ブレゼンハムの実装とほぼ同じ数のポイントを生成すると、それがどれほど高速であるかがわかります。

これをベンチマークし、生成されたポイント数を出力するコードをいくつか書きました。最初の結果は次のとおりです。

三角法: 181 ミリ秒、平均 73 ポイント
Bresenham: 120 ミリ秒、平均 867.568 ポイント

三角関数クラスを変更して、円をより滑らかにするための反復を増やした後:

    int totalPoints = (int)Math.ceil(0.7 * r * 8);
    double delta = 2 * Math.PI / totalPoints;
    for (double angle = 0; angle <= 2*Math.PI; angle = angle + delta) {

結果は次のとおりです。

三角法: 2006 ミリ秒、平均 854.933 ポイント
ブレゼンハム: 120 ミリ秒、平均 867.568 ポイント

于 2015-04-08T03:23:28.963 に答える