web-dev-qa-db-ja.com

隙間なく同心円を描く

Cellマトリックスに半径が増加する円周を描くことにより、円を別の色で塗りつぶしますLollipopのように。私は現在、ポイントを取得するために Midpoint 円アルゴリズムを使用しています。 私が持っている問題は、この方法で円を塗りつぶすときにいくつかのギャップがあるということです。

下の例(元は1番)では、ピンクと白を使用してさまざまな周囲を強調表示しています。 (黒いピクセルに注意してください)

私の実装(1)のコードはここにあります: https://Gist.github.com/beppe9000/e4a29542a76b8ee3b47f

そのようなギャップを生成しないアルゴリズムを探しています。

更新

セル内のいくつかのメタデータもグループで取得すると更新されるため(それぞれが円周に対応するため)、同心円パスを使用して円を塗りつぶすことが重要です。私は問題を単純化しようとしたので、すべてを理解できませんでした。私が優れたコミュニケーターでない場合は、申し訳ありません...;)

基本的には、生成される円周が完全に隣接している必要があります。これは、半径nとn + 1の円の間にギャップがないことを意味します

アップデート2

私はブルートフォースアルゴリズムを試しましたが、いくつかの結果が得られましたが、個々の円周を取得する機能がまだありませんでした。特に、3番に大きな希望がありました... enter image description here

7
beppe9000

中点アルゴリズムは、中心から「正確に」所与の距離にある一連の点を提供します。

あなたがしたいことは、距離がよりも小さい(そして半径に等しくない)かどうかをテストする別のアルゴリズムを使用することです。

ブルートフォースアルゴリズムはグリッド内のすべてのセルをチェックすることですが、本当に効率的にしたい場合は、近傍テスト(中点アルゴリズムと同様)を実行し、上から開始する何らかの種類のラインスキャンを実行できます(x_start = x_radius ; y_start = y_radius + radius)。

擬似コードの例:

foreach i in i_range:
    foreach j in j_range:
        y = i - ry
        x = j - rx
        if (x^2 + y^2)^0.5 - 0:
           Paint_cell(i,j)
3
heltonbiker

あなたが尋ねたこととまったく同じではありませんが、垂直スキャンだけの方が簡単だと思います。

line((x_center-r,y_center)-(x_center+r,y_center))
for(y=y_center+1;y_center+r;y++) {
    w=sqrt((r+y)(r-y))
    x_min=x_center-w
    x_max=x_center+w
    y_mirror=2*y_center-y
    line((x_min,y)-(x_max,y))
    line((x_min,y_mirror)-(x_max,y_mirror))
}

したがって、画面の円の直径が101ピクセルの場合、50平方根の計算と101本の水平線を描画する必要があります(各長さは2本の線があります)。

3
Mandrill

私が考えることができる最も単純なアプローチは、ピンク色の完全に塗りつぶされた円から始めて(たとえば、@ Mandrillの回答を使用)、既存の中点アルゴリズムを使用して、ピンク色の円の上に白い円のみを描画します。それは黒い斑点を残さないでしょう、すべての黒い斑点はあなたが始めた色を得ます。

ただし、何も描画したくない場合は、次の方法でこれを処理できます。

  • 半径が大きい順に「円周」を描く

  • 中点アルゴリズムを次のように変更します。円の左半分のセルを特定の色に設定するたびに、同じ行のそのセルの右隣に黒い点があるかどうかをテストします。その場合は、黒以外のスポットまたは中央の列に到達するまで、それらも現在の色で塗りつぶします。右半分のセルに色を付ける場合も同じようにしますが、これらのセルの場合、現在の行の左側の黒い点を塗りつぶします。

例えば:

enter image description here

このアプローチの利点は、「中間サークル」が最終結果だけでなく、常に要件を満たしていることです。そして、「黒い斑点」はすべて同じ「好ましい色」で満たされていませんが、白とピンクの比率がより均一に分布しています。実行時間は、依然として色付きのセルの数に比例しています。

これを簡単に変更して、特定の半径の「円周」の座標を任意の順序で生成するアルゴリズムを作成できます。関数を作成する

  IEnumerable<Point> MidpointCircle(int radius,Point center){...}

次に、次のように使用します(C#の大まかな概要、注意、エアコード):

  IEnumerable<Point> Circumference(int radius,Point center)
  {
       if(radius==0)
          yield break;
       var points = MidpointCircle(radius,center);
       var innerPoints =new Hashset(MidpointCircle(radius-1,center));
       foreach(var p in points)
       {
            yield return p;
            if(p.X<center.X)
            {
               Point q = new Point(p.X+1,p.Y);
               while(q.X<center.X && !innerPoints.Contain(q))
               {
                  yield return q;
                  q = new Point(p.X+1,p.Y);
               }
            }
            else
            {
               // similar code for points right from the center
            }
       }
  }

これにより、探している結果が得られます。

1
Doc Brown

同心円パスを使用して円を塗りつぶすことが重要です

デカルト座標系で「同心円パス」を使用して「完璧」なものを描画することはできません。これは、最終的に行う必要があることです。 ピクセルがNステップの半径の円と一致しないだけです。しかし、適切な近似を行うことができます。また、使用する方法は、最終的な目標によって異なります。

...セルのメタデータもグループで取得すると更新されます(それぞれが周囲に対応します)。

これは、円の一部であるcouldであるすべての点をポイントごとに操作する必要があるように聞こえます。これにより、円の "中心との関係"を決定します。

つまり、最終的には heltonbiker's solution に非常に似たものになりますが、それはあらゆるポイント通り過ぎる。次の2つの機能が必要な場合があります。

// a simple distance function
var distanceFrom = function(x, y, cx, cy) {
    return Math.sqrt(
        Math.pow(cx - x, 2) + Math.pow(cy - y, 2)
    );
};

// something to determine the "value" of a pixel, based on the circle center
var getValue = function(x, y) {

    // in your real application, these three vars will be parameters ...
    var radius = 7;
    var center_x = 7;
    var center_y = 7;

    var d = distanceFrom(x, y, center_x, center_y);
    if (d <= radius) {
        return d / radius;
    } else {
        return 1; // white/off
    }
}; // getValue()

そして、単純なループ...適切な名前のメソッドのどこかに:

// something that loops through all the relevant pixels.
// ... this does NOT need to look at every pixel. it only
// needs to look at the pixels within the box that bounds
// your circle.
for (var y = 0; y < 15; y++) {
    for (var x = 0; x < 15; x++) {
        Paint(x, y, getValue(x, y));
    }
}

「ピクセル」を示すために32倍に拡大された this fiddle を参照してください。

pixelated circle gradient

結果からわかるように、同心円を描くと、醜いピクセルと円が強制的に一致します。ピクセル単位のアプローチでは、すべてのピクセルが評価されて描画されるだけでなく、各ピクセルを最も関連性の高い円または半径に関連付けることができます。

非勾配パターンを探している場合は、値関数を更新するだけです。

// alternating / concentric-ish circles ....
var getValue = function(x, y) {
    var radius = 7;
    var center_x = 7;
    var center_y = 7;
    var d = distanceFrom(x, y, center_x, center_y);
    if (d <= radius) {
        return Math.floor(d) % 2 === 0 ? 0 : 0.5;
    } else {
        return 1; // white/off
    }
}; // getValue()

表示 ここのフィドルとして 。そして、これが生成するものです:

concentric-ish circles

そして、もちろん、他の円と互換性のある単一の「円」に色を付ける場合は、円領域全体をスキャンする最適化されていないアプローチから始めて、flooredまたは丸め中心からの距離は、整数(またはフロア/丸め)半径と一致します。

var getValue = function(x, y) {
    var radius = 7;
    var center_x = 7;
    var center_y = 7;

    // the radius of the "circle" we're drawing (should be parametized)
    var draw_only = 5;

    var d = distanceFrom(x, y, center_x, center_y);

    // the new condition is tacked on here:
    if (d <= radius && Math.floor(d) === draw_only) {
        return Math.floor(d) % 2 === 0 ? 0 : 0.5;
    } else {
        return 1; // white/off
    }
}; // getValue()

効率的ではありませんですが、描画されるリングは、色が交互に変化する例のリングと一致する必要があります。

enter image description here

フィドル を参照してください。

最後の例で行ったように、一度に1つのリングのみを描画する場合は、円を歩き回り、「おそらく」であるピクセルのブロックを処理することで、(大きな円に対して)最適化できる可能性があります。着色されます。何かsimilar to thistotally untestedcode ...

// radius of the circle we want to draw
var radius = 5;
for (var deg = 0; deg < 360; deg++) {
  var radians = 2 * Math.PI * deg/360;

  var focus_x = Math.floor(radius * Math.cos(radians));
  var focus_y = Math.floor(radius * Math.sin(radians));

  // i'm not sure how much "fuzz" you need ... play with it:
  var x1 = focus_x - 2;
  var x2 = focus_x + 2;
  var y1 = focus_y - 2;
  var y2 = focus_y + 2;

  for (var y = y1; y < y2; y++) {
    for (var x = x1; x < x2; x++) {
      Paint(x, y, getValue(x, y), radius); // again, radius needs to be parametized
    }
  }
}

免責事項:この回答も、関連するフィドルも、 HTML5キャンバス の適切な使用法を示すことを目的としたものではありません。

1
svidgen

パーティーには遅れましたが、トニーバレラの「4接続」サークルアルゴリズムが Will Perone's site 、それはギャップを埋め、同時に最速であるようです、以下または fiddle のJavascriptコード。この答えは 円を描く描画アルゴリズム にも適用できます。

var canvas = document.querySelector('canvas')
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';

var DrawPixel = function (x, y) {
    ctx.fillRect(x, y, 1, 1);
}

function Barrera4(x0, y0, radius) {
    var x = 0;
    var y = radius;
    var d = -(radius >>> 1);

    while(x <= y) {
        DrawPixel(x + x0, y + y0);
        DrawPixel(y + x0, x + y0);
        DrawPixel(-x + x0, y + y0);
        DrawPixel(-y + x0, x + y0);
        DrawPixel(-x + x0, -y + y0);
        DrawPixel(-y + x0, -x + y0);
        DrawPixel(x + x0, -y + y0);
        DrawPixel(y + x0, -x + y0);

        if(d <= 0) {
            x++;
            d += x;
        } else {
            y--;
            d -= y;
        }
    }
}

for(var r = 100; 0 < r; r--) {
    ctx.fillStyle = (r%8 < 4) ? 'pink' : 'white';
    //DrawCirle(120, 120, r);
    Barrera4(120, 120, r);
    //Barrera8(120, 120, r);
}
canvas {
  background-color: black;
}
<canvas width=300 height=300></canvas>
1
Mike C.

同心円を描くことに興味があり、 [〜#〜] aabb [〜#〜] のブロアードスイープを実行しても構わない場合は、各ピクセルで実行するテストは次のとおりです。

distanceFromCenter <= radius && distanceFromCenter > (radius - someThreshold)

またはより具体的な例として:

void drawCircle(Point center, int radius, int thickness, Color color) {
    for (var x = center.x - radius; x < center.x + radius; x++) {
        for (var y = center.y - radius; y < center.y + radius; y++) {
            float distance = distanceFrom(x, y, center.x, center.y);
            if (distance <= radius && distance > radius - thickness) {
                drawPixel(x, y, color);
            }
        }
    }
}

これらの円を頻繁に更新する必要があり、パフォーマンスが問題になる場合は、関連するピクセルにポイント配列または マスク を格納できます。

0
Kelly Thomas