基本图形的扫描转换这个名词不太容易理解,通俗地讲就是通过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; }
修改:全部代码这里的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; }
椭圆可以分为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; }
第一个圆是经过反走样处理的,第二个圆是未处理过的。
彩色反走样就是线条颜色到背景颜色的渐变过程,假设线段颜色为(rf,gf,bf),背景色(rb,gb,bb),则像素颜色为RGB((rb-rf)*ei,(gb-gf)*ei,(bb-bf)*ei+bi);