簡介
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: 銫下面這兩個重要的函數(方法):
一個函數接收角度和半徑作為參數,傳回圍繞某個原點的對應點位置。 (簡單來說,就是把角度轉換為點)
一個完成相反的功能,以點(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; }
也應看到,函數程式碼中用到的是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繪製外圈圓
用寬度為2的Pen繪製外圈圓
用40%(約1000%)
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中文网!