簡介
Adobe Photoshop有兩個非常專業的用來設置如投影、斜面與浮雕等特效的控件:一個是角度選擇器,另一個是角度與高度選擇器(如上圖所示)。
本文將帶領讀者創建兩個自定義控件,來模仿Photoshop中這兩個控件的外觀和行為。
基礎知識——數學
畢達哥拉斯定理
(即勾股定理,為尊重原文,以下簡稱畢氏定理。盡管有點繞口。——野比注)
利用畢氏定理,我們可以計算直角三角形的斜邊(最長邊)。計算公式為。這樣,斜邊c就等于
。
單位圓
鑒于接下來的工作和角度及圓有關,我們先熟悉一下單位圓的形式是很有好處的。單位圓就是以(0,0)為圓心,半徑為1的圓。在常規網格(指畫布——野比注)中,0度(的坐標)從(1,0)這點(右)開始,按逆時針方向增大。因此,90度是(0,1),180度是(-1,0),270度是(0,-1),最后360度和0點重合。
三角函數
這里我們只需要知道三個基本的三角函數:sin、cos和tan(正弦、余弦和正切——野比注)。如果我們還記得SOH-CAH-TOA(譯注+)的話,我們就知道,(直角)三角形的正弦等于對邊比上斜邊,余弦等于鄰邊比上斜邊,正切等于對邊比上鄰邊。
同樣,我們知道反三角函數用來計算未知角度。
譯注+:
SOH-CAH-TOA是老外用來記憶三角函數的口訣。其中:O為opposite(對邊),H為Hypotenuse(斜邊),A為Adjacent(鄰邊)。
SOH: Sine = Opposite ÷ Hypotenuse
CAH: Cosine = Adjacent ÷ Hypotenuse
TOA: Tangent = Opposite ÷ Adjacent
常用函數
我們制作的自定義控件都會用到下面這兩個重要的函數(方法):
一個函數接收角度和半徑作為參數,返回圍繞某個原點的相應點位置。(簡單來說,就是把角度轉換為點)
一個完成相反的功能,以點(X, Y)作為參數,找到最匹配的角度。
第一個函數要簡單些:
private PointF DegreesToXY(float degrees, float radius, Point origin) { PointF xy = new PointF(); double radians = degrees * Math.PI / 180.0; xy.X = (float)Math.Cos(radians) * radius + origin.X; xy.Y = (float)Math.Sin(-radians) * radius + origin.Y; return xy; }
要注意的是首先我們需要把角度換算成弧度。一般來說,我們只需要在單位圓中進行研究:
該函數已知角度和半徑,利用三角函數,我們算出X和Y值,然后在加上給定的原點初始坐標即可。
還應看到,函數代碼中用到的是Y分量的負值,這是因為計算機顯示器上網格是上下顛倒的(向下為正)。
第二個函數的功能是把用戶在控件上點擊的點位置轉換為相應的角度值。這稍稍麻煩點,因為我們不得不考慮添加一些東西。限于文章篇幅,我這里貼出部分代碼:
private float XYToDegrees(Point xy, Point origin) { double angle = 0.0; if (xy.Y < origin.Y) { if (xy.X > origin.X) { angle = (double)(xy.X - origin.X) / (double)(origin.Y - xy.Y); angle = Math.Atan(angle); angle = 90.0 - angle * 180.0 / Math.PI; } else if (xy.X < origin.X) { //如此這般 } } else if (xy.Y > origin.Y) { //如此這般 } if (angle > 180) angle -= 360; //控制角度范圍 return (float)angle; }
該函數主要通過檢查鼠標相對中心點的位置,確定其所在象限。一旦我們知道了象限,就可以利用三角函數(反正切)計算出角度。
如果角度大于180度,則減去360度。這樣就和Photoshop一樣,把角度控制在-180度和180度之間。當然,這一步可以不做,不加這行代碼控件一樣能用。
制作控件
繪制控件
這兩個控件的背景相同:
用寬度為2的Pen繪制外圈圓
用40%(約100)不透明度的白色填充
控件中心是3x3像素的正方形
protected override void OnPaint(PaintEventArgs e) { //... //Draw g.SmoothingMode = SmoothingMode.AntiAlias; g.DrawEllipse(outline, drawRegion); g.FillEllipse(fill, drawRegion); //...光標 g.SmoothingMode = SmoothingMode.HighSpeed; g.FillRectangle(Brushes.Black, originSquare); //... }
注意SmoothMode屬性。在繪制圓圈時將該屬性設置為AntiAlias(抗鋸齒),這樣看起來既光滑又專業。但是如果畫正方形時也用抗鋸齒,就會顯得模糊難看,所以將SmoothMode設置為HighSpeed(高速),這樣畫出的正方形邊緣整齊犀利。
根據控件不同,光標也有不同繪制方法。角度選擇器比較簡單,只需要從圓心到DegreesToXY函數返回的點連一條直線即可。角度與高度選擇器則是在這點上繪制一個1x1的矩形,然后在周圍繪制一個十字型光標。
處理用戶點擊
多虧我們有了XYToDegrees函數,處理用戶點擊變得特別簡單。為了讓我們的控件用起來和Photoshop一模一樣,我們需要設置MouseDown和MouseMove事件。這樣,各項數值將實時更新。這里是一個附注函數的代碼:
private int findNearestAngle(Point mouseXY) { int thisAngle = (int)XYToDegrees(mouseXY, origin); if (thisAngle != 0) return thisAngle; else return -1; }
高度控件需要額外的處理,就是找到中心點和鼠標點擊點的距離:
private int findAltitude(Point mouseXY) { float distance = getDistance(mouseXY, origin); int alt = 90 - (int)(90.0f * (distance / origin.X)); if (alt < 0) alt = 0; return alt; }
在Photoshop中,選擇點(指鼠標點擊點)在圓心時,高度為90,在邊緣處則為0。這樣,我們可以通過找到點擊點到圓心距離和半徑高度比值來計算出高度。然后,用90減去該值(實際上是按90到0來翻轉一下)。
自定義事件
為了讓我們的自定義控件更加專業,需要控件能夠在數值發生變化時以編程方式進行提醒。這就是我們要設置事件的原因。
例如,像這樣給角度變化添加一個事件:
public delegate void AngleChangedDelegate(); public event AngleChangedDelegate AngleChanged;
然后,我們要做的就是每次變更Angle屬性時,調用AngleChanged()(需要先判斷是否為null)。
限制與改進
閃爍
沒有閃爍!只需要在制作控件時設置DoubleBuffered屬性為true,.NET Framework 2.0會處理剩下的工作,保證控件能流暢的重繪。
尺寸
因為控件使用基于半徑(圓)的數學計算方法,因此需要保證控件的長度和寬度相等。
顏色
我是照著Photoshop的樣子來的,所以并沒包含背景顏色、外圈顏色這些屬性。但是,瀏覽下代碼,你會發現改成你喜歡的顏色或者讓顏色可以動態修改并不是什么難事。
結論
我建議你下載項目文件(或者至少下載DEMO),這樣你可以看到這倆控件用起來很爽。
協議
本文及有關代碼、程序均基于CPOL(Codeproject Open License)協議。
更多Photoshop樣式的角度和高度選擇器控件 相關文章請關注PHP中文網!
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com