uicolor
Swift
gradient
Swift2.0

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

More than 1 year has passed since last update.

Clear.png

ClearのようなグラデーションUIを実装しようとなった時,どうしていますか?
カラーピッカーを少しずつ動かしてますか?
グラデーションツールなんかを使って抽出していますか?

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

サンプルプロジェクト

サンプルプロジェクトはこちらに置いてます.

グラデーションの生成

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

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

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

グラデーションのイメージはこんな感じです.

各階層の色を抽出

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

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周りで何をしているかはこちらに詳しく書かれていたので,割愛します.m(._.)m
抽出したいポイントをこの関数に投げてやると,UIColorを返してくれます.

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

// 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

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

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

補足

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

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

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で指定したいことってよくあるんで,自分は以下のような拡張をしてます.

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なんかも追加すれば,快適なカラーライフが待っているかと!!