C++,MFC模板,VS2017算法
畫直線(DDA,中點,Bresenham)數據結構
一、DDA畫線法函數
直線方程:y=kx+bthis
增量處理:y_i+1 = y_i + kspa
void CLine01View::DDALine() { CDC* pDC = GetDC(); int x, y, k, _k, dx, dy; x = begin.x, y = begin.y; dx = end.x - begin.x, dy = end.y - begin.y; k = dy / dx, _k = dx / dy; if (abs(k) < 1) {//斜率的絕對值小於1 if (begin.x > end.x) {//從左到右畫 CPoint temp; temp = begin; begin = end; end = temp; } for (x; x <= end.x; x++) { pDC->SetPixel(x, int(y + 0.5), m_color);//四捨五入 y = y + k; } } else {//大於1 if (begin.y > end.y) {//從下到上畫 CPoint temp; temp = begin; begin = end; end = temp; } for (y; y <= end.y; y++) { pDC->SetPixel(int(x + 0.5), y, m_color); x = x + _k; } } ReleaseDC(pDC); }
優勢:邏輯簡單.net
缺點:k值和四捨五入包含浮點運算3d
二、中點畫線法code
直線方程:F(x,y)= Ax + By + C = 0視頻
避免浮點運算(直接計算中點):blog
d0 = A(xi + 1)+ B(yi + 0.5)+ C = 0 + A + 0.5B
增量處理:
d<0時,d1 = A(xi + 2)+ B(yi + 1.5)+ C = d0 + A + B
d>0時,d2 = A(xi + 2)+ B(yi + 0.5)+ C = d0 + A
void CLine01View::MidPointLine()
{ CDC* pDC = GetDC(); int A, B, d, d1, d2, x, y; if (begin.x > end.x) {//確保begin在左 CPoint temp; temp = begin; begin = end; end = temp; } A = begin.y - end.y; B = end.x - begin.x; x = begin.x, y = begin.y; if (abs(A) < B) {//0<|k|<1 if (A < 0) {//單調增 k>0 d = 2 * A + B, d1 = 2 * (A + B), d2 = 2 * A; pDC->SetPixel(x, y, m_color); while (x < end.x) { if (d < 0) { x++, y++, d = d + d1; } else { x++, d = d + d2; } pDC->SetPixel(x, y, m_color); } } else {//單調減 k<0 d = 2 * A - B, d1 = 2 * A, d2 = 2 * (A - B); pDC->SetPixel(x, y, m_color); while (x < end.x) { if (d < 0) { x++, d += d1; } else { x++, y--, d += d2; } pDC->SetPixel(x, y, m_color); } } } else {//大斜率 |k|>1 if (A < 0) {//增 k>0 d = 2 * B + A, d1 = 2 * B, d2 = 2 * (A + B); pDC->SetPixel(x, y, m_color); while (y < end.y) { if (d > 0) { y++, x++, d += d2; } else { y++, d += d1; } pDC->SetPixel(x, y, m_color); } } else {//減 k<0 d = A - 2 * B, d1 = 2 * (A - B), d2 = -2 * B; pDC->SetPixel(x, y, m_color); while (y > end.y) { if (d < 0) { y--, x++, d += d1; } else { y--, d += d2; } pDC->SetPixel(x, y, m_color); } } } ReleaseDC(pDC);
}
優勢:避免浮點運算
缺點:受直線方程限制
三、Bresenham算法
(有兩種解題思路,實操計算,結果都同樣,我的感受 e = k - 0.5 比較好理解)
偏差項:d = d + k (一旦d >= 1,d = d - 1)
思路:e0 = - 0.5;e = e + k;if(e > 0)e = e - 1;
調整(同時擴大 2dx 倍):e0 = - dx;e = e + 2dy;if(e > 0)e = e - 2dx;
void CLine01View::BresenhamLine() { CDC* pDC = GetDC(); int dx, dy, e, x, y; dx = abs(end.x - begin.x), dy = abs(end.y - begin.y); x = begin.x, y = begin.y; int f1, f2, interchange; (end.x - begin.x) >= 0 ? f1 = 1 : f1 = -1; (end.y - begin.y) >= 0 ? f2 = 1 : f2 = -1;
if (dy > dx) {//斜率|k|>1 時,=>|_k| int temp = dx; dx = dy; dy = temp; interchange = 1; } else { interchange = 0; }
e = 2 * dy - dx; pDC->SetPixel(x, y, m_color); for (int i = 1; i <= dx; i++) { if (e >= 0) { if (interchange == 1) { x += f1; } else { y += f2; } pDC->SetPixel(x, y, m_color); e = e - 2 * dx; } else { if (interchange == 1) { y += f2; } else { x += f1; } pDC->SetPixel(x, y, m_color); e = e + 2 * dy; } }
ReleaseDC(pDC); }
優勢:目前來講最好
缺點:不明
畫圓(中點,Bresenham)
(條件:第一象限,起點(0,r),圓具備八對稱性,橢圓四對稱)
一、中點畫圓
圓方程:F(x,y)= x^2 + y^2 - r^2 = 0
計算中點:
d0 = (xi + 1)^2 + (yi + 0.5)^2 + r^2 = 1.25 - r
增量處理:
d<0時,d1 = (xi + 2)^2 + (yi - 0.5)^2+ r^2 = d0 + 2xi + 3
d>0時,d2 = (xi + 2)^2 + (yi - 1.5)^2 + r^2 = d0 + 2(xi - yi) + 5
void CLine01View::Midpointcircle() { CDC* pDC = GetDC(); int xc = begin.x; int yc = begin.y; int r = 50; int x, y; float d; x = 0; y = r; d = 1.25 - r; while (x <= y) { if (d < 0) d += 2 * x + 3; else { d += 2 * (x - y) + 5; y--; } x++; pDC->SetPixel((xc + x), (yc + y), m_color); pDC->SetPixel((xc - x), (yc + y), m_color); pDC->SetPixel((xc + x), (yc - y), m_color); pDC->SetPixel((xc - x), (yc - y), m_color); pDC->SetPixel((xc + y), (yc + x), m_color); pDC->SetPixel((xc - y), (yc + x), m_color); pDC->SetPixel((xc + y), (yc - x), m_color); pDC->SetPixel((xc - y), (yc - x), m_color); } ReleaseDC(pDC); }
二、Bresenham畫圓
(y^2 = r^2 - (xi + 1)^2 ;d1 = yi^2 - y^2 ;d2 = y^2 - (yi-1)^2 ;)
偏差項:pi = d1 - d2 = 2(xi + 1)^2 + yi^2 + (yi - 1)^2 - 2r^2
增量處理:pi+1 = pi + 4xi + 6
void CLine01View::Bresenhamcircle() { CDC* pDC = GetDC(); int xc = begin.x; int yc = begin.y; int r = 50; int x = 0; int y = r; int p = 3 - 2 * r; while (x <= y) { pDC->SetPixel((xc + x), (yc + y), m_color); pDC->SetPixel((xc - x), (yc + y), m_color); pDC->SetPixel((xc + x), (yc - y), m_color); pDC->SetPixel((xc - x), (yc - y), m_color); pDC->SetPixel((xc + y), (yc + x), m_color); pDC->SetPixel((xc - y), (yc + x), m_color); pDC->SetPixel((xc + y), (yc - x), m_color); pDC->SetPixel((xc - y), (yc - x), m_color); if (p < 0) { p = p + 4 * x + 6; } else { p = p + 4 * (x - y) + 10; y--; } x++; } ReleaseDC(pDC); }
三、中點算法畫橢圓
橢圓公式:F(x,y)= b^2x^2 + a^2y^2 - a^2b^2 = 0
法向量(偏導):N(x,y)= (dF/dx) i + (dF/dy) j = 2b^2 xi + 2a^2 yi
上半部分:d1 = F(xi + 1,yi - 0.5)
下半部分:d2 = F(xi + 0.5,yi - 1)
void CLine01View::Midpointellispe() { CDC* pDC = GetDC(); int a = 200, b = 100, xc = begin.x, yc = begin.y; int x, y; double d1, d2; x = 0, y = b; d1 = b * b + a * a*(-b + 0.25);//上半部分 while (b*b*(x + 1) < a*a*(y - 0.5)) {//法向量 if (d1 < 0) { d1 += b * b*(2 * x + 3); } else { d1 += b * b*(2 * x + 3) + a * a*(-2 * y + 2); y--; } x++; pDC->SetPixel((xc + x), (yc + y), m_color); pDC->SetPixel((xc - x), (yc + y), m_color); pDC->SetPixel((xc + x), (yc - y), m_color); pDC->SetPixel((xc - x), (yc - y), m_color); } d2 = sqrt(b*(x + 0.5)) + a * (y - 1) - a * b;//下半部分 while (y > 0) { if (d2 < 0) { d2 += b * b*(2 * x + 2) + a * a*(-2 * y + 3); x++; } else { d2 += a * a*(-2 * y + 3); } y--; pDC->SetPixel((xc + x), (yc + y), m_color); pDC->SetPixel((xc - x), (yc + y), m_color); pDC->SetPixel((xc + x), (yc - y), m_color); pDC->SetPixel((xc - x), (yc - y), m_color); } ReleaseDC(pDC); }
區域填充(掃描、種子)
(條件:凹/凸/內含環 多邊形)
void CLine01View::OnLButtonDblClk(UINT nFlags, CPoint point) { // TODO: 在此添加消息處理程序代碼和/或調用默認值 RedrawWindow(); CDC* pDC = GetDC(); CPen newpen(PS_SOLID, 1, RGB(255, 0, 0)); CPen *old = pDC->SelectObject(&newpen); spt[0] = CPoint(100, 100); spt[1] = CPoint(300, 100); spt[2] = CPoint(250, 250); spt[3] = CPoint(100, 250); spt[4] = CPoint(150, 200); spt[5] = CPoint(90, 180); spt[6] = CPoint(150, 150); spt[7] = CPoint(100, 100); pDC->Polyline(spt, 8); pDC->SelectObject(old); ReleaseDC(pDC); for (int i = 0; i < pointCount; i++) {//獲取最大和最小點 maxY = maxY > spt[i].y ? maxY : spt[i].y; minY = minY < spt[i].y ? minY : spt[i].y; } }
一、掃描線填充
(比較好理解,但不是最簡潔的。「活性邊表/新邊表」,請參考:https://blog.csdn.net/orbit/article/details/7368996)
此處用 CPtrArray 存放活性邊,自定義函數: fill(point_1,point_2)、getCrossPoint(point_0,point_1,y)
void CLine01View::OnScanfill() { // TODO: 在此添加命令處理程序代碼 int crossPointCount = 0; CPtrArray ptrPoint; for (int y = minY; y < maxY; y += 3) {//對每隔三行的線進行填充 crossPointCount = 0;//將交點數歸零 ptrPoint.RemoveAll(); //獲取與各邊的交點 for (int i = 1; i < pointCount; i++) {//對每一個點進行檢查 if (i < pointCount) {//前count-2個點 if (y < spt[i - 1].y&&y > spt[i].y || y > spt[i - 1].y&&y < spt[i].y) { crossPointCount++; CPoint p = getCrossPoint(spt[i - 1], spt[i], y);//得到掃描線與多邊形的交點 //存儲點 ptrPoint.Add(new CPoint(p.x, p.y)); } } } if (crossPointCount >= 2) {//填充 for (int i = 1; i <= crossPointCount; i += 2) {//每兩點內爲要填充區域 int x = ((CPoint*)ptrPoint.GetAt(i - 1))->x; int y = ((CPoint*)ptrPoint.GetAt(i - 1))->y; CPoint p0(x, y); x = ((CPoint*)ptrPoint.GetAt(i))->x; y = ((CPoint*)ptrPoint.GetAt(i))->y; CPoint p1(x, y); fill(p0, p1); } } } }
缺點:數據結構複雜,只適合軟件實現
二、注入填充(4-聯通,遞歸)
棧輔助理解:
簡稱:換色
void CLine01View::floodfill(int x,int y, COLORREF oldcolor, COLORREF newcolor) { CClientDC dc(this); if (dc.GetPixel(x,y) == oldcolor) { dc.SetPixel(x, y, newcolor); floodfill(x, y + 1, oldcolor, newcolor); floodfill(x, y - 1, oldcolor, newcolor); floodfill(x - 1, y, oldcolor, newcolor); floodfill(x + 1, y, oldcolor, newcolor); } }
三、種子掃描線填充(侷限)
(hhhh,我本身這麼叫的,書上說是種子填充,可是我以爲不嚴謹)
void CLine01View::OnSeedfill() { // TODO: 在此添加命令處理程序代碼 CDC* pDC = GetDC(); int color = RGB(0, 255, 0); int boundary = RGB(255, 0, 0); CPoint pt = s_point; int x, y; x = pt.x; y = pt.y; for (; y < maxY; y++) {//下 int current = pDC->GetPixel(x, y); while ((current != boundary) && (current != color)) {//右 pDC->SetPixel(x, y, color); x++; current = pDC->GetPixel(x, y); } x = pt.x; x--; current = pDC->GetPixel(x, y); while ((current != boundary) && (current != color)) {//左 pDC->SetPixel(x, y, color); x--; current = pDC->GetPixel(x, y); } x = pt.x; } x = pt.x; y = pt.y - 1; for (; y > minY; y--) {//上 int current = pDC->GetPixel(x, y); while ((current != boundary) && (current != color)) {//左 pDC->SetPixel(x, y, color); x++; current = pDC->GetPixel(x, y); } x = pt.x; x--; current = pDC->GetPixel(x, y); while ((current != boundary) && (current != color)) {//右 pDC->SetPixel(x, y, color); x--; current = pDC->GetPixel(x, y); } x = pt.x; } }
四、ex:
邊填充
柵欄填充
參考資料:
一、《計算機圖形學原理及算法教程》和青芳 編著
二、計算機圖形學 - 中國農業大學 趙明老師視頻
三、計算機圖形學 - 南京工學院 丁宇辰老師講解
本文采用CC BY 4.0知識共享許可協議。