Core Graphics 學(xué)習(xí) (一) 坐標系,Path,簡單路徑繪制

Core Graphics 也叫 Quartz 2D, 是 iOS,TVOS,macOS應(yīng)用開發(fā)中先進的二維繪制引擎.

在iOS中, QuartZ 2D 可與所有可用的圖形和動畫技術(shù) (如 Core Animation, OpenGL和UIKit類)配合使用.

Quartz 2D使用 painters model進行成像. 一個對象繪制之后只用通過不對新的繪制操作來進行修改.不同元素繪制順序不同,展示的效果可能不同.

使用Core Graphics 可以做到以下事情:
圖層透明度,陰影
根據(jù)路徑(path)繪圖
離屏渲染
色彩管理
抗鋸齒渲染
PDF文件管理(創(chuàng)建,展示,解析)

QuartZ 2D坐標系

QuartZ 2D默認坐標系如下. 左下角為原點(0,0),橫向像右為x正向,列向向上為y正向.


image.png

因為不同的設(shè)施具有不同的底層呈像能力,圖形的位置和大小必須以獨立于設(shè)施的方式來進行定義.例如,一些屏幕設(shè)施一英寸可能顯示不超過96像素,但是打印設(shè)置打印一英寸顯示300個像素.如果在某一設(shè)施上定義坐標系,那么在其他設(shè)施上就不能正常顯示需要繪制的圖像,會出現(xiàn)失真.

QuartZ 2D 獨立的坐標系統(tǒng) - 用戶空間 (user space), 經(jīng)過當(dāng)前矩陣變換(current transformation matrix,或 CTM),將圖像映射到設(shè)施坐標系統(tǒng) - 設(shè)施空間(divece space). CTM是進行仿射變換的特定類型的矩陣,可以通過平移,旋轉(zhuǎn),縮放等操作,將一個點從一個坐標系映射到另一個坐標系.

在一些繪圖技術(shù)中使用額坐標系和QuartZ 2D的默認坐標系不同. 它的源點在左上角,并且y軸向下是正方向.例如下面幾種情況使用的就是這種坐標系:
在Mac OS X中,子類化一個NSView, 重寫isFlipped方法,返回YES,獲得的坐標系.
在iOS中, UIView返回的繪圖上下文使用的坐標系.
在iOS中, 通過UIGraphicsBeginImageContextWithOptions方法創(chuàng)建繪圖的上下文.

UIKit返回的繪圖上下文使用修改過的坐標系,是因為UIKit本身使用的坐標系就是不同于默認坐標系的; UIKit會自動將它創(chuàng)建的繪圖上下文的坐標系轉(zhuǎn)換為于它本身的坐標系相匹配.如果一個應(yīng)用程序想要使用相同的規(guī)則創(chuàng)建UIView 和 PDF上下文(由Quartz 和它的默認坐標系繪制) , 則需要對PDF的坐標系進行轉(zhuǎn)換,也就是將原點轉(zhuǎn)化到左上角,將y值變成-y. 下圖展示坐標轉(zhuǎn)換之后的效果,默認坐標系上是順時針,在轉(zhuǎn)化之后的坐標系上是逆時針:


image.png

圖形上下文 (Graphics Context)

圖像上下文 (the Graphics Context), 用于封裝QuartZ 用于將圖像繪制到輸出設(shè)施(如PDF文件,位圖或窗口) 上的信息.圖像上下文中的信息包括圖像繪圖參數(shù)和特定設(shè)施中的頁面繪制內(nèi)容. QuartZ中的所有對象都被繪制到或包含在圖形上下文中.

在IOS中繪制視圖上下文
要在IOS 應(yīng)用程序中繪制屏幕, 可以設(shè)置UIView對象并實現(xiàn)其drawRect: 方法來執(zhí)行繪圖. 在調(diào)用自定義drawRect: 方法之前,視圖對象會自動配置其繪圖環(huán)境,以便代碼可以立即開始繪制.
let contextRef = UIGraphicsGetCurrentContext()

創(chuàng)建PDF圖像上下文

當(dāng)你創(chuàng)建一個PDF繪圖上下文,并在上面進行繪制, 那么QuartZ將會將你的繪制當(dāng)作一系列的PDF繪制命令,寫入到一個文件.通過給定一個PDF輸出位置和一個默認的媒體框(meida box) - 也就是一個特定范圍的頁面.

QuartZ 2D API 提供了兩個創(chuàng)建PDF圖形上下文的方法:
方法一:
func creatPDFContext(with filePath: CFString, mediaBox: inout CGRect) -> CGContext? {
// 根據(jù)文件的路徑創(chuàng)建 url let url = CFURLCreateWithFileSystemPath(nil, filePath, .cfurlposixPathStyle, false) guard (url != nil) else { return nil } // 根據(jù) url 和 medioBox 創(chuàng)建 pefCOntext let pdfContect = CGContext.init(url!, mediaBox: &mediaBox, nil) return pdfContect}

方法二:

func creatPDFContext2(with filePath: CFString, mediaBox: inout CGRect) -> CGContext? { let url = CFURLCreateWithFileSystemPath(nil, filePath, .cfurlposixPathStyle, false) guard (url != nil) else { return nil } let dataConsumer = CGDataConsumer.init(url: url!) guard dataConsumer != nil else { return nil } let pdfContext = CGContext.init(consumer: dataConsumer!, mediaBox: &mediaBox, nil) return pdfContext }

IOS 中 PDF圖形上下文使用的是 QuartZ提供的默認坐標系

創(chuàng)建位圖圖形上下文(Bitmap Graphics Context)
位圖上下文接受一個指向內(nèi)存緩沖區(qū)的指針,它包含了位圖的存儲空間.當(dāng)你在位圖上下文中進行繪制時,緩存區(qū)會進行更新. 在釋放了位圖上下文之后,將會完全按照你指定的像素更新位圖.

在iOS應(yīng)用程序中應(yīng)該使用UIGraphicsBeginImageContextWithOptions方式創(chuàng)建位圖上下文.因為使用這種方式創(chuàng)建的位圖上下文,UIKit會將相同的放射變換應(yīng)用于上下文,就想UIView創(chuàng)建的上下文.

抗鋸齒效果(Aniti-Aliasing)
位圖上下文支持抗鋸齒功能.通過設(shè)置 setAllowsAntialiasing為 true,允許抗鋸齒.

Paths

path 定義了一個或多個形狀; 或定義多條自路徑. 一條子路徑可能包含直線和曲線.
創(chuàng)建,繪制路徑:
路徑的創(chuàng)建,繪制是獨立的任務(wù).首先,創(chuàng)建一條路徑; 當(dāng)需要渲染一條路徑的時候,再去繪制它.可以選擇對路徑進行描邊(stroke), 填充(fill), 剪裁(clip).

繪制構(gòu)建模塊

子路徑由直線(Lines), 弧線(Arcs),曲線(Curves) 構(gòu)成.

Point
QuartZ的機制是持續(xù)追蹤當(dāng)前點. 當(dāng)使用 move(to point: CGPoint)方法設(shè)置一個點(10,10),這個值就是當(dāng)前點.
// 指定一個子路徑的開始點 public fuc move(to point: CGPoint)

Line
一條直線的位置取決于終點的位置.
知道當(dāng)前點,并給定一個終點,創(chuàng)建一條直線路徑.
// 指定一個子路徑的開始點 : public func move(to point: CGPoint)
// 創(chuàng)建一條直線路徑 public func addLine(to Point: CGPoint)

還可以之賜你個給定一連串點包括當(dāng)前點和每條直線的終點,創(chuàng)建相連的多條直線:
public func addLines(between points:[CGPoint])

Arcs

Arcs是圓形的分段. 提供了兩個方法進行創(chuàng)建.

根據(jù)圓心,半徑,角度繪制(clockwise 為true 為順時針):
public func AddArc(center: CGPoint,radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)

當(dāng)前點第一個end點的連線,第一個end點與第二個end點的連線構(gòu)成相交的兩個線段,繪制與這兩條直線相切并滿足半徑條件的圓弧.
public fuc addArc(tangent1End: CGPoint, tangent2End: CGPoint, radius: CGFloat)

Curves
曲線主要是貝塞爾曲線: 三階貝塞爾曲線,二階貝塞爾曲線.

三階貝塞爾曲線
public func addCurve(to end: CCGPoint,control1: CGPoint, control2: CGPoint)
二階貝塞爾曲線
public fun addQuadCurve(to end; CGPoint, control: CGpoint)

closePath
關(guān)閉并終止當(dāng)前路徑的子路徑.從當(dāng)前點追加一行到當(dāng)前子路徑的起點,并結(jié)束子路徑.關(guān)閉子路徑后,您的應(yīng)用程序可以開始一個新的子路徑,而無需首先調(diào)用moveTo(x: y:). 在這種情況下,新的子路徑被隱式創(chuàng)建,起始點和當(dāng)前點等于先前子路徑的起始點.如果當(dāng)前路徑為空或當(dāng)前子路徑已經(jīng)關(guān)閉,則此功能不起作用.
public func closePath()

*** 橢圓(Elipses)***
橢圓是壓扁的圓. QuartZ可以在給定的矩形區(qū)域內(nèi)繪制相應(yīng)的橢圓,如果長寬相等,繪制的是圓.
public func addElipse(in rect: CGRect)

矩形(Rectangles)

繪制一個矩形
public func addRect(_rect: CGRect)
繪制多條矩形
public func addRects(_rects:[CGRect])

創(chuàng)一條路徑的步驟

  1. 開始路徑
    contextRef!.beginPath()
  2. 為第一個形狀,子路徑設(shè)置開始點
    contextRef!.move(to: CGPont(x:10, y:10))
  3. 之后根據(jù)需求繪制相應(yīng)的路徑(直線,曲線,矩形).....

注意以下幾點:
beginPath()
起始點:move(to: CGPoint(x: 10, y: 10))
關(guān)閉路徑:closePath()
當(dāng)您繪制弧線時,Quartz會在當(dāng)前點和弧線起點之間畫一條線。
創(chuàng)建矩形、橢圓的時候,相當(dāng)于添加了一個新的封閉子路徑
必須調(diào)用繪圖函數(shù)來填充或描繪路徑,因為創(chuàng)建路徑不會繪制路徑。

繪制路徑之后,路徑從圖像上下文中刷新.你可能想保存這個路徑,特別是如果它描繪了一個復(fù)雜的場景,你不想要一遍又一遍的創(chuàng)建路徑.因此,QuartZ提供了兩種創(chuàng)建可重用路徑的數(shù)據(jù)類型-CGPath和CGMutablePath. 可以調(diào)用函數(shù)CGPathCreteMutable創(chuàng)建一個可變CGPath對象, 您可以向其中添加線,弧,曲線和矩形. QuartZ提供了一組CGPath函數(shù):

let mutablePath = CGMutablPath.init()
相當(dāng)contextRef!.beginPath()
mutablePath.move(to: CGPoint(x:10 , y:10))
相當(dāng)于 contextRef!.move(to: CGPoint(x: 10, y:10))
mutablePath.addLine(to:CGPoint(x:60, y:10))
添加直線
mutablePath.addRect(CGRect(x: 0, y: 0, width: 50, height: 60))
添加矩形
mutablePath.addEllipse(in: CGRect(x: 0, y: 0, width: 50, height: 30))
添加橢圓
mutablePath.addArc(tangent1End: CGPoint(x: 60, y: 10), tangent2End: CGPoint(x: 10, y: 60), radius: 50)
添加圓弧
mutablePath.addCurve(to: CGPoint(x: 60,y: 10), control1: CGPoint(x: 20, y: 0), control2: CGPoint(x: 40, y: 20))
添加貝塞爾曲線
mutablePath.closeSubpath()
封閉路徑
contextRef!.addPath(mutablePath)
添加路徑到圖形上下文

繪制路徑

當(dāng)創(chuàng)建好路徑之后,接下來就是要將路徑繪制到上下文. 繪制路徑有兩種方式: Stroking(描邊). filling(填充)

Stroking(描邊)

關(guān)于stroking的常用屬性參數(shù):

contextRef!.setLineWidth(1.0) // 線寬
contextRef!.setLineJoin(.round) // 設(shè)置連接點樣式
contextRef!.setLineCap(.round) // 設(shè)置端點樣式
contextRef!.setMiterLimit(10.0) //如果連線設(shè)置為斜角(miter), 用來判斷連接點是miter還是bevel. 斜角的長度除以線寬.如果結(jié)果大于miter限制,則將樣式轉(zhuǎn)換為 bevel
contextRef!.setLineDash(phases: 0 ,lengths:[2,3,5]) // phase: 表示繪制虛線的位置, lengths: 表示實線,虛線之間的間隔
contextRef!.setStrokeColor(UIColor.red.cgColor) // 設(shè)置描邊顏色

Stroking 常用函數(shù):

contextRef!.strokePath() // 為當(dāng)前路徑描邊
contextRef!.stroke(rect:rect) // 描邊特定的矩形
contextRef!.stroke(rect: rect, width: 1.0) // 根據(jù)特定的線寬,描邊一個矩形
contextRef!.strokeEllipse(in: rect) // 描邊一個橢圓
contextRef!.strokeLineSegments(between: [point1, point2 point3, point4]) // 描繪一系列相連線段。
contextRef!.drawPath(using: .stroke) // 根據(jù)提供的繪制模式,繪制

連接點樣式:

image.png

線端點樣式:

image.png

線破折號樣式:

image.png

填充路徑(Filling a Path)
當(dāng)一個路徑是封閉路徑,可以進行填充.填充之前需要先計算填充區(qū)域. QuartZ可以通過兩種方式計算天長區(qū)域.諸如圓形和矩形的簡單路徑具有明確的區(qū)域. 但是,如果您的路徑由重疊段組成,或者如果路徑包含多個子路徑,例如同心圓,則可以使用這兩個規(guī)則來確定填充區(qū)域.

默認的填充規(guī)則是 非零繞線數(shù)規(guī)則. 決定一個點是否需要繪制,從該點開始繪制一條超出圖形邊界的射線. 從零開始計數(shù), 如果路徑線段從左向右穿過該射線,計數(shù)加一; 相反減一; 如果結(jié)果是零就不繪制該點.路徑的繪制方向會影響填充效果.

還可以選擇 奇偶規(guī)則。 還是先從一點繪制一條射線穿過路徑邊界,從零記錄穿過的路徑邊界的次數(shù),奇數(shù)就繪制該點,偶數(shù)就不繪制。

image.png

填充路徑的方法:

contextRef!.fillPath(using: .evenOdd) // 使用 even-odd 規(guī)則填充
contextRef!.fillPath(using: .winding) // 使用 nonzero winding number 規(guī)則填充
contextRef!.fill(CGRect(x: 0, y: 0, width: 50, height: 50)) // 填充一個矩形
contextRef!.fill([CGRect(x: 0, y: 0, width: 50, height: 50)]) // 填充多個矩形
contextRef!.fillEllipse(in: CGRect(x: 0, y: 0, width: 50, height: 50)) // 填充一個橢圓
contextRef!.drawPath(using: .fill) //根據(jù)提供的繪制模式,繪制

設(shè)置混合模式(Blend Modes)

混合模式指定QuartZ怎樣在背景上使用涂料. QuartZ默認使用普通混合模式,它使用以下公式將前景繪圖與背景繪畫相結(jié)合:
result = (alpha foreground) + (1 - alpha) background

對于下面的示例,您可以假定顏色完全不透明(alpha值= 1.0)。對于不透明的顏色,當(dāng)使用普通混合模式進行繪制時,任何在背景之上繪制的內(nèi)容完全遮擋背景上的繪圖。

您可以通過調(diào)用函數(shù)CGContextSetBlendMode來設(shè)置混合模式以實現(xiàn)各種效果,傳遞適當(dāng)?shù)幕旌夏J匠A?。請記住,混合模式是圖形狀態(tài)的一部分。如果在更改混合模式之前使用函數(shù)CGContextSaveGState,則調(diào)用函數(shù)CGContextRestoreGState會將混合模式重置為 normal。

contextRef!.setBlendMode(.color)

剪切路徑(Clipping
當(dāng)前的裁剪區(qū)域通過一個路徑創(chuàng)建,用來充當(dāng) mask 的作用,允許阻止顯示不想繪制的頁面部分。例如,如果有一個非常大的位圖圖像,并且僅需要顯示其一小部分,則可以將剪切區(qū)域設(shè)置為僅要顯示的部分。

裁剪方法:

contextRef!.clip() // 使用 nonzero winding number rule 來計算裁剪路徑
contextRef!.clip(using: .evenOdd) // 使用 even-odd rule
contextRef!.clip(to: CGRect(x: 0, y: 0, width: 50, height: 50))contextRef!.clip(to: CGRect(x: 0, y: 0, width: 50, height: 50), mask: nil)
// 創(chuàng)建一個圓形裁剪區(qū)域

根據(jù)上述知識繪制一些基本圖形

//: MARK: 繪制直線 func drawLine(_ context: CGContext, rect: CGRect) { // 描邊的屬性 context.setStrokeColor(red: 0, green: 0, blue: 1, alpha: 1) context.setLineWidth(2.0) context.setLineCap(.round) // 繪制一條直線 context.move(to: CGPoint(x: 10, y: 10)) context.addLine(to: CGPoint(x: rect.size.width - 20, y: 10)) context.strokePath() } //: MARK: 繪制多條直線,組成一個三角形 func drawLines(_ context: CGContext, rect: CGRect) { // 設(shè)置屬性 context.setStrokeColor(red: 1, green: 0, blue: 0, alpha: 1) context.setLineWidth(3.0) context.setLineJoin(.round) context.setLineCap(.round) // 繪制 let startPoint = CGPoint(x: 60, y: 20) let point2 = CGPoint(x: 110, y: 120) let point3 = CGPoint(x: 10, y: 120) context.addLines(between: [startPoint, point2, point3]) context.closePath() context.strokePath() } //: MARK: 繪制一條圓弧 func drawArcs(_ context: CGContext, rect: CGRect) { // 設(shè)置屬性 context.setStrokeColor(red: 1, green: 1, blue: 0, alpha: 1) context.setLineWidth(3.0) context.setLineCap(.round) // 繪制 // 方式一:當(dāng)前點于第一個end點的連線,第一個end點與第二個end點的連線構(gòu)成相交的兩個線段,繪制與這兩條直線相切并且滿足半徑條件的圓弧。 context.move(to: CGPoint(x: 120, y: 20)) context.addArc(tangent1End: CGPoint(x: 220, y: 20), tangent2End: CGPoint(x: 120, y: 120), radius: 20) context.strokePath() // 方式二: // clockwise 為 true 是順時針, false 是 逆時針 context.addArc(center: CGPoint(x: 300, y: 70), radius: 50, startAngle: CGFloat(-Double.pi / 2), endAngle: CGFloat(Double.pi / 3), clockwise: false) context.strokePath() } //: MARK: 繪制貝塞爾曲線 func drawCurves(_ context: CGContext, rect: CGRect) { // 設(shè)置屬性 context.setStrokeColor(red: 1, green: 0.5, blue: 0.3, alpha: 1) context.setLineWidth(3.0) context.setLineCap(.round) // 繪制 // 二次 context.move(to: CGPoint(x: 10, y: 140)) context.addQuadCurve(to: CGPoint(x: rect.size.width - 10, y: 140), control: CGPoint(x: 50, y: 200)) context.strokePath() // 三次 context.move(to: CGPoint(x: 10, y: 250)) context.addCurve(to: CGPoint(x: rect.size.width - 10, y: 250), control1: CGPoint(x: 200, y: 210), control2: CGPoint(x: rect.size.width - 200, y: 290)) context.strokePath() } //: MARK: 繪制橢圓 func drawElipses(_ context: CGContext, rect: CGRect) { // 設(shè)置屬性 context.setFillColor(red: 0.3, green: 0.6, blue: 0.9, alpha: 1) context.setStrokeColor(red: 0.7, green: 0.5, blue: 0.3, alpha: 1) context.setLineWidth(3.0) context.setLineCap(.round) // 繪制 context.addEllipse(in: CGRect(x: 10, y: 270, width: rect.size.width - 20, height: 100)) context.drawPath(using: .fillStroke) } //: MARK: 繪制矩形 func drawRect(_ context: CGContext, rect: CGRect) { // 設(shè)置屬性 context.setFillColor(red: 0.3, green: 0.6, blue: 0.9, alpha: 1) context.setStrokeColor(red: 0.7, green: 0.5, blue: 0.3, alpha: 1) context.setLineWidth(3.0) context.setLineCap(.round) // 繪制 context.addRect(CGRect(x: 10, y: 380, width: rect.size.width - 20, height: 100)) context.drawPath(using: .fillStroke) } //: MARK: 繪制一個同心圓 func drawConcentricCicles(_ context: CGContext, rect: CGRect) { // 設(shè)置屬性 context.setFillColor(red: 0.3, green: 0.6, blue: 0.9, alpha: 1) context.setStrokeColor(red: 0.7, green: 0.5, blue: 0.3, alpha: 1) context.setLineWidth(3.0) context.setLineCap(.round) // 繪制 context.addArc(center: CGPoint(x: rect.size.width / 2, y: rect.size.height - 80), radius: 80, startAngle: CGFloat(0), endAngle: CGFloat(Double.pi2), clockwise: false) context.addArc(center: CGPoint(x: rect.size.width / 2, y: rect.size.height - 80), radius: 40, startAngle: CGFloat(Double.pi2), endAngle: CGFloat(0), clockwise: true) context.fillPath(using: .evenOdd) }

繪制結(jié)果:

image.png

Demo:
https://github.com/luckySlider/CoreGraphics.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容