|
![]() |
2018年12月02日 |
![]()
|
如何由GDI+繪圖工具取得SPline、Bezier、Ellipse等曲線座標及現場放樣 一、 前言 GDI+(VB.Net)函數中,有繪製雲狀線(Cubic SPline)、比賽爾(Bezier curve)及橢圓(Ellipse)曲線之工具,但其並無直接提供上述曲線之座標,因此如欲於現場放樣,就可能會產生問題,本文將介紹如何利用VB.Net之AddPath、Flatten及PathData等工具以取得上述曲線之座標,在圖上位置佈設一基準線AB,計算AB與AX(X為曲線上)之夾角Cita及AX距離L,在工地放樣時,我們可以將經緯儀等測量儀器架設於對應於圖上基準點A之工址,固定上、下盤,後視點B後放鬆上盤,依圖上計算角度方向旋轉cita角後固定上盤,使用橫軸制動螺絲 調整望遠鏡方向後固定方向,然後量取距離L得X,如此依序逐點佈設曲線。
圖1.SPline曲線
圖2.SBesier曲線
二、 繪製曲線及計算曲線座標 一般Spline曲線都會通過已知測點,曲線形狀受張力箝制大小(0.0~1.0)影響,而Bezier曲線只通過起點及終點,其形狀受第1,2點方向及地第(n,n-1)點方向控制。VB.Net DrawCurve(SPline)曲線點數可以不受限制;而DrawBezier曲線點數限制為4點,DrawBeziers曲線點數限制為(3n+1)點,n=1時等同DrawBezier。 利用VB或VB.Net自定程式實作前述曲線,可以很容易地在網路上搜尋下載,因此本文不擬專文介紹,下面我們要討論的是如何利用VB.Net命名空間所提供的工具來測繪SPline及Bezier曲線,假設User想繪製通過點A(-9.1,-7),B(4,5),C(5,2),D(1,5,-8.7)四控制點之雲狀線或Bezier曲線及其曲線關鍵之座標,可以依下列步驟繪製及計算:
(a)利用路徑扁平成線段 (1) 將控制點座標(浮點Real資料型態,世界實際座標)轉換成以表單圖框之螢幕像素座標(Pixel整數資料型態)。 (2) 利用DrawCurve繪製曲線 (3) 利用AddPath將曲線轉化為路徑(Path) (4) 將路徑扁平(Flatten)為多重線(資料型態為Real) (5) 利用PathPoints()擷取多重線座標 (6) 放樣距離、角度計算
如欲求某特定之x(y)所對應之y(x)值所有解答,求x=xg(y=yg)直線與扁平化後路徑多重線之所有實夾點即可。
(b)利用讀取影像顏色擷取曲線座標 (1) 將控制點座標(浮點Real資料型態,世界實際座標)轉換成以表單圖框之螢幕像素座標(Pixel整數資料型態)。 (2) 利用DrawCurve繪製曲線 (3) 計算曲線外圍最小包含矩形 (4) 在包含矩形內由上至下(或由下至上)逐條掃瞄水平線,搜尋及記錄與圖框背景色不同之像素位置(Pixel整數資料型態) (5) 將掃瞄出像素位置轉換世界座標點型態 (6) 放樣距離、角度計算
如欲求某特定之x(y)所對應之y(x)值所有解答,可設定i=Cint(xg)[j=cint(yg)] ,然後利用水平(垂直)直接掃瞄即可。
三、 程式碼編寫 上述兩種測繪曲線之程式碼主要有
(a) 世界座標及螢幕座標轉換 Public Function ptConvertToUser(ByVal canvas As PictureBox, ByVal xLeft As Single, ByVal xRight As Single, ByVal yTop As Single, ByVal yBottom As Single, _ ByVal xPix As Integer, ByVal yPix As Integer) As PointF ptConvertToUser.X = CSng(xRight - xLeft) / CSng(canvas.ClientSize.Width) * xPix + xLeft ptConvertToUser.Y = CSng(yBottom - yTop) / CSng(canvas.ClientSize.Height) * yPix + yTop End Function Public Function ptConvertToPix(ByVal canvas As PictureBox, ByVal xLeft As Double, ByVal xRight As Double, ByVal yTop As Double, ByVal yBottom As Double, _ ByVal xUser As Single, ByVal yUser As Single) As PointF ptConvertToPix.X = xUser * CSng(canvas.ClientSize.Width) / (xRight - xLeft) - xLeft * CSng(canvas.ClientSize.Width) / (xRight - xLeft) ptConvertToPix.Y = yUser * CSng(canvas.ClientSize.Height) / (yBottom - yTop) - yTop * CSng(canvas.ClientSize.Height) / (yBottom - yTop) End Function Public Function ptConvertToUser(ByVal canvas As PictureBox, ByVal xLeft As Single, ByVal xRight As Single, ByVal yTop As Single, ByVal yBottom As Single, _ ByVal ptPix As Point) As PointF ptConvertToUser.X = CSng(xRight - xLeft) / CSng(canvas.ClientSize.Width) * ptPix.X + xLeft ptConvertToUser.Y = CSng(yBottom - yTop) / CSng(canvas.ClientSize.Height) * ptPix.Y + yTop End Function Public Function ptConvertToPix(ByVal canvas As PictureBox, ByVal xLeft As Double, ByVal xRight As Double, ByVal yTop As Double, ByVal yBottom As Double, _ ByVal PtUser As PointF) As Point ptConvertToPix.X = PtUser.X * CSng(canvas.ClientSize.Width) / (xRight - xLeft) - xLeft * CSng(canvas.ClientSize.Width) / (xRight - xLeft) ptConvertToPix.Y = PtUser.Y * CSng(canvas.ClientSize.Height) / (yBottom - yTop) - yTop * CSng(canvas.ClientSize.Height) / (yBottom - yTop) End Function 上述中 xLeft為包含資料點之x最小值 xRight為包含資料點x最大值 yTop為包含資料點之y最小值 yBottom為包含資料點y最大值
(b)繪製曲線扁平擷取座標(以Spline為例) Private Sub DrawSpline_Flatten() Dim ptspath() As PointF Dim ptsUser() As PointF myPen = Pens.Green myBmp = New Bitmap(PictureBox1.Size.Width, PictureBox1.Size.Height) myBmpGraphics = Graphics.FromImage(myBmp) Dim colorBase As Color = myBmp.GetPixel(1, 1) Dim ptsApi() As Point ‘轉換後整數資料點 PtsData_Bounds(PictureBox1, ptsOrg, ptsApi) ‘曲線外圍矩形 For i As Integer = 0 To ptsApi.Length - 1 ListBox1.Items.Add("org x=" & ptsOrg(i).X & ";y= " & ptsOrg(i).Y & " ;api x= " & ptsApi(i).X & " ;y= " & ptsApi(i).Y) myBmpGraphics.DrawEllipse(Pens.Blue, ptsApi(i).X - 4, ptsApi(i).Y - 4, 8, 8) Next Dim myGraphicsPath = New GraphicsPath myPen = Pens.Red Dim tension As Single = 0.5 ‘張力鉗制係數(0~1.0) myGraphicsPath.AddCurve(ptsApi, 0.5) Dim rect As RectangleF = myGraphicsPath.GetBounds myBmpGraphics.DrawRectangle(New Pen(Color.Green, 1), CInt(rect.X), CInt(rect.Y), CInt(rect.Width), CInt(rect.Height)) myBmpGraphics.DrawPath(myPen, myGraphicsPath) Dim count As Integer = 0 myGraphicsPath.Flatten() ‘曲線路徑扁平成折線 ptspath = myGraphicsPath.PathPoints For i As Integer = 0 To ptspath.Length - 1 myBmpGraphics.DrawEllipse(Pens.Blue, ptspath(i).X - 2, ptspath(i).Y - 2, 4, 4) ListBox1.Items.Add("spline x=" & ptspath(i).X & "y=" & ptspath(i).Y) Next Dim ptsBaseline(1) As PointF ptsBaseline(0) = New PointF(100, 100) ptsBaseline(1) = New PointF(200, 100)
For i As Integer = 0 To ptspath.Length - 1 myBmpGraphics.DrawLine(Pens.Green, ptsBaseline(0), ptspath(i)) ReDim Preserve ptsUser(i) ptsUser(i) = ptConvertToUser(PictureBox1, xLeft, xRight, yTop, yBottom, CInt(ptspath(i).X), CInt(ptspath(i).Y)) ‘轉換為世界座標 ListBox1.Items.Add("ptUser on path :x=" & ptsUser(i).X & ";y= " & ptsUser(i).Y) Next Dim lDist() As Double, angsIncl() As Double, angsPtI() As Double Call Stake(PictureBox1, ptsUser, ptsBaseline, lDist, angsIncl, angsPtI) ‘放樣資料計算 ‘lDist:測站至曲線測點距離 ‘angsIncl: 測站基基準線與測點夾角(deg.) ‘angsPtI:座標水平線與測點夾角(deg.),方向同座標角度量測 myBmpGraphics.DrawLine(New Pen(Color.Magenta, 2), ptsBaseline(0), ptsBaseline(1)) PictureBox1.Image = myBmp End Sub
(c)繪製曲線後掃瞄擷取座標(以Bezier為例)
Private Sub DrawBezier_getPixel() Dim ptsUser() As PointF myPen = Pens.Green myBmp = New Bitmap(PictureBox1.Size.Width, PictureBox1.Size.Height) myBmpGraphics = Graphics.FromImage(myBmp) Dim colorBase As Color = myBmp.GetPixel(1, 1) Dim ptsApi() As Point
PtsData_Bounds(PictureBox1, ptsOrg, ptsApi) For i As Integer = 0 To ptsApi.Length - 1 ListBox1.Items.Add("org x=" & ptsOrg(i).X & ";y= " & ptsOrg(i).Y & " ;api x= " & ptsApi(i).X & " ;y= " & ptsApi(i).Y) Next Dim myGraphicsPath = New GraphicsPath myPen = Pens.Red myGraphicsPath.AddBeziers(ptsApi) Dim rect As RectangleF = myGraphicsPath.GetBounds myBmpGraphics.DrawRectangle(New Pen(Color.Green, 1), CInt(rect.X), CInt(rect.Y), CInt(rect.Width), CInt(rect.Height)) myBmpGraphics.DrawPath(myPen, myGraphicsPath) Dim count As Integer = 0, ptsUserApi() As Point ‘矩形框掃瞄 For j As Integer = CInt(rect.Y) + 2 To CInt(rect.Height + rect.Y) - 2 For i As Integer = CInt(rect.X) + 2 To CInt(rect.Width + rect.X) - 2 If myBmp.GetPixel(i, j) <> colorBase Then ReDim Preserve ptsUser(count), ptsUserApi(count) ptsUserApi(count) = New Point(i, j) ptsUser(count) = ptConvertToUser(PictureBox1, xLeft, xRight, yTop, yBottom, i, j) ListBox1.Items.Add(" x=" & i & " ;y=" & j & " ;xuser= " & ptsUser(count).X & " ;yuser=" & ptsUser(count).Y) count += 1 End If Next i Next j Dim ptsBaseline(1) As PointF ptsBaseline(0) = New PointF(100, 100) ptsBaseline(1) = New PointF(200, 100) For i As Integer = 0 To UBound(ptsUserApi) Step 10 myBmpGraphics.DrawLine(New Pen(Color.Green, 1), ptsBaseline(0), ptsUserApi(i)) Next myBmpGraphics.DrawLine(Pens.Magenta, ptsBaseline(0), ptsBaseline(1)) Dim lDist() As Double, angsIncl() As Double, angsPtI() As Double Call Stake(PictureBox1, ptsUser, ptsBaseline, lDist, angsIncl, angsPtI) For i As Integer = 0 To ptsApi.Length - 1 myBmpGraphics.DrawEllipse(Pens.Blue, ptsApi(i).X - 4, ptsApi(i).Y - 4, 8, 8) Next PictureBox1.Image = myBmp
(d)繪製曲線放樣計算 Private Sub Stake(ByVal canvas As PictureBox, ByVal ptsPathUser() As PointF, ByVal ptsBaseLineAPI() As PointF, ByRef Ldist() As Double, ByRef angsIncl() As Double, ByRef angsPtI() As Double)
Dim ptsBaseUser(0 To 1) As PointF For i As Integer = 0 To 1 ptsBaseUser(i) = ptConvertToUser(canvas, xLeft, xRight, yTop, yBottom, ptsBaseLineAPI(i).X, ptsBaseLineAPI(i).Y) ListBox1.Items.Add("pt_ " & i & " ;baseLine= " & ptsBaseUser(i).X & ";" & ptsBaseUser(i).Y) Next
Dim delX1 As Double = ptsBaseUser(1).X - ptsBaseUser(0).X Dim delY1 As Double = ptsBaseUser(1).Y - ptsBaseUser(0).Y Dim L1 As Double = Sqrt(delX1 * delX1 + delY1 * delY1) For i As Integer = 0 To ptsPathUser.Length - 1 Dim delX2 As Double = CDbl(ptsPathUser(i).X - ptsBaseUser(0).X) Dim delY2 As Double = CDbl(ptsPathUser(i).Y - ptsBaseUser(0).Y) Dim L2 As Double = Sqrt(delX2 * delX2 + delY2 * delY2) ReDim Preserve angsIncl(i), Ldist(i), angsPtI(i) angsIncl(i) = Acos((delX1 * delX2 + delY1 * delY2) / L1 / L2) * 180 / PI angsPtI(i) = Atan2(delY2 - delY1, delX2 - delX1) * 180 / PI If angsPtI(i) < 0.0F Then angsPtI(i) = angsPtI(i) + 360.0 Ldist(i) = L2 ListBox1.Items.Add(" pt_ " & i & " ;X= " & Round(ptsPathUser(i).X, 5) & " ;Y= " & Round(ptsPathUser(i).Y, 5) & " ;ldist= " & Round(Ldist(i), 5) & " ;ang= " & Round(angsIncl(i), 5) & " ;angPtI= " & Round(angsPtI(i), 5)) Next
End Sub 四、 結論及建議 圖3及圖4分別為SPline及BEZIER曲線利用路徑曲線扁平轉換為線段之成果示圖;圖5及圖6分別為SPline及BEZIER曲線利用圖像掃瞄之成果示圖。兩不同方法會因取點位置不同而座標值會有所差異,如兩者所取位置相同,則利用不同方法會有極相近之計算結果。如欲繪製橢圓(弧),則可利用畫圖物件畫圖,然後比照前述方法測繪及放樣。圖7為以A(-9.1,-7),B(4,5),C(5,2),D(1,5,-8.7)為包含矩形所繪製之橢圓弧以Flatten所取得的曲線點座標。
圖3 SPline曲線(Flatten法)
圖4 BEzier曲線(Flatten法)
圖5 SPline曲線(GetPIxel法)
圖6 Besier曲線(GetPIxel法)
圖7 Ellipse曲線(Flatten法)
|
上次修改此網站的日期: 2018年11月25日