如何使用 CAGradientLayer 绘制渐变色轮?

Posted

技术标签:

【中文标题】如何使用 CAGradientLayer 绘制渐变色轮?【英文标题】:How to draw a gradient color wheel using CAGradientLayer? 【发布时间】:2017-08-20 18:42:26 【问题描述】:

我从这些链接中得到了一些参考——

What is the Algorithm behind a color wheel?

Math behind the Colour Wheel

Basic color schemes

How to fill a bezier path with gradient color

我已经了解了“HSV 色彩空间”的概念。但是我想在CAGradientLayer的帮助下使用RGB绘制一个色轮。

这里是使用简单的RGB颜色数组和UIBezierPath制作色轮的代码sn-p -

func drawColorWheel()

    context?.saveGState()

    range = CGFloat(100.00 / CGFloat(colorArray.count))

    for k in 0 ..< colorArray.count
    

        drawSlice(startPercent: CGFloat(k) * range, endPercent: CGFloat(CGFloat(k + 1) * range), color: colorArray.object(at: k) as! UIColor)
    

    context?.restoreGState()


func drawSlice(startPercent: CGFloat, endPercent: CGFloat, color: UIColor)
        
    let startAngle = getAngleAt(percentage: startPercent)
    let endAngle = getAngleAt(percentage: endPercent)

    let path =  getArcPath(startAngle: startAngle, endAngle: endAngle)
    color.setFill()
    path.fill()

其中getAngleAt()getArcPath() 是用角度绘制路径的私有函数。

这是我的代码的最终输出 -

现在,我的问题是如何给这些颜色一个渐变效果,使每种颜色与渐变色层混合?

【问题讨论】:

【参考方案1】:

一种方法是构建图像并手动操作像素缓冲区:

创建特定大小和特定类型的CGContext; 通过data属性访问其数据缓冲区; 将其重新绑定到可以轻松操作该缓冲区的东西(我使用Pixelstruct 用于像素的 32 位表示); 逐个循环遍历像素,将其转换为该图像中圆圈的angleradius;和 如果像素在圆圈内,则创建适当颜色的像素;如果不是,则将其设为零 alpha 像素。

所以在 Swift 3 中:

func buildHueCircle(in rect: CGRect, radius: CGFloat, scale: CGFloat = UIScreen.main.scale) -> UIImage? 
    let width = Int(rect.size.width * scale)
    let height = Int(rect.size.height * scale)
    let center = CGPoint(x: width / 2, y: height / 2)

    let space = CGColorSpaceCreateDeviceRGB()
    let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: space, bitmapInfo: Pixel.bitmapInfo)!

    let buffer = context.data!

    let pixels = buffer.bindMemory(to: Pixel.self, capacity: width * height)
    var pixel: Pixel
    for y in 0 ..< height 
        for x in 0 ..< width 
            let angle = fmod(atan2(CGFloat(x) - center.x, CGFloat(y) - center.y) + 2 * .pi, 2 * .pi)
            let distance = hypot(CGFloat(x) - center.x, CGFloat(y) - center.y)

            let value = UIColor(hue: angle / 2 / .pi, saturation: 1, brightness: 1, alpha: 1)

            var red: CGFloat = 0
            var green: CGFloat = 0
            var blue: CGFloat = 0
            var alpha: CGFloat = 0
            value.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

            if distance <= (radius * scale) 
                pixel = Pixel(red:   UInt8(red * 255),
                              green: UInt8(green * 255),
                              blue:  UInt8(blue * 255),
                              alpha: UInt8(alpha * 255))
             else 
                pixel = Pixel(red: 255, green: 255, blue: 255, alpha: 0)
            
            pixels[y * width + x] = pixel
        
    

    let cgImage = context.makeImage()!
    return UIImage(cgImage: cgImage, scale: scale, orientation: .up)

在哪里

struct Pixel: Equatable 
    private var rgba: UInt32

    var red: UInt8 
        return UInt8((rgba >> 24) & 255)
    

    var green: UInt8 
        return UInt8((rgba >> 16) & 255)
    

    var blue: UInt8 
        return UInt8((rgba >> 8) & 255)
    

    var alpha: UInt8 
        return UInt8((rgba >> 0) & 255)
    

    init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) 
        rgba = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
    

    static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue

    static func ==(lhs: Pixel, rhs: Pixel) -> Bool 
        return lhs.rgba == rhs.rgba
    

产生:

或者您可以根据半径调整亮度或饱和度:

【讨论】:

真棒答案+1

以上是关于如何使用 CAGradientLayer 绘制渐变色轮?的主要内容,如果未能解决你的问题,请参考以下文章

Swift - CAGradientLayer

iOS 编程中, 如何使用 Swift 和 CAGradientLayer 绘制这个矩形

圆形 CAGradientLayer 蒙版

iOS 绘制颜色渐变圆环 --- 值得一看

使用 CAGradientLayer 和 drawRect:(CGRect) 绘制顺序

使用 CAShapeLayer 创建的动画圆的 CAGradientLayer 问题