このアルゴリズムの課題

45度付近の精度

サンプルコードのままだと、RADIUS_MAXが大きすぎると問題が起こります。 例えば、「今のPCなら数MBくらいのテーブル問題ないよね?」などとRADIUS_MAX=1000にするとこんな出力に。

画像はサンプルコードで半径が10ずつ増える同心円を描いたその1部で、1番外側の円の半径は1000です。 3倍に拡大してあります。 45度付近のところに出っ張りがあるのが確認できます。

こうなったのはdの値が0〜255(ALPHA_MAX)に丸められたせいです。 dとd_oldが実数なら確認できたはずの差が丸められたせいで無くなってしまい、ずれるはずのピクセルがずれなかったためにこうなっています。

対応するには、いくつかの方法があります。 まず1つ目。 ALPHA_MAXの代わりに大きめの値のD_MAXを用意します。

public static final int ALPHA_MAX = 255;
public static final int D_SHIFT = 8;
public static final int D_MAX = ALPHA_MAX << D_SHIFT;

点を打つときに本来の値に戻します。

setPixel8way(cx, cy, x, y, color, (D_MAX - d) >> D_SHIFT);

シフト演算を使っているのはかけ算より速い(足し算並)からです。

この方法の難点は、想定以上に大きな円を描くことになった場合、やはり不具合が出てしまうというところです。 D_SHIFTが十分なら大丈夫なのかな? 試してないけど。

2つ目は「最初からD[r][y]を実数型で作っておく」という方法。 実数型のdから整数型のアルファ値を計算するときちょっとだけ時間がかかりますが、それが問題にならないなら1番楽な方法ですね。

3つ目はそれを組み合わせた方法です。 精度が担保されたRADIUS_MAXを用意しておき、RADIUS_MAX以下の円を描くときは整数型のD[r][y]を使い、それ以上の円を描くときは描画ループ内で実数型のdを計算します。 コードが少し煩雑になるのと、速度差が出そうなのが問題かな?

ぱっと思いついたのはこの3つです。 この他にも色々方法があると思います。 用途や実装にあわせて対応しましょう。

直径は奇数

サンプルコードどおりだと直径が奇数の円しか描けません。 一応、円弧のコピーをずらせば、直径が偶数の円も描けはします。 具体的には、setPixel8wayを2つ用意しておきます。 偶数用は、

public void setPixel8wayEven(int cx, int cy, int x, int y, int color, int alpha)
{
    int rgba = alpha << 24 | color;
    _img.setRGB(cx + x    , cy + y    , rgba);
    _img.setRGB(cx + x    , cy - y - 1, rgba);
    _img.setRGB(cx - x - 1, cy + y    , rgba);
    _img.setRGB(cx - x - 1, cy - y - 1, rgba);
    _img.setRGB(cx + y    , cy + x    , rgba);
    _img.setRGB(cx + y    , cy - x - 1, rgba);
    _img.setRGB(cx - y - 1, cy + x    , rgba);
    _img.setRGB(cx - y - 1, cy - x - 1, rgba);
}

奇数用のsetPixel8wayOddはサンプルコードそのままでOKです。

これが良い解決法かはアプリケーションによりますよね? これでダメな場合は、浅知恵では解決しなそうです。