[Swift] グラデーションの任意の色を自動抽出することでClearのようなUIを目指す


![Clear.png](https://qiita-image-store.s3.amazonaws.com/0/93219/ee3bc77e-2984-d798-a35c-e23807129341.png)

[Clear](https://itunes.apple.com/jp/app/clear-tasuku-todorisuto/id493136154?mt=8)のようなグラデーションUIを実装しようとなった時，どうしていますか？
カラーピッカーを少しずつ動かしてますか？
グラデーションツールなんかを使って抽出していますか？

**さすがに面倒くさいので，グラデーションから自動で色を抽出したいですよね！**
この手の記事は少ないように思えたので，色々と調べまわった結果をまとめようと思います．

# サンプルプロジェクト
サンプルプロジェクトは[こちら](https://github.com/krimpedance/HierarchicalGradationDemo)に置いてます．

# グラデーションの生成
まずは抽出する対象となるグラデーションの生成をCAGradientLayerを用いて行います．

```swift
let startColor = UIColor.redColor()  // 開始色
let endColor = UIColor.yellowColor()  // 終了色

let gradient = CAGradientLayer()
gradient.frame = self.view.bounds
gradient.colors = [
  startColor.CGColor,
  endColor.CGColor
]
```

グラデーションのイメージはこんな感じです．
<img src="https://qiita-image-store.s3.amazonaws.com/0/93219/7c49a642-ba56-760d-44ef-d4932c89a15f.png" height="200">


# 各階層の色を抽出
生成したグラデーションから，各階層に割り当てる色を抽出します．
抽出する関数は以下のようになっています．

```swift
func colorOfPoint(gradation :CAGradientLayer, point:CGPoint)->UIColor {
  var pixel:[CUnsignedChar] = [0,0,0,0]
  
  let colorSpace = CGColorSpaceCreateDeviceRGB()
  let bitmap = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)
  let context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, colorSpace, bitmap.rawValue)
        
  CGContextTranslateCTM(context, -point.x, -point.y)
  gradation.renderInContext(context!)
        
  let red:CGFloat = CGFloat(pixel[0])/255.0
  let green:CGFloat = CGFloat(pixel[1])/255.0
  let blue:CGFloat = CGFloat(pixel[2])/255.0
  let alpha:CGFloat = CGFloat(pixel[3])/255.0
        
  return UIColor(red:red, green: green, blue:blue, alpha:alpha)
}
```

context周りで何をしているかは[こちら](http://ameblo.jp/xcc/entry-10444639753.html)に詳しく書かれていたので，割愛します．m(._.)m
抽出したいポイントをこの関数に投げてやると，`UIColor`を返してくれます．

7等分したポイントから取得する時の例を示します．

```swift
// pick color
let divisionCount = 7
let interval = gradient.frame.height / CGFloat(divisionCount)

let colors = (0..<divisionCount).map {
  let point = CGPointMake(0, CGFloat($0)*interval)
  return self.colorOfPoint(gradient, point: point)
}
```

![比較.png](https://qiita-image-store.s3.amazonaws.com/0/93219/afb12dd7-507f-e23f-cad2-835dc44b0b36.png)

このように，ちゃんと色が取れて階層的になっていることがわかります．

若干投げやりな説明でしたが，グラデーションからの色抽出に関する説明は以上になります．m(._.)m


# 補足
グラデーションを作成する時に，カラーを増やしたり，`locations`プロパティをいじることで変化のある色を取得できるかと思います．

また，今回の説明で`colorOfPoint(gradation :CAGradientLayer, point:CGPoint)->UIColor`はそのまま追加していましたが，
現実的には以下のように`CAGradientLayer`を拡張するのが良いかと思います．

```swift
extension CAGradientLayer {   
  func colorOfPoint(point:CGPoint)->UIColor{
    var pixel:[CUnsignedChar] = [0,0,0,0]
      
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmap = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)
      
    let context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, colorSpace, bitmap.rawValue)     
    CGContextTranslateCTM(context, -point.x, -point.y)
        
    self.renderInContext(context!)
        
    let red:CGFloat = CGFloat(pixel[0])/255.0
    let green:CGFloat = CGFloat(pixel[1])/255.0
    let blue:CGFloat = CGFloat(pixel[2])/255.0
    let alpha:CGFloat = CGFloat(pixel[3])/255.0
        
    return UIColor(red:red, green: green, blue:blue, alpha:alpha)
  }    
}

// colors = gradient.colorOfPoint(CGPointMake(10, 10))
```

# おまけ
Extensionの話が出たのでついでにもう一つ...笑
UIColorをIntで指定したいことってよくあるんで，自分は以下のような拡張をしてます．

```swift
extension UIColor {
  /// intColor with alpha
  convenience init(intRed red :Int, green :Int, blue :Int, alpha :CGFloat) {
    self.init(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha:alpha)
  }                                    
    
  /// intColor
  convenience init(intRed red :Int, green :Int, blue :Int) {
    self.init(intRed: red, green: green, blue: blue, alpha: 1.0)
  }
}
```

Hexなんかも追加すれば，快適なカラーライフが待っているかと！！