1. 程式人生 > 其它 >Quartz 2D CGGradient與CGShading實現漸變的繪製

Quartz 2D CGGradient與CGShading實現漸變的繪製

Quartz 提供了兩種不透明的資料型別來建立漸變CGShadingCGGradient,你可以使用其中任何一個來建立軸向或徑向漸變。

軸向漸變:沿著一個軸方向線性漸變

徑向漸變:一個點為原型,指定半徑的範圍內輻射漸變

我們用兩個圖來理解一下這兩個概念:

1、軸向漸變(軸:沿著左上角 到 右下角)

2、徑向漸變(以中心點為圓心,向半徑為50的四周方向輻射漸變)

CGShadingCGGradient之間的關係,以及使用的不同。

CGGradientCGShading的子集,CGGradient封裝了對CGShading的漸變實現函式,因此CGGradient使用起來會比CGShading

簡單。

下面介紹一下兩者的不同:

CGGradient CGShading
使用同一個CGGradient例項可以同時繪製軸向漸變和徑向漸變 在繪製軸向漸變和徑向漸變時,需要分別例項化不同的物件
在繪製時建立漸變的幾何形狀 在建立物件時建立漸變的幾何形狀
Quartz自動完成計算漸變中每個點的顏色變化 你需要提供一個回撥函式,來計算漸變中每個點的顏色
使用簡單,定義兩個點和對應的顏色即可 需要在回撥函式中計算,使用比較麻煩

我們先來介紹一下CGGradient的使用,它使用起來很簡單,我們通過它來實現上面介紹的軸向漸變和徑向漸變的例子

例項化方法:

//
引數 // 1、顏色空間 // 2、指定漸變中的顏色(需要與locations個數對應,每組顏色值為rgba(根據顏色空間決定),例如:1,0,0,1,表示一個紅色,透明度為1) // 3、指定漸變的過度位置(值範圍:[0,1],0:幾何開始的位置;1:幾何結束的位置;0.5:幾何中間的位置) init?(colorSpace space: CGColorSpace, colorComponents components: UnsafePointer<CGFloat>, locations: UnsafePointer<CGFloat>?, count: Int)
// 引數 // 1、顏色空間 // 2、指定漸變中的顏色(需要與locations個數對應,每組顏色值為:CGColor) // 3、指定漸變的過度位置(值範圍:[0,1],0:幾何開始的位置;1:幾何結束的位置;0.5:幾何中間的位置) init?(colorsSpace space: CGColorSpace?, colors: CFArray, locations: UnsafePointer<CGFloat>?)

具體實現程式碼:

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    
    // 初始化顏色空間,有兩種方法,一般採用第一種(下面兩個顏色空間是一樣的)
    // 方法1:
    let space = CGColorSpaceCreateDeviceRGB()
    // 方法2:
//        let space = CGColorSpace(name: CGColorSpace.sRGB)!
    // 設定漸變的過度位置,範圍:[0, 1]
    var locations: [CGFloat] = [0, 0.5, 1]
    // 設定過度的顏色元件集合,因為我們的顏色空間是RGB,因此每組的顏色值為rgba,即:[r, g, b, a]
    // 注意:每組的顏色是在陣列中連續的,即:[r,g,b,a, r,g,b,a, r,g,b,a]
    var colors: [CGFloat] = [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1]
    // 初始化漸變物件CGGradient
    guard let gradient = CGGradient(colorSpace: space,
                                    colorComponents: &colors,
                                    locations: &locations,
                                    count: locations.count) else {
        print("gradient create fail")
        return
    }
    // 繪製軸向漸變
    // 引數:
    // 1、漸變物件例項
    // 2、漸變的幾何開始點座標
    // 3、漸變的幾何結束點座標
    // 4、繪製選項,包括:
    // (1)drawsBeforeStartLocation:在幾何開始點的位置之前的空間部分(超出邊界不繪製),用第一個顏色值擴散顏色
    // (2)drawsAfterEndLocation:在幾何結束點的位置之後的空間部分(超出邊界不繪製),用最後一個顏色值擴散顏色
    context.drawLinearGradient(gradient,
                               start: .init(x: 0, y: 0),
                               end: .init(x: bounds.width, y: bounds.height),
                               options: .drawsBeforeStartLocation)
    
    // 繪製徑向漸變
    // 引數:
    // 1、漸變物件例項
    // 2、漸變開始的圓心點座標
    // 3、漸變開始的圓半徑(輻射半徑)
    // 4、漸變結束的圓心點座標
    // 5、漸變結束的圓半徑(輻射半徑)
    // 6、繪製選項,跟上面的一樣
//        context.drawRadialGradient(gradient,
//                                   startCenter: .init(x:bounds.width/2, y:bounds.height/2),
//                                   startRadius: 10,
//                                   endCenter: .init(x: bounds.width/2, y: bounds.height/2),
//                                   endRadius: 50,
//                                   options: .drawsBeforeStartLocation)
    
}

程式碼中註釋介紹的已經很詳細了,這裡我給大家著重介紹一下options引數。我們看下圖便一目瞭然:

紅色區域:是漸變開始點繪製之前的顏色填充部分。

藍色區域:是漸變結束點繪製之後的顏色填充部分。

options選項決定在漸變的開始點之前 還是 結束點之後 的區域內進行顏色填,填充的顏色色值為:

1、開始點之前繪製(drawsBeforeStartLocation):填充色為顏色陣列中第一個顏色值

2、結束點之後繪製(drawsAfterEndLocation):填充色為顏色陣列中最後一個顏色值

下面是兩種options的軸向漸變的繪圖結果:(徑向漸變也是如此)

drawsBeforeStartLocation:(開始點前採用紅色填充,結束點後無填充)

drawsAfterEndLocation:(開始點前無填充,結束點後採用藍色填充)

接下來介紹CGShading的使用,它在繪製漸變時用起來比CGGradient要複雜,因為需要我們自己來計算漸變過程中每個點的顏色。使用時涉及到以下幾個關鍵物件:

CGFunctionCallbacks:用於提供計算漸變過程中每個點的顏色的回撥函式,這就是我們實現漸變的關鍵點。

CGFunction:為上面的回撥函式設定初始值,以及相關值的範圍的物件。

CGShading:定義漸變的規則,例如:顏色空間、從幾何區域的哪裡開始漸變到哪裡結束漸變等。

實現軸向漸變與徑向漸變的例項化方法不一樣,下面是對應這兩種實現的例項化方法:

// 初始化軸向漸變物件
init?(axialSpace space: CGColorSpace, 
      start: CGPoint, 
      end: CGPoint, 
      function: CGFunction, 
      extendStart: Bool, 
      extendEnd: Bool)
        
// 初始化徑向漸變物件
init?(radialSpace space: CGColorSpace, 
      start: CGPoint, 
      startRadius: CGFloat, 
      end: CGPoint, 
      endRadius: CGFloat, 
      function: CGFunction, 
      extendStart: Bool, 
      extendEnd: Bool)

下面是一個利用CGShading實現軸向漸變的具體實現的demo,程式碼中註釋寫的很詳細,大家可以參考。

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    
    // 初始化一個回撥函式,用於計算每個點的顏色值
    // 引數:
    // 1、CGFunction初始化傳入的第一個引數info(指標型別)
    // 2、輸入值
    // 3、輸出值
    // 4、釋放info指標的回撥函式
    var callback = CGFunctionCallbacks(version: 0) { info, inVal, outVal in
        
        let numberOfComponents: Int
        if let info = info {
            // 注意這裡不能用takeRetainedValue()取值,否則會提前釋放info的記憶體,導致下次訪問info時出錯
            numberOfComponents = Unmanaged<NSNumber>.fromOpaque(info).takeUnretainedValue().intValue
        }else{
            numberOfComponents = 0
        }
        
        // 漸變開始的顏色值
        let startColor: [CGFloat] = [1, 0, 0, 1]
        // 漸變結束的顏色值
        let endColor: [CGFloat] = [0, 1, 0, 1]
        
        let aInVal = inVal.pointee // 獲取每次迭代的輸入值,下面指定了輸入值範圍在[0, 1],因此aInVal的值範圍在[0, 1]
        var out = outVal
        
        // 下面指定了輸出值包含的分量個數為numberOfComponents(即:out中應返回numberOfComponents個元素)
        for i in 0..<numberOfComponents {
            
            // 注意:下面指定了輸出值中每個分量(每個元素)的值範圍[0, 1],因此這裡的計算結果不要超出這個範圍
            if aInVal < 0.5 {
                // 計算漸變開始點到中間點區間的顏色分量值
                out.pointee = startColor[i] * (0.8 - aInVal)
            }else{
                // 計算漸變中間點到結束點區間的顏色分量值
                out.pointee = endColor[i] * (aInVal - 0.2)
            }
            out = out.successor()
        }
        
    } releaseInfo: { info in
        guard let info = info else { return }
        // 釋放info指標記憶體
        Unmanaged<NSNumber>.fromOpaque(info).release()
    }
    
    // 初始化顏色空間
    let space = CGColorSpaceCreateDeviceRGB()
    
    // 獲取通道顏色分量的個數,這裡要加一個alpha
    let numberOfComponents = space.numberOfComponents + 1 // rgb + a
    // 指定上面callback中輸入值的遞增迭代次數,這裡設定1,即:輸入值會從domain的範圍內,迭代一次(0, 0.0.00167, ..., 1)終止
    // 如果設定為2,會迭代兩次:第一次(0, 0.0.00167, ..., 1),第二次(0, 0.0.00167, ..., 1)
    let domainDimension = 1
    // 輸入值的變化範圍,這裡指定從0到1,即輸入值會根據漸變的開始點到結束點的變化,從0開始遞增到1,如(0, 0.0.00167, ..., 1)
    // 如果設定為[1, 10],則輸入值會從1開始遞增到10
    let domain: [CGFloat] = [0, 1] // 注意:這裡的範圍必須是遞增的
    // 指定上面callback中輸出值包含的分量個數,如果為4,那麼上面callback的輸出值out指標應該包含4個分量值(即:rgba)。
    // 可以理解為輸出值陣列的size
    let rangeDimension = numberOfComponents
    // 輸出值每個分量的範圍,即r值的範圍[0, 1],g值的範圍[0, 1], b值的範圍[0, 1], a值的範圍[0, 1]
    // 可以理解為輸出值陣列中每個元素值的取值範圍
    let range: [CGFloat] = [0, 1, 0, 1, 0, 1, 0, 1] // 陣列下標0和1代表第一個值的範圍,一次類推
    guard let function = CGFunction(info: Unmanaged<NSNumber>.passRetained(NSNumber(integerLiteral: numberOfComponents)).toOpaque(),
                                    domainDimension: domainDimension,
                                    domain: domain,
                                    rangeDimension: rangeDimension,
                                    range: range,
                                    callbacks: &callback) else {
        print("function create fail")
        return
    }
    
    // 漸變的開始點座標
    let start = CGPoint(x: bounds.width/4, y: bounds.height/4)
    // 漸變的結束點座標
    let end = CGPoint(x: bounds.width * 3 / 4, y: bounds.height * 3 / 4)
    
    // 指定漸變開始點之前的區域是否執行填充顏色,相當於使用CGGradient繪製漸變時,呼叫drawLinearGradient方法時設定的:drawsBeforeStartLocation
    let extendStart = true
    // 指定漸變結束點之後的區域是否執行填充顏色,相當於使用CGGradient繪製漸變時,呼叫drawLinearGradient方法時設定的:drawsAfterEndLocation
    let extendEnd = true
    // 初始化一個軸向漸變例項CGShading
    guard let shading = CGShading(axialSpace: space,
                                  start: start,
                                  end: end,
                                  function: function,
                                  extendStart: extendStart,
                                  extendEnd: extendEnd) else {
        print("shading create fail")
        return
    }
    // 繪製漸變
    context.drawShading(shading)
}

程式碼中註釋介紹的已經很詳細了,這裡就不詳細說明了,下面是實現的效果:

徑向漸變的實現也是類似的,只不過是CGShading的例項化方法不一樣。下面是採用CGShading實現的徑向漸變的效果:

要得到你必須要付出,要付出你還要學會堅持,如果你真的覺得很難,那你就放棄,但是你放棄了就不要抱怨,我覺得人生就是這樣,世界真的是平等的,每個人都要通過自己的努力,去決定自己生活的樣子。