圖形學入門(2)——圓生成算法(中點畫圓法)


一個圖形學萌新的學習記錄。

學習了直線生成算法之后,繼續來學習繪制圓弧的方法,如果要生成一個整圓,可以利用坐標系的八對稱性,在其中一個象限繪制之后再在其他象限的對稱點繪制即可。

我們首先考慮圓心在原點,半徑為r的圓,計算出像素之后只需加上一個偏移量即可繪制圓心在任意一點的圓。

要畫圓最暴力的方法當然是利用圓的參數方程來計算:

 

 

 但這樣計算使用了三角函數和浮點運算,效率低下,所以通常我們使用中點畫圓法來進行圓的繪制。

中點畫圓法

中點畫圓法利用的也是類似於Bresenham直線算法的思想,利用判別式選擇像素,只需做簡單的整數運算。

我們八象限中第二象限的1/8圓為例,若確定了一個像素點為($x_{p},y_{p}$),那么下一個點要么是右方的P1,要么是右下方的P2。

 

構造函數一個函數F(x,y)=$x^{2}$+$y^{2}$-$R^{2}$,當F大於0時,點在圓外,反之則在圓內。

圖中的M是P1和P2的中點,所以M=($x_{p}$+1,$y_{p}-0.5$),當F(M)<0時,M在圓內,說明P1離圓弧更近,反之M在圓外,P2更近。

根據以上原理,可構造判別式:

 

 當$d_{p}$<0時,取P1為下一像素,下一像素判別式為:

 

 當$d_{p}$>0時,取P2為下一像素,下一像素判別式為:

 

 我們按順時針方式生成八分圓,所以第一個像素為(0,R),初始判別式為:

 1.25-R可以簡化成1-R,去除浮點數運算,因為運算過程中增量都為整數,所以減去0.25是不會影響符號的。

代碼實現如下:

void CirclePoints(HDC hdc, int x, int y,int offx,int offy)//利用對稱性畫整圓
{
    SetPixel(hdc, x + offx, y + offy, RGB(0, 0, 0));
    SetPixel(hdc, y + offx, x + offy, RGB(0, 0, 0));
    SetPixel(hdc, x + offx, -y + offy, RGB(0, 0, 0));
    SetPixel(hdc, -y + offx, x + offy, RGB(0, 0, 0));
    SetPixel(hdc, -x + offx, y + offy, RGB(0, 0, 0));
    SetPixel(hdc, y + offx, -x + offy, RGB(0, 0, 0));
    SetPixel(hdc, -x + offx, -y + offy, RGB(0, 0, 0));
    SetPixel(hdc, -y + offx, -x + offy, RGB(0, 0, 0));
}

void MidPointCircle(HDC hdc, int x1, int y1, int r)//中點畫圓
{
    int x, y, e;
    x = 0; y = r; e = 1 - r;
    CirclePoints(hdc, x, y, x1, y1);
    while (x <= y)
    {
        if (e < 0)
            e += 2 * x + 3;
        else
        {
            e += 2 * (x - y) + 5;
            y--;
        }
        x++;
        CirclePoints(hdc, x, y, x1, y1);
    }
}

 

完整可運行Windows代碼:

 

#include<Windows.h>
#include<iostream>
#include<cmath>
using namespace std;

const int ScreenWidth = 500;
const int ScreenHeight = 500;

LRESULT CALLBACK WinProc(HWND hWnd, UINT message, 
WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }
    return 0;
}

void CirclePoints(HDC hdc, int x, int y,int offx,int offy)//利用對稱性畫整圓
{
    SetPixel(hdc, x + offx, y + offy, RGB(0, 0, 0));
    SetPixel(hdc, y + offx, x + offy, RGB(0, 0, 0));
    SetPixel(hdc, x + offx, -y + offy, RGB(0, 0, 0));
    SetPixel(hdc, -y + offx, x + offy, RGB(0, 0, 0));
    SetPixel(hdc, -x + offx, y + offy, RGB(0, 0, 0));
    SetPixel(hdc, y + offx, -x + offy, RGB(0, 0, 0));
    SetPixel(hdc, -x + offx, -y + offy, RGB(0, 0, 0));
    SetPixel(hdc, -y + offx, -x + offy, RGB(0, 0, 0));
}

void MidPointCircle(HDC hdc, int x1, int y1, int r)
{
    int x, y, e;
    x = 0; y = r; e = 1 - r;
    CirclePoints(hdc, x, y, x1, y1);
    while (x <= y)
    {
        if (e < 0)
            e += 2 * x + 3;
        else
        {
            e += 2 * (x - y) + 5;
            y--;
        }
        x++;
        CirclePoints(hdc, x, y, x1, y1);
    }
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
PSTR szCmdLine, int nShowCmd)
{
    WNDCLASS wcs;
    wcs.cbClsExtra = 0;                                         
    wcs.cbWndExtra = 0;                                         
    wcs.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);  
    wcs.hCursor = LoadCursor(hInstance, IDC_CROSS);              
    wcs.hIcon = LoadIcon(NULL, IDI_WINLOGO);                      
    wcs.hInstance = hInstance;                                 
    wcs.lpfnWndProc = (WNDPROC)WinProc;
    wcs.lpszClassName = "CG";
    wcs.lpszMenuName = NULL;
    wcs.style = CS_VREDRAW | CS_HREDRAW;
    RegisterClass(&wcs);
    HWND hWnd;
    hWnd = CreateWindow("CG", "DrawCircle", WS_OVERLAPPEDWINDOW, 
200, 200, ScreenWidth, ScreenHeight, NULL, NULL, hInstance, NULL);
    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);
    MSG msg;

    // hdc init
    HDC hdc = GetDC(hWnd);

    MidPointCircle(hdc, 200, 200, 150);

    // 消息循環  
    while (GetMessage(&msg, 0, NULL, NULL)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // release
    ReleaseDC(hWnd, hdc);
    return 0;
}
View Code

 

 

運行結果:

 

 

 

 接下來是區域填充算法,加油~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM