15

中点円アルゴリズムは、円の境界をラスタライズするために使用できます。ただし、ピクセルを複数回描画せずに円を塗りつぶしたい(これは非常に重要です)。

この回答は、塗りつぶされた円を生成するアルゴリズムの変更を提供しますが、一部のピクセルは数回アクセス されます。塗りつぶされた円を描画するための高速アルゴリズム?

Q:ピクセルを複数回描画せずに円をラスタライズするにはどうすればよいですか?RAMは非常に限られていることに注意してください!

アップデート:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CircleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[,] buffer = new byte[50, 50];
            circle(buffer, 25, 25, 20);

            for (int y = 0; y < 50; ++y)
            {
                for (int x = 0; x < 50; ++x)
                    Console.Write(buffer[y, x].ToString());

                Console.WriteLine();
            }
        }

        // 'cx' and 'cy' denote the offset of the circle center from the origin.
        static void circle(byte[,] buffer, int cx, int cy, int radius)
        {
            int error = -radius;
            int x = radius;
            int y = 0;

            // The following while loop may altered to 'while (x > y)' for a
            // performance benefit, as long as a call to 'plot4points' follows
            // the body of the loop. This allows for the elimination of the
            // '(x != y)' test in 'plot8points', providing a further benefit.
            //
            // For the sake of clarity, this is not shown here.
            while (x >= y)
            {
                plot8points(buffer, cx, cy, x, y);

                error += y;
                ++y;
                error += y;

                // The following test may be implemented in assembly language in
                // most machines by testing the carry flag after adding 'y' to
                // the value of 'error' in the previous step, since 'error'
                // nominally has a negative value.
                if (error >= 0)
                {
                    error -= x;
                    --x;
                    error -= x;
                }
            }
        }

        static void plot8points(byte[,] buffer, int cx, int cy, int x, int y)
        {
            plot4points(buffer, cx, cy, x, y);
            if (x != y) plot4points(buffer, cx, cy, y, x);
        }

        // The '(x != 0 && y != 0)' test in the last line of this function
        // may be omitted for a performance benefit if the radius of the
        // circle is known to be non-zero.
        static void plot4points(byte[,] buffer, int cx, int cy, int x, int y)
        {
#if false // Outlined circle are indeed plotted correctly!
            setPixel(buffer, cx + x, cy + y);
            if (x != 0) setPixel(buffer, cx - x, cy + y);
            if (y != 0) setPixel(buffer, cx + x, cy - y);
            if (x != 0 && y != 0) setPixel(buffer, cx - x, cy - y);
#else // But the filled version plots some pixels multiple times...
            horizontalLine(buffer, cx - x, cy + y, cx + x);
            //if (x != 0) setPixel(buffer, cx - x, cy + y);
            //if (y != 0) setPixel(buffer, cx + x, cy - y);
            //if (x != 0 && y != 0) setPixel(buffer, cx - x, cy - y);
#endif
        }

        static void setPixel(byte[,] buffer, int x, int y)
        {
            buffer[y, x]++;
        }

        static void horizontalLine(byte[,] buffer, int x0, int y0, int x1)
        {
            for (int x = x0; x <= x1; ++x)
                setPixel(buffer, x, y0);
        }
    }
}

関連する結果は次のとおりです。

00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000011111111111111111111111111111111111111100000
00000011111111111111111111111111111111111111100000
00000011111111111111111111111111111111111111100000
00000001111111111111111111111111111111111111000000
00000001111111111111111111111111111111111111000000
00000000111111111111111111111111111111111110000000
00000000111111111111111111111111111111111110000000
00000000011111111111111111111111111111111100000000
00000000001111111111111111111111111111111000000000
00000000000111111111111111111111111111110000000000
00000000000011111111111111111111111111100000000000
00000000000001111111111111111111111111000000000000
00000000000000122222222222222222222210000000000000
00000000000000001222222222222222221000000000000000
00000000000000000012333333333332100000000000000000
00000000000000000000012345432100000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000

下のピクセルが何度もプロットされています。ここで何が欠けていますか?

アップデート#2:このソリューションは機能します:

static void circle(byte[,] buffer, int cx, int cy, int radius)
{
    int error = -radius;
    int x = radius;
    int y = 0;

    while (x >= y)
    {
        int lastY = y;

        error += y;
        ++y;
        error += y;

        plot4points(buffer, cx, cy, x, lastY);

        if (error >= 0)
        {
            if (x != lastY)
                plot4points(buffer, cx, cy, lastY, x);

            error -= x;
            --x;
            error -= x;
        }
    }
}

static void plot4points(byte[,] buffer, int cx, int cy, int x, int y)
{
    horizontalLine(buffer, cx - x, cy + y, cx + x);
    if (y != 0)
        horizontalLine(buffer, cx - x, cy - y, cx + x);
}    
4

5 に答える 5

19

他の質問への答えは完全に問題ありません。ただし、混乱を招いているので、少し説明します。

ウィキペディアにあるアルゴリズムは、基本的に円の1/8(角度0から)を見つけxて、その鏡である8つの点を描画します。例えば:ypi/4

    (o-y,o+x) x         x (o+y,o+x)

(o-x,o+y) x                  x (o+x,o+y) <-- compute x,y

                   o

(o-x,o-y) x                  x (o+x,o-y)

    (o-y,o-x) x         x (o+y,o-x)

他の解決策が示唆していることは、この写真をよく見ると完全に理にかなっていますが、8点を描く代わりに、4本の水平線を描くことです。

    (o-y,o+x) x---------x (o+y,o+x)

(o-x,o+y) x-----------------x (o+x,o+y) <-- compute x,y

                   o

(o-x,o-y) x-----------------x (o+x,o-y)

    (o-y,o-x) x---------x (o+y,o-x)

ここ(x,y)で、の角度を計算し、[0, pi/4]計算されたすべての点に対してこれらの4本の線を描画すると、線が他の線と重なることなく、円を埋める多くの水平線が描画されます。

アップデート

円の下部に線が重なっている理由は、(x,y)座標が丸みを帯びているためです。そのため、これらの場所では、水平方向に(x,y) 移動します。

このウィキペディアの写真を見てみると:

ここに画像の説明を入力してください

円の上部に、いくつかのピクセルが水平方向に整列していることがわかります。それらの点から始まる水平線の描画は重なります。

これが必要ない場合、解決策は非常に簡単です。描画した前の線を保持するx必要があり(上部と下部は元の線のミラーである(x,y)ため、これらの線のyを表す前のxを保持する必要があります)、その値が変更された場合にのみ水平線を描画します。そうでない場合、それはあなたが同じラインにいることを意味します。

最初に最も内側のポイントに遭遇するという事実を考えると、新しいポイントが異なる場合にのみ、前のポイントの線を描画する必要がありますx(もちろん、最後の線は常に描画されます)。または、角度PI/4からPI/4の代わりに0まで描画を開始し、最初に外側の点に遭遇するため、新しいを表示するたびに線を描画しますx

于 2012-06-04T08:45:43.020 に答える
4

私はこれを行う必要がありました、これが私がそれのために思いついたコードです。ここでの視覚的な画像は、ピクセルがトラバースされる順序である番号で描画されたピクセルを示しています。緑色の数字は、コードに示されているように、対称性を使用した列の完成の反射を使用して描画されたピクセルを表します。

ここに画像の説明を入力してください

void drawFilledMidpointCircleSinglePixelVisit( int centerX, int centerY, int radius )
{
    int x = radius;
    int y = 0;
    int radiusError = 1 - x;

    while (x >= y)  // iterate to the circle diagonal
    {

        // use symmetry to draw the two horizontal lines at this Y with a special case to draw
        // only one line at the centerY where y == 0
        int startX = -x + centerX;
        int endX = x + centerX;
        drawHorizontalLine( startX, endX, y + centerY );
        if (y != 0)
        {
            drawHorizontalLine( startX, endX, -y + centerY );
        }

        // move Y one line
        y++;

        // calculate or maintain new x
        if (radiusError<0)
        {
            radiusError += 2 * y + 1;
        }
        else 
        {
            // we're about to move x over one, this means we completed a column of X values, use
            // symmetry to draw those complete columns as horizontal lines at the top and bottom of the circle
            // beyond the diagonal of the main loop
            if (x >= y)
            {
                startX = -y + 1 + centerX;
                endX = y - 1 + centerX;
                drawHorizontalLine( startX, endX,  x + centerY );
                drawHorizontalLine( startX, endX, -x + centerY );
            }
            x--;
            radiusError += 2 * (y - x + 1);
        }

    }

}
于 2014-07-02T09:47:44.007 に答える
4

すでに塗りつぶされている円を描くアルゴリズムを思いついた。
円が描画されるピクセルを繰り返し、他には何もありません。
ここからは、ピクセル描画機能の速度について説明します。

これが、アルゴリズムの機能を示す*.gifです。

アルゴリズムに関しては、ここにコードがあります:

    //The center of the circle and its radius.
    int x = 100;
    int y = 100;
    int r = 50;
    //This here is sin(45) but i just hard-coded it.
    float sinus = 0.70710678118;
    //This is the distance on the axis from sin(90) to sin(45). 
    int range = r/(2*sinus);
    for(int i = r ; i >= range ; --i)
    {
        int j = sqrt(r*r - i*i);
        for(int k = -j ; k <= j ; k++)
        {
            //We draw all the 4 sides at the same time.
            PutPixel(x-k,y+i);
            PutPixel(x-k,y-i);
            PutPixel(x+i,y+k);
            PutPixel(x-i,y-k);
        }
    }
    //To fill the circle we draw the circumscribed square.
    range = r*sinus;
    for(int i = x - range + 1 ; i < x + range ; i++)
    {
        for(int j = y - range + 1 ; j < y + range ; j++)
        {
            PutPixel(i,j);
        }
    }

これがお役に立てば幸いです...一部の新規ユーザー...ネクロ投稿でごめんなさい。
〜Shmiggy

于 2014-12-12T21:02:06.937 に答える
2

私はあなたのアップデート#2についてコメントしたかった:この解決策はうまくいく:(しかし私は最初にもっと評判が必要だと思う...)小さな円を描くときに偶然にも解決策に小さなバグがある。半径を1に設定すると、次のようになります。

00000
00000
01110
00100
00000

これを修正するには、plot4pointsの条件チェックをから変更するだけです。

if (x != 0 && y != 0)

if (y != 0)

私はこれを小さな円と大きな円でテストして、各ピクセルがまだ1回だけ割り当てられていることを確認しました。うまく機能しているようです。x!=0は必要なかったと思います。パフォーマンスも少し節約できます。

于 2015-04-26T15:30:17.403 に答える
0

アップデート#2

 if (error >= 0)
 {
    if (x != lastY) 
       plot4points(buffer, cx, cy, lastY, x);

 if (error >= 0)
 {     
    plot4points(buffer, cx, cy, lastY, x);

Circle And FillCircleバージョン:

Const
  Vypln13:Boolean=False;  // Fill Object


//Draw a circle at (cx,cy)
Procedure Circle(cx: integer; cy: integer; radius: integer );
Var
   error,x,y: integer;
Begin  
   error := -radius;
   x := radius;
   y := 0;

   while (x >= y) do
   Begin

     Draw4Pixel(cx,cy, x, y);
     if ( Not Vypln13 And ( x <> y) ) Then Draw4Pixel(cx,cy, y, x);

     error := error + y;
     y := y + 1;
     error := error + y;

     if (error >= 0) Then
     Begin

       if ( Vypln13) then Draw4Pixel(cx, cy, y - 1, x);

       error := error - x;
       x := x - 1;
       error := error - x;
     End;
   End;
End;


Procedure Draw4Pixel(cx,cy,dx,dy: integer);
Begin
  if ( (dx = 0) And (dy = 0) ) then
  begin
    PutPixel (cx , cy , Color13);
    exit;
  End;

  IF Vypln13 Then
  Begin
    HorizontLine (cx - dx,  cx + dx, cy + dy, Color13);
    if ( dy = 0 ) then exit;
    HorizontLine (cx - dx,  cx + dx, cy - dy, Color13);
    exit;
  end;

  PutPixel (cx + dx, cy + dy, Color13);
  if ( dx <> 0 ) then
  begin
    PutPixel (cx - dx, cy + dy, Color13);
    if ( dy = 0 ) then exit;
    PutPixel (cx + dx, cy - dy, Color13);
  End;
  PutPixel (cx - dx, cy - dy, Color13);

End;
于 2015-05-16T10:42:04.627 に答える