iOS 繪制貝塞爾曲線

1. 貝塞爾曲線的原理

以三階為例:設P0浦楣、P02袖肥、P2是一條拋物線上順序三個不同的點。過P0和P2點的兩切線交于P1點振劳,在P02點的切線交P0P1和P2P1于P01和P11椎组,則如下比例成立:

這是所謂拋物線的三切線定理

當P0历恐,P2固定寸癌,引入?yún)?shù)t,令上述比值為t:(1-t)弱贼,即有:

當P0蒸苇,P2固定,引入?yún)?shù)t吮旅,令上述比值為t:(1-t)溪烤,即有:

t從0變到1,第一庇勃、二式就分別表示控制二邊形的第一氛什、二條邊,它們是兩條一次Bezier曲線匪凉。將一枪眉、二式代入第三式得:


當t從0變到1時,它表示了由三頂點P0再层、P1贸铜、P2三點定義的一條二次Bezier曲線堡纬。

并且表明:

這二次Bezier曲線P02可以定義為分別由前兩個頂點(P0,P1)和后兩個頂點(P1,P2)決定的一次Bezier曲線的線性組合。

依次類推蒿秦,

由四個控制點定義的三次Bezier曲線P03可被定義為分別由(P0,P1,P2)和(P1,P2,P3)確定的二條二次Bezier曲線的線性組合烤镐,由(n+1)個控制點Pi(i=0,1,...,n)定義的n次Bezier曲線P0n可被定義為分別由前、后n個控制點定義的兩條(n-1)次Bezier曲線P0n-1與P1n-1的線性組合:

由此得到Bezier曲線的遞推計算公式

Bézier curve(貝塞爾曲線)是應用于二維圖形應用程序的數(shù)學曲線棍鳖。 曲線定義:起始點炮叶、終止點(也稱錨點)、控制點渡处。通過調整控制點镜悉,貝塞爾曲線的形狀會發(fā)生變化。 1962年医瘫,法國數(shù)學家Pierre Bézier第一個研究了這種矢量繪制曲線的方法侣肄,并給出了詳細的計算公式,因此按照這樣的公式繪制出來的曲線就用他的姓氏來命名醇份,稱為貝塞爾曲線稼锅。
以下公式中:B(t)為t時間下 點的坐標;

P0為起點,Pn為終點,Pi為控制點

一階貝塞爾曲線(線段):

意義:由 P0 至 P1 的連續(xù)點僚纷, 描述的一條線段

二階貝塞爾曲線(拋物線):

原理:
由 P0 至 P1 的連續(xù)點 Q0矩距,描述一條線段。
由 P1 至 P2 的連續(xù)點 Q1怖竭,描述一條線段剩晴。
由 Q0 至 Q1 的連續(xù)點 B(t),描述一條二次貝塞爾曲線侵状。

經(jīng)驗:P1-P0為曲線在P0處的切線赞弥。

三階貝塞爾曲線:

通用公式:


高階貝塞爾曲線:

4階曲線:
5階曲線:
貝塞爾曲線的推到過程:

2. 用Swift進行貝塞爾曲線繪制

2.1 普通線條繪制

用Swift進行普通線條的繪制代碼,如下:

import UIKit

class BezierPathView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        self.drawLine()
        self.drawCommonCurve()
        self.drawSmoothPath()
    }
    
    func drawLine() {
        
        let offset:CGFloat = 50.0
        
        // 繪制矩形
        //let rectpath = UIBezierPath(rect: CGRect.init(x: 15, y: offset, width: 300, height: 60))
        let rectpath = UIBezierPath(roundedRect: CGRect.init(x: 15, y: offset, width: 300, height: 60), cornerRadius: 5.0)

        rectpath.lineWidth = 5.0
        UIColor.green.setStroke()
        rectpath.stroke()
        UIColor.red.setFill()
        rectpath.fill()
        
        // 繪制直線
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 25.0, y: offset + 130.0))
        path.addLine(to: CGPoint(x: 300.0, y: offset + 130.0))
        path.lineWidth = 5.0
        UIColor.cyan.setStroke()
        path.stroke()
    }
    
    func drawCommonCurve() {
        
        let offset:CGFloat = 260.0
        
        let curvePath = UIBezierPath()
        curvePath.move(to: CGPoint(x: 30.0, y: offset))
        curvePath.addQuadCurve(to: CGPoint(x: 350.0, y: offset), controlPoint: CGPoint(x: 350.0, y: offset + 100))
        UIColor.blue.setStroke()
        curvePath.stroke()
    }
    
    func drawSmoothPath() {
        
        let offset:CGFloat = 430
        
        let pointCount:Int = 4
        let pointArr:NSMutableArray = NSMutableArray.init()
        for i in 0...pointCount {
            let px: CGFloat = 15 + CGFloat(i) * CGFloat(80)
            let py: CGFloat = i % 2 == 0 ? offset - 60 : offset + 60
            let point: CGPoint = CGPoint.init(x: px, y: py)
            pointArr.add(point)
        }
        
        let bezierPath = UIBezierPath()
        bezierPath.lineWidth = 2.0
        var prevPoint: CGPoint!
        
        for i in 0 ..< pointArr.count {
            let currPoint:CGPoint = pointArr.object(at: i) as! CGPoint
            
            // 繪制綠色圓圈
            let arcPath = UIBezierPath()
            arcPath.addArc(withCenter: currPoint, radius: 3, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: true)
            UIColor.green.setStroke()
            arcPath.stroke()
            
            // 繪制平滑曲線
            if i==0 {
                bezierPath.move(to: currPoint)
            }
            else {
                let conPoint1: CGPoint = CGPoint.init(x: CGFloat(prevPoint.x + currPoint.x) / 2.0, y: prevPoint.y)
                let conPoint2: CGPoint = CGPoint.init(x: CGFloat(prevPoint.x + currPoint.x) / 2.0, y: currPoint.y)
                bezierPath.addCurve(to: currPoint, controlPoint1: conPoint1, controlPoint2: conPoint2)
            }
            prevPoint = currPoint
        }
        UIColor.red.setStroke()
        bezierPath.stroke()
    }
}

運行結果:


源碼Github地址

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趣兄,一起剝皮案震驚了整個濱河市绽左,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艇潭,老刑警劉巖拼窥,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蹋凝,居然都是意外死亡鲁纠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門鳍寂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來改含,“玉大人,你說我怎么就攤上這事迄汛『慈溃” “怎么了骤视?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鹃觉。 經(jīng)常有香客問我专酗,道長,這世上最難降的妖魔是什么盗扇? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任祷肯,我火速辦了婚禮,結果婚禮上疗隶,老公的妹妹穿的比我還像新娘佑笋。我一直安慰自己,他們只是感情好抽减,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布允青。 她就那樣靜靜地躺著橄碾,像睡著了一般卵沉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上法牲,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天史汗,我揣著相機與錄音,去河邊找鬼拒垃。 笑死停撞,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的悼瓮。 我是一名探鬼主播戈毒,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼横堡!你這毒婦竟也來了埋市?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤命贴,失蹤者是張志新(化名)和其女友劉穎道宅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胸蛛,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡污茵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了葬项。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泞当。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖民珍,靈堂內(nèi)的尸體忽然破棺而出零蓉,到底是詐尸還是另有隱情笤受,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布敌蜂,位于F島的核電站箩兽,受9級特大地震影響,放射性物質發(fā)生泄漏章喉。R本人自食惡果不足惜汗贫,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秸脱。 院中可真熱鬧落包,春花似錦、人聲如沸摊唇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巷查。三九已至有序,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岛请,已是汗流浹背旭寿。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留崇败,地道東北人盅称。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像后室,于是被迫代替她去往敵國和親缩膝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容