基本图形的扫描转换这个名词不太容易理解,通俗地讲就是通过SetPixel或者SetPixelV函数来实现绘图,比如画一条直线会使用LineTo,同样可以用SetPixel函数实现。

  其中SetPixel与SetPixelV的区别,SetPixelV不放回实际像素点的RGB值,执行速度比SetPixel快得多!!!

   

       圆的扫描转换

  既如何自定义绘制圆,而不是通过Ellipse直接绘制,而是通过SetPixelV去绘制。

  1、根据对称性,一个在中心在原点的圆,有4条对称轴x=0,y=0,x=y,x=-y,分成8等分。所以只要绘制出第一象限内的1/8圆弧,通过对称性,可以绘制出整个圆。比如第一象限内圆上某点为(x,y),则另外7个点为(x,-y),(-x,y),(-x,-y),(y,x),(-y,x),(y,-x),(-y,-x);

  2、通过Bresenhma算法可以推得公式 误差di = (xi+1)*(xi+1) + (yi-0.5)*(yi-0.5) - R*R;该公式推理过程可以看孔玲德著作计算机图形学基础教程(第二版96页)。通过现有坐标(xi,yi)推断(xi+1,yi+1)的值。如果di>>=0 yi+1 = yi-1,否则yi+1 = yi。

  通过这两个特点我们可以编写程序,由于第一个特性需要中心在原点,所以我们要自己设置画布的原点正好在圆心。代码如下:

  

    RECT rtClient;
    GetClientRect(hwnd,&rtClient);
    SIZE ptOldViewExt,ptOldWindowExt;
    POINT ptOldOrg;
    int OldMapMode = SetMapMode(hdc,MM_ANISOTROPIC);
    int iWidth = abs(rtClient.right - rtClient.left);
    int iHeight = abs(rtClient.bottom - rtClient.top);
    SetViewportExtEx(hdc,iWidth,iHeight,&ptOldViewExt);
    SetWindowExtEx(hdc,iWidth,-iHeight,&ptOldWindowExt);
    POINT ptOrg = {pt.x,pt.y};
    DPtoLP(hdc,&ptOrg,1);
    SetWindowOrgEx(hdc,-ptOrg.x,-ptOrg.y,&ptOldOrg); 

  绘制结束需要复原坐标,这是一个好习惯!

   SetViewportExtEx(hdc,ptOldViewExt.cx,ptOldViewExt.cy,NULL);
    SetWindowExtEx(hdc,ptOldWindowExt.cx,ptOldWindowExt.cy,NULL);
    SetWindowOrgEx(hdc,ptOldOrg.x,ptOldOrg.y,NULL);

  并且根据对称性,我们可以写一下函数

  

VOID DrawAxial(HDC hdc,int x,int y,COLORREF color)
{
    SetPixelV(hdc,x,y,color);
    SetPixelV(hdc,x,-y,color);
    SetPixelV(hdc,-x,y,color);
    SetPixelV(hdc,-x,-y,color);
    SetPixelV(hdc,y,x,color);
    SetPixelV(hdc,y,-x,color);
    SetPixelV(hdc,-y,x,color);
    SetPixelV(hdc,-y,-x,color);
}

 

  特性2代码如下:

  

    COLORREF color = RGB(0,0,0);
    double di = 0;
    int xi = 0,yi = R;
    DrawAxial(hdc,xi,yi,color);
    for(int i=1;i<=(int)(1.0*R/sqrt(2.0));++i)
    {
        di = (xi+1)*(xi+1) + (yi-0.5)*(yi-0.5) - R*R;
        ++xi;
        if(di >= 0)
        {
            --yi;
        }
        DrawAxial(hdc,xi,yi,color);
    }

  R为半径,起点为(0,R)。通过公式可以轻而易举地写出代码。

  完整win32代码如下:

  

#include "Main.h"
#include<tchar.h>
#include<stdio.h>
#include<windows.h>
#include<math.h>

LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    WNDCLASS wndcls;
    wndcls.cbClsExtra = 0;
    wndcls.cbWndExtra = 0;
    wndcls.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wndcls.hCursor = LoadCursor(NULL,IDC_ARROW);
    wndcls.hIcon = LoadIcon(NULL,IDI_APPLICATION);
    wndcls.hInstance = hInstance;
    wndcls.lpfnWndProc = WinSunProc;
    wndcls.lpszClassName = _T("sunxin2006");
    wndcls.lpszMenuName = NULL;
    wndcls.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&wndcls);

    HWND hwnd = CreateWindow(_T("sunxin2006"),_T("helloworld"),WS_OVERLAPPEDWINDOW,
        0,0,600,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOW);
    UpdateWindow(hwnd);

    MSG msg;
    while(GetMessage(&msg,NULL,0,0)>0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

VOID OnCreate(HWND hwnd,WPARAM wParam,LPARAM lParam)
{
    int scrWidth,scrHeight;
    RECT rect;
    //获得屏幕尺寸
    scrWidth = GetSystemMetrics(SM_CXSCREEN);
    scrHeight = GetSystemMetrics(SM_CYSCREEN);
    //取得窗口尺寸
    GetWindowRect(hwnd,&rect);
    //重新设置rect里的值
    rect.left = (scrWidth-rect.right)/2;
    rect.top = (scrHeight-rect.bottom)/2;
    //移动窗口到指定的位置
    SetWindowPos(hwnd,HWND_TOP,rect.left,rect.top,rect.right,rect.bottom,SWP_SHOWWINDOW);
}

VOID DrawAxial(HDC hdc,int x,int y,COLORREF color)
{
    SetPixelV(hdc,x,y,color);
    SetPixelV(hdc,x,-y,color);
    SetPixelV(hdc,-x,y,color);
    SetPixelV(hdc,-x,-y,color);
    SetPixelV(hdc,y,x,color);
    SetPixelV(hdc,y,-x,color);
    SetPixelV(hdc,-y,x,color);
    SetPixelV(hdc,-y,-x,color);
}


VOID MyDrawCircle(HWND hwnd,HDC hdc,const POINT& pt,int R)
{
    //Ellipse(hdc,pt.x-R/2,pt.y-R/2,pt.x+R/2,pt.y+R/2);

    RECT rtClient;
    GetClientRect(hwnd,&rtClient);
    SIZE ptOldViewExt,ptOldWindowExt;
    POINT ptOldOrg;
    int OldMapMode = SetMapMode(hdc,MM_ANISOTROPIC);
    int iWidth = abs(rtClient.right - rtClient.left);
    int iHeight = abs(rtClient.bottom - rtClient.top);
    SetViewportExtEx(hdc,iWidth,iHeight,&ptOldViewExt);
    SetWindowExtEx(hdc,iWidth,-iHeight,&ptOldWindowExt);
    POINT ptOrg = {pt.x,pt.y};
    DPtoLP(hdc,&ptOrg,1);
    SetWindowOrgEx(hdc,-ptOrg.x,-ptOrg.y,&ptOldOrg);

    COLORREF color = RGB(0,0,0);
    double di = 0;
    int xi = 0,yi = R;
    DrawAxial(hdc,xi,yi,color);
    for(int i=1;i<=(int)(1.0*R/sqrt(2.0));++i)
    {
        di = (xi+1)*(xi+1) + (yi-0.5)*(yi-0.5) - R*R;
        ++xi;
        if(di >= 0)
        {
            --yi;
        }
        DrawAxial(hdc,xi,yi,color);
    }

    SetViewportExtEx(hdc,ptOldViewExt.cx,ptOldViewExt.cy,NULL);
    SetWindowExtEx(hdc,ptOldWindowExt.cx,ptOldWindowExt.cy,NULL);
    SetWindowOrgEx(hdc,ptOldOrg.x,ptOldOrg.y,NULL);

}

VOID MyDrawCircle(HWND hwnd,HDC hdc,int iLeft,int iTop,int iRight,int iBottom)
{
    POINT  pt = {(iLeft+iRight)/2,(iTop+iBottom)/2};
    MyDrawCircle(hwnd,hdc,pt,abs(iRight-iLeft)/2);
}

VOID OnPaint(HWND hwnd,WPARAM wParam,LPARAM lParam)
{
    RECT rtClient;
    GetClientRect(hwnd,&rtClient);
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd,&ps);
    HDC hMemDC = CreateCompatibleDC(hdc);
    HBITMAP hMemBmp = CreateCompatibleBitmap(hdc,rtClient.right,rtClient.bottom);
    HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC,hMemBmp);
    FillRect(hMemDC,&rtClient,WHITE_BRUSH);

    //POINT pt = {250,250};
    //MyDrawCircle(hwnd,hMemDC,pt,100);
    MyDrawCircle(hwnd,hMemDC,100,100,300,300);
    BitBlt(hdc,0,0,rtClient.right,rtClient.bottom,hMemDC,0,0,SRCCOPY);
    SelectObject(hMemDC,hOldBmp);
    DeleteObject(hMemBmp);
    EndPaint(hwnd,&ps);
}

LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_CREATE:
        OnCreate(hwnd,wParam,lParam);
        break;
    case WM_PAINT:
        OnPaint(hwnd,wParam,lParam);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }
    return 0;
}
View Code

 修改:全部代码这里的OnPaint函数中hMemDC忘记delelte了。。。

 

椭圆的扫描转换(先贴代码)

#include "Main.h"
#include<tchar.h>
#include<stdio.h>
#include<windows.h>
#include<math.h>

LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    WNDCLASS wndcls;
    wndcls.cbClsExtra = 0;
    wndcls.cbWndExtra = 0;
    wndcls.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wndcls.hCursor = LoadCursor(NULL,IDC_ARROW);
    wndcls.hIcon = LoadIcon(NULL,IDI_APPLICATION);
    wndcls.hInstance = hInstance;
    wndcls.lpfnWndProc = WinSunProc;
    wndcls.lpszClassName = _T("sunxin2006");
    wndcls.lpszMenuName = NULL;
    wndcls.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&wndcls);

    HWND hwnd = CreateWindow(_T("sunxin2006"),_T("helloworld"),WS_OVERLAPPEDWINDOW,
        0,0,600,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOW);
    UpdateWindow(hwnd);

    MSG msg;
    while(GetMessage(&msg,NULL,0,0)>0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

VOID OnCreate(HWND hwnd,WPARAM wParam,LPARAM lParam)
{
    int scrWidth,scrHeight;
    RECT rect;
    //获得屏幕尺寸
    scrWidth = GetSystemMetrics(SM_CXSCREEN);
    scrHeight = GetSystemMetrics(SM_CYSCREEN);
    //取得窗口尺寸
    GetWindowRect(hwnd,&rect);
    //重新设置rect里的值
    rect.left = (scrWidth-rect.right)/2;
    rect.top = (scrHeight-rect.bottom)/2;
    //移动窗口到指定的位置
    SetWindowPos(hwnd,HWND_TOP,rect.left,rect.top,rect.right,rect.bottom,SWP_SHOWWINDOW);
}

VOID DrawAxial(HDC hdc,int x,int y,COLORREF color)
{
    SetPixelV(hdc,x,y,color);
    SetPixelV(hdc,-x,y,color);
    SetPixelV(hdc,x,-y,color);
    SetPixelV(hdc,-x,-y,color);
}

VOID OnDrawElipse(HWND hwnd,HDC hdc,const POINT& pt,double a,double b)
{
    //Ellipse(hdc,pt.x-a,pt.y-b,pt.x+a,pt.y+b);
    RECT rtClient;
    GetClientRect(hwnd,&rtClient);
    int OldMapMode = SetMapMode(hdc,MM_ANISOTROPIC);
    SIZE OldViewExt,OldWindowExt;
    SetViewportExtEx(hdc,rtClient.right,rtClient.bottom,&OldViewExt);
    SetWindowExtEx(hdc,rtClient.right,-rtClient.bottom,&OldWindowExt);
    POINT OldOrg,ptOrg = {pt.x,pt.y};
    DPtoLP(hdc,&ptOrg,1);
    SetWindowOrgEx(hdc,-ptOrg.x,-ptOrg.y,&OldOrg);


    COLORREF color = RGB(0,0,0);
    int xi = 0,yi = b;
    int divison = a;
    if(a*a+b*b !=0)
    {
        divison = (int)(a*a/sqrt((a*a+b*b)));
    }
    DrawAxial(hdc,xi,yi,color);
    for(xi=1;xi<=divison;++xi)
    {
        double di = b*b*xi*xi+a*a*(yi-0.5)*(yi-0.5) - a*a*b*b;
        if(di >=0) --yi;
        DrawAxial(hdc,xi,yi,color);
    }
    --xi;
    for(;yi>=0;--yi)
    {
        double di = b*b*(xi+0.5)*(xi+0.5)+a*a*(yi-1)*(yi-1) - a*a*b*b;
        if(di < 0) ++xi;
        DrawAxial(hdc,xi,yi,color);
    }
    
    SetWindowOrgEx(hdc,OldOrg.x,OldOrg.y,NULL);
    SetWindowExtEx(hdc,OldWindowExt.cx,OldWindowExt.cy,NULL);
    SetViewportExtEx(hdc,OldViewExt.cx,OldViewExt.cy,NULL);
    SetMapMode(hdc,OldMapMode);
}
VOID OnPaint(HWND hwnd,WPARAM wParam,LPARAM lParam)
{
    RECT rtClient;
    GetClientRect(hwnd,&rtClient);
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd,&ps);
    HDC hMemDC = CreateCompatibleDC(hdc);
    HBITMAP hMemBmp = CreateCompatibleBitmap(hMemDC,rtClient.right,rtClient.bottom);
    HBITMAP hOldBmp = (HBITMAP) SelectObject(hMemDC,hMemBmp);
    FillRect(hMemDC,&rtClient,WHITE_BRUSH);

    POINT pt = {200,200};
    OnDrawElipse(hwnd,hMemDC,pt,150,100);

    BitBlt(hdc,0,0,rtClient.right,rtClient.bottom,hMemDC,0,0,SRCCOPY);
    SelectObject(hMemDC,hOldBmp);
    DeleteObject(hMemDC);
    DeleteObject(hMemBmp);
    EndPaint(hwnd,&ps);
}
LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_CREATE:
        OnCreate(hwnd,wParam,lParam);
        break;
    case WM_PAINT:
        OnPaint(hwnd,wParam,lParam);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }
    return 0;
}
View Code

 椭圆可以分为4对称,x轴对称,y轴对称。在第一象限椭圆上点为(x,y),则对称点(x,-y),(-x,y),(-x,-y)。由于第一象限中y/x的值是有abs(k)>1到abs(k)<1或者相反。所以要先求得abs(k)=1的情况,然后根据该分界线分为两部分。

求k=-1,其中的k为斜率。

  解:

    b^2*x^2 + a^2*y^2-a^2*b^2 = 0;

    对其求导 2*b*x + 2*a*y*y' = 0    =>   y1 = 1  =>  a^2*y = b^2*x

    带入椭圆方程求得(a^2/sqrt(a^2+b^2),b^2/sqrt(a^2+b^2))

       方程b^2*x^2 + a^2*y^2-a^2*b^2 = 0;

  当b<a时,如果a>b时,可以考虑一下。

  第一部分x在(0,a^2/sqrt(a^2+b^2))时:

    d1i = b^2*(xi+1)^2 +a^(yi-0.5)^2-a^y2b4*b^2;

    yi+1 = di>=0 ? yi-1 : yi;

    第二部分y在(b^2/sqrt(a^2+b^2))时:

    d2i = b^2*(xi+0.5)^2+a^2*(yi-1)^2 - a^2*b^2;

    xi+1 = d2i >= 0 ? xi:xi+1;

 

  直线扫描转换算法在处理非水平,非垂直,非45度的直线段时会出现锯齿,而画曲线更是如此!!!因此出现了反走样技术来抗锯齿。反走样技术主要分为两类:一类是硬件技术,通过提高显示器的分别率来实现,另一类是软件技术,通过改进算法来实现。软件反走样技术主要是加权区域采样。

   Wu反走样算法

  该算法是采取空间混色原理来对走样进行修正。空间混色原理指出,人眼对某一区域颜色的识别是取这个区域的平均值。Wu反走样算法原理是对于理想直线上的任意一点,同时以两个不同亮度等级的相邻像素来表示。

       推导公式:

    x^2+y^2 - R^2 = 0;

    y = sqrt(R^2-X^2);

    y0 = int(y),y1 = ceil(y);

    最后结论(x+1,y0,255*(y-y0)),(x+1,y1,255*(y-y1));第三个是颜色。

  代码:

  以下代码是画一个黑色圆圈,请把字体设成多字节然后编译。

#include "Main.h"
#include<tchar.h>
#include<stdio.h>
#include<windows.h>
#include<math.h>

LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    WNDCLASS wndcls;
    wndcls.cbClsExtra = 0;
    wndcls.cbWndExtra = 0;
    wndcls.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wndcls.hCursor = LoadCursor(NULL,IDC_ARROW);
    wndcls.hIcon = LoadIcon(NULL,IDI_APPLICATION);
    wndcls.hInstance = hInstance;
    wndcls.lpfnWndProc = WinSunProc;
    wndcls.lpszClassName = _T("sunxin2006");
    wndcls.lpszMenuName = NULL;
    wndcls.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&wndcls);

    HWND hwnd = CreateWindow(_T("sunxin2006"),_T("helloworld"),WS_OVERLAPPEDWINDOW,
        0,0,600,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOW);
    UpdateWindow(hwnd);

    MSG msg;
    while(GetMessage(&msg,NULL,0,0)>0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

VOID OnCreate(HWND hwnd,WPARAM wParam,LPARAM lParam)
{
    int scrWidth,scrHeight;
    RECT rect;
    //获得屏幕尺寸
    scrWidth = GetSystemMetrics(SM_CXSCREEN);
    scrHeight = GetSystemMetrics(SM_CYSCREEN);
    //取得窗口尺寸
    GetWindowRect(hwnd,&rect);
    //重新设置rect里的值
    rect.left = (scrWidth-rect.right)/2;
    rect.top = (scrHeight-rect.bottom)/2;
    //移动窗口到指定的位置
    SetWindowPos(hwnd,HWND_TOP,rect.left,rect.top,rect.right,rect.bottom,SWP_SHOWWINDOW);
}


VOID DrawAx(HDC hdc,int x,int y,COLORREF c)
{
    SetPixelV(hdc,x,y,c);
    SetPixelV(hdc,-x,y,c);
    SetPixelV(hdc,x,-y,c);
    SetPixelV(hdc,-x,-y,c);
    SetPixelV(hdc,y,x,c);
    SetPixelV(hdc,y,-x,c);
    SetPixelV(hdc,-y,x,c);
    SetPixelV(hdc,-y,-x,c);
}
VOID DrawMyCircle(HWND hwnd,HDC hdc,WPARAM wParam,LPARAM lParam)
{
    int R = 50;
    RECT rtClient;
    GetClientRect(hwnd,&rtClient);
    POINT pt = {300,200};    //圆心
    Ellipse(hdc,pt.x-R,pt.y-R,pt.x+R,pt.y+R);
    pt.x = 100;
    int OldMapMode = SetMapMode(hdc,MM_ANISOTROPIC);
    SIZE OldViewSize,OldWindowSize;
    POINT ptOldOrg;
    SetViewportExtEx(hdc,rtClient.right,rtClient.bottom,&OldViewSize);
    SetWindowExtEx(hdc,rtClient.right,-rtClient.bottom,&OldWindowSize);
    DPtoLP(hdc,&pt,1);
    SetWindowOrgEx(hdc,-pt.x,-pt.y,&ptOldOrg);

    double x=0,y=R;
    DrawAx(hdc,0,R,RGB(0,0,0));
    for(x=1;x<=1.0*R/sqrt(2.0);++x)
    {
        y = sqrt(R*R-x*x);
        double y0 = int(y),y1 = ceil(y);
        int c0 = (int)(255.0*(y-y0)),c1 = (int)(255.0*(y1-y));
        DrawAx(hdc,x,y0,RGB(c0,c0,c0));
        DrawAx(hdc,x,y1,RGB(c1,c1,c1));
    }


    SetWindowOrgEx(hdc,ptOldOrg.x,ptOldOrg.y,NULL);
    SetWindowExtEx(hdc,OldWindowSize.cx,OldWindowSize.cy,NULL);
    SetViewportExtEx(hdc,OldViewSize.cx,OldViewSize.cy,NULL);
    SetMapMode(hdc,OldMapMode);
}
VOID OnPaint(HWND hwnd,WPARAM wParam,LPARAM lParam)
{
    RECT rtClient;
    GetClientRect(hwnd,&rtClient);
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd,&ps);
    HDC hMemDC = CreateCompatibleDC(hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc,rtClient.right,rtClient.bottom);
    SelectObject(hMemDC,hBitmap);
    FillRect(hMemDC,&rtClient,WHITE_BRUSH);

    DrawMyCircle(hwnd,hMemDC,wParam,lParam);

    BitBlt(hdc,0,0,rtClient.right,rtClient.bottom,hMemDC,0,0,SRCCOPY);
    DeleteObject(hBitmap);
    DeleteObject(hMemDC);
    EndPaint(hwnd,&ps);
}
LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_PAINT:
        OnPaint(hwnd,wParam,lParam);
        break;
    case WM_CREATE:
        OnCreate(hwnd,wParam,lParam);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd,uMsg,wParam,lParam);
    }
    return 0;
}
View Code

  第一个圆是经过反走样处理的,第二个圆是未处理过的。

  彩色反走样就是线条颜色到背景颜色的渐变过程,假设线段颜色为(rf,gf,bf),背景色(rb,gb,bb),则像素颜色为RGB((rb-rf)*ei,(gb-gf)*ei,(bb-bf)*ei+bi);