原文連接:Core Graphics, Part 4: A Path! A Path!
譯文原鏈:【譯】Core Graphics,第四部分:Path!Path!
看看上一篇吧:【譯】Core Graphics,第三部分:線ios
在 Core Graphics 中,一個 path
就是對某種形狀的一步一步的描述。它能夠是一個圓、一個正方形、一個桃心、一個字頻柱狀圖或者多是一個笑臉。它並不包含任何諸如像素顏色、線寬或漸變這樣的信息。路徑主要是用於繪製——將其用顏色填充或者描邊——用顏色描出輪廓。你以前看到的各類 GState
參數控制着 path 如何被繪製,包括例如 line join 和 dash pattern 在內的全部線屬性。git
這一次讓你看看 path 是什麼組成的。下一次你會看到一些用 path 能作的遠非簡單繪製的很酷的東西。github
雖然一個 path 表明了一個理想圖形的配方,它須要被渲染出來才能被人真正看到。每個 Core Graphics context 都盡其可能將 path 渲染出來。當繪製一個位圖時,任何曲線和斜線都是反鋸齒化的。這意味着使用陰影來欺騙眼睛使其一位這個形狀是平滑的即便它是由方形像素點組成的。當在打印機上繪製時,一樣的事情發生着,不過用的是極其小的像素點。當繪製 PDF 時,path 大部分僅僅是原位防止,由於 Core Graphics 繪製模型和 PDF 繪製模型基本是同樣的。PDF 引擎(例如 Preview 或者 Adobe Acrobat)會去渲染那些 PDF path 而非 Core Graphics 引擎。vim
你能夠試試 GrafDemo 裏面的 path。大多數這裏的截圖都來自 GrafDemo 裏的 Path 部分、Arcs 以及 All The Parts 窗口。數組
一個 path 就是由一些被稱之爲元素的原始形狀(曲線、弧和直線)鏈接起來的一系列點。你能夠想象每個元素是給一個專門的拿着鉛筆的機器人的一個指令。你告訴這個機器人要提起鉛筆並移動到笛卡爾平面的一個點,可是不要留下任何 印記。你能夠告訴這個機器人去把鉛筆落下來而後從當前的點到一個新點間畫點什麼。有五種基本的路徑元素:安全
Move to Point——移動當前的點到一個新的位置但不畫任何東西。機器人擡起鉛筆而且移動它的手臂。閉包
Add Line To Point——從當前點到一個新點間添加一條線。機器人將鉛筆落下來並畫出一條直線。如下是一次移動到點(左下方)和其後的兩次添加線到點:app
path.move(to: startPoint) path.addLine(to: nextPoint) path.addLine(to: endPoint)
添加二次曲線到點 Add Quad Curve To Point——經過一個控制點,從當前點到一個新點間添加一條二次曲線。機器人落下了鉛筆並在繪製一條曲線。這條線並非直接劃到那個控制點——相反這個控制點影響着(線的)形狀。控制點離曲線越遠,形狀就越極端。工具
path.move(to: firstPoint) path.addQuadCurve(to: endPoint, control: controlPoint)
Add Curve To Point——經過兩個控制點,從當前點到新點添加一條三次貝塞爾曲線。和二次曲線同樣,控制點影響着這條線該如何畫。二次曲線沒法自身造成一個環,可是貝塞爾曲線能夠。若是你曾在 Photoshop 或者 Illustrator 中使用過鋼筆工具,你就和貝塞爾曲線打過交道了。spa
path.move(to: firstPoint) path.addCurve(to: endPoint, control1: firstControl, control2: secondControl)
Close Subpath——從當前點到路徑的第一個點間添加一條直線。更確切地說,最近的那個 move-to-point (的點)。你會但願閉合一個路徑而非添加一條線到起始位置。根據你如何計算這些點,累積的浮點化整可能使得計算出的終點和起始點不同。如下(代碼)能夠繪製一個三角形:
path.move(to: startPoint) path.addLine(to: nextPoint) path.addLine(to: endPoint) path.closeSubpath()
注意這個名字是 Close Subpath。經過執行一次 move-to 操做,你能夠建立一個包含分離部分的路徑,例如這個在咱們 Advanced iOS bootcamp 中新練習題裏的一個柱狀圖。這些柱形都是用一個路徑繪製的。這個路徑被用來給它們上色,而且描出輪廓以清楚地區別各個柱形。
簡單的圖形用那僅有的五個基本路徑元素去生成的話(代碼、過程)可能會變得很冗長。Core Graphics (或者說 CG)提供了一些簡便方法來添加常見的形狀,好比矩形、橢圓或者一個圓角矩形。
let squarePath = CGPath(rect: rect1, transform: nil) let ovalpath = CGPath(ellipseIn: rect2, transform: nil) let roundedRectanglePath = CGPath(roundedRect: rect3, cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)
這些方法使用了一個 transform 對象做爲它們最後一個參數。你會在以後的文章中看到更多關於 transform 的東西,所以暫時只傳個 nil 就好了。以上的方法(CGPath(rect:tranform:)
、CGPath(ellipseIn:transform:)
和CGPath(roundedRect:cornerWidth:cornerHeight:transform:)
)生成了這些形狀:
一樣也還有一些可讓你用一個就能建立更復雜的路徑的方法,例如多個矩形或者多個橢圓、多個線段或者一整個別的路徑。
你也能夠加一點弧在裏面,就是一個圓形邊的部分。用哪個取決於你手上握着什麼值。
Arc——須要給定你想要的弧所在的那個圓的圓心、它的半徑以及起始和終止角度(用弧度表示)。那個圓從起始角度到終止角度之間的那一段將會被繪製。弧的終點成爲了當前點。如下代碼繪製了左邊的線,外加一個圓:
path.move(to: startPoint) path.addLine(to: firstSegmentPoint) path.addArc(center: centerPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
Relative Arc——這個與正常的弧相似。須要給定圓心、半徑和起始角度。但並非要給定一個終止角度,而是你要說明要從起始角度往前或者日後畫多少個弧度:
path.move(to: startPoint) path.addLine(to: firstSegmentPoint) path.addRelativeArc(center: centerPoint, radius: radius, startAngle: startAngle, delta: deltaAngle)
Arc to Point——這個就有點奇怪了。你須要給定圓半徑和兩個控制點。在後臺呢,當前的店會和第一個控制點鏈接,而後與第二個控制點造成一個角度。這些線接下來被用於構建一個有着給定半徑並正切於這些線的圓。我將這種弧稱做 「Arc to Point」 是由於其底層的 C API 名字叫 CGContextAddArcToPoint
。
path.move(to: startPoint) path.addLine(to: firstSegmentPoint) path.addArc(tangent1End: tangent1Point, tangent2End: tangent2Point, radius: radius)
我在試着想出一個這個方法的好的應用場景時,朋友 Jeremy W. Sherman 想到一個很酷的應用:若是你想作一個曲面的交叉影線時可能比較有用,想一想「給一把劍的頂部加一點陰影」——你能夠重複一樣的正切而且改變半徑來畫一些離頂部愈來愈遠的弧。
你可能已經注意到了這些弧的方法能夠用直線段來鏈接圓弧。用前兩個弧方法來建立一個新的路徑是不會建立這個鏈接用的線段的。Arc to point 可能會包含那個初始的部分。
有兩種在代碼裏建立路徑的方法。第一種方式是告訴 context:「嘿,建立一個新的 path」 並開始累積 path 元素。這個 path 在你描邊或者填充時就消失了。沒了。拜拜了。這個 path 也並無被保存或者能夠在你保存恢復 GState 的時候恢復——它實際上並非 GState 的一部分。每個 context 只有一個在用狀態的 path。
一下是當前 context 被用於構建和描邊一個 path 時的例子:
let context = UIGraphicsGetCurrentContext() context.beginPath() context.move(to: controlPoints[0]) context.addQuadCurve(to: controlPoints[1], control: controlPoints[2]) context.strokePath()
這些對於那些一次性的只建立一次、用一次而後被遺忘的 path 很是棒。
你也能夠建立一個新的 CGMutablePath
path 對象(一個 CGPath
類型的 mutable 子類,與 NSArray / NSMutableArray
間的關係相似)並在其中累積 path 組件。這是一個你能夠一直用的實例。要用一個 path 對象繪製的話,你要將這個 path 添加到 context 中而後執行描邊與/或填充操做:
let path = CGMutablePath() path.move(to: controlPoints[0]) path.addQuadCurve(to: controlPoints[1], control: controlPoints[2]) context.addPath(path) context.strokePath()
對於你經常使用的形狀(好比卡片遊戲裏的那一套圖標),你可能但願建立一個紅心 path 和一個方片 path 一次而後用它們一次次繪製。
那麼你如何才能建立出有用又有趣的 path 呢,好比心形或者笑臉?一個方法是作好數學功課並計算出點、線、曲線和弧須要怎麼走。
另外一個方法就是用軟件工具。有一些可讓你畫形狀的的應用,而後返回給你一堆能夠直接粘貼到你的應用中的 CG 代碼。同時也有一些能夠將其餘形式數據(例如來自 Illustrator的、PDF 或者 SVG)轉換成 path 的庫。我在給 Protocols part 2: Delegation 準備的 world demo app 中的可點擊地圖中使用了 SVG。
Core Graphics 路徑是不透明數據類型。你先累積路徑元素而後在 context 中渲染它。爲了瞭解內部狀況,使用 CGPath
的 apply(info:function:)
方法來遍歷路徑組件。你能夠提供一個被每個路徑元素重複調用的方法(在 Swift 中你能夠用閉包)。(你能夠忽略 info 參數經過傳 nil
。這是在 Swift Core Graphics API 底下的 C API 的一個延續。在 C 裏面你須要提供一個方法並傳遞任何你須要在裏面使用到的對象。用閉包的話你就只須要用你須要的那些。)
也由於其對 C 的繼承,這個傳進來的方法或閉包是一個 UnsafePointer<CGPathElement>
。這是一個指向內存中 CGPathElement
的指針。你須要經過 pointee
來引用那個指針以獲得實際的 CGPathElement
。這個 path 元素有一個用於表現其類型的枚舉值,還有一個指向一個指針數組裏的第一個 CGPoint
的 UnsafeMutablePointer<CGPoint>
。你須要本身去搞清楚你能夠從那個數組裏面安全讀取多少指針。
下面是一個 CGPath
擴展,它可讓一個 path 傾倒出其內容。你也能夠從這個 gist 中找到這段代碼。
import CoreGraphics extension CGPath { func dump() { self.apply(info: nil) { info, unsafeElement in let element = unsafeElement.pointee switch element.type { case .moveToPoint: let point = element.points[0] print("moveto - \(point)") case .addLineToPoint: let point = element.points[0] print("lineto - \(point)") case .addQuadCurveToPoint: let control = element.points[0] let point = element.points[1] print("quadCurveTo - \(point) - \(control)") case .addCurveToPoint: let control1 = element.points[0] let control2 = element.points[1] let point = element.points[2] print("curveTo - \(point) - \(control1) - \(control2)") case .closeSubpath: print("close") } } } }
打印出以前建立那個 arc to point 圖片的 path 展現出這條弧是一系列 curveTo 操做及鏈接用的直線:
path.move(to: startPoint) path.addLine(to: firstSegmentPoint) path.addArc(tangent1End: tangent1Point, tangent2End: tangent2Point, radius: radius) path.addLine(to: secondSegmentPoint) path.addLine(to: endPoint)
moveto - (5.0, 91.0) // explicit code lineto - (72.3, 91.0) // explicit code lineto - (71.6904767391754, 104.885702433811) // added by addArc curveTo - (95.5075588575432, 131.015122621923) - (71.0519422129889, 118.678048199439) - (81.7152130919145, 130.376588095736) curveTo - (113.012569145714, 124.955236840146) - (101.903264013406, 131.311220082842) - (108.168814214539, 129.14221144167) lineto - (129.666666666667, 91.0) // explicit code lineto - (197.0, 91.0) // explicit code
即便是一個用 CGPath(ellipseIn:transform:)
建立的「簡單的」橢圓也有些複雜:
curveTo - (62.5, 107.0) - (110.0, 86.4050984922165) - (88.7335256169627, 107.0) curveTo - (15.0, 61.0) - (36.2664743830373, 107.0) - (15.0, 86.4050984922165) curveTo - (62.5, 15.0) - (15.0, 35.5949015077835) - (36.2664743830373, 15.0) curveTo - (110.0, 61.0) - (88.7335256169627, 15.0) - (110.0, 35.5949015077835)
這一次你看到了建立一個 path、繪製它以及其中發生的一切。還有不少你能夠用 path 作的事情,下次繼續。