Swift |使用 HSL 颜色 space 而不是标准 HSB/HSV

Swift | Use HSL color space instead of standard HSB/HSV

我目前正在开发 HSL 颜色选择器,它需要 HSL 颜色 space。

但是据我发现,UIColorColor都只能用brightness初始化,而不是我需要的lightness

有没有办法使用 HSL 颜色 space 而不是默认的 HSB/HSV 颜色?

请问,从 HSB 转换为 HSL 的最佳做法是什么?

谢谢!

您可以简单地创建一个 HSL 颜色 space 初始值设定项。

注意: //From HSL TO HSB --------- 部分的公式取自 https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV

实现如下:

UIColor

import UIKit 

extension UIColor {
    convenience init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat) {
        precondition(0...1 ~= hue &&
                     0...1 ~= saturation &&
                     0...1 ~= lightness &&
                     0...1 ~= alpha, "input range is out of range 0...1")
        
        //From HSL TO HSB ---------
        var newSaturation: CGFloat = 0.0
        
        let brightness = lightness + saturation * min(lightness, 1-lightness)
        
        if brightness == 0 { newSaturation = 0.0 }
        else {
            newSaturation = 2 * (1 - lightness / brightness)
        }
        //---------
        
        self.init(hue: hue, saturation: newSaturation, brightness: brightness, alpha: alpha)
    }
}

Color

import SwiftUI

extension Color {
    init(hue: Double, saturation: Double, lightness: Double, opacity: Double) {
        precondition(0...1 ~= hue &&
                     0...1 ~= saturation &&
                     0...1 ~= lightness &&
                     0...1 ~= opacity, "input range is out of range 0...1")
        
        //From HSL TO HSB ---------
        var newSaturation: Double = 0.0
        
        let brightness = lightness + saturation * min(lightness, 1-lightness)
        
        if brightness == 0 { newSaturation = 0.0 }
        else {
            newSaturation = 2 * (1 - lightness / brightness)
        }
        //---------
        
        self.init(hue: hue, saturation: newSaturation, brightness: brightness, opacity: opacity)
    }
}

您还可以在创建时将 HSL 值转换为 RGB

public extension UIColor {
    typealias RGBA = (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)

    var rgbaValues: RGBA  {
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0
        
        self.getRed(&r,
                    green: &g,
                    blue : &b,
                    alpha: &a
        )
        return (red:r, green:g, blue:b, alpha: a)
    }

    convenience init(hue h: CGFloat, saturation s: CGFloat, lightness l: CGFloat, alpha : CGFloat) {
        let v = UIColor
            .from(hue       : h,
                  saturation: s,
                  lightness : l,
                  alpha     : alpha)
            .rgbaValues
        self.init(red   : v.red,
                  green : v.green,
                  blue  : v.blue,
                  alpha : v.alpha
        )
    }

    static func from(hue h: CGFloat, saturation s: CGFloat, lightness l: CGFloat, alpha a: CGFloat = 1.0) -> UIColor {
        precondition(0...1 ~= h && 0...1 ~= s && 0...1 ~= l && 0...1 ~= a, "input range must be in 0...1" )
        let h6: CGFloat = (h * 6.0).truncatingRemainder(dividingBy: 6.0)

        let c: CGFloat = (1 - abs((2 * l) - 1)) * s
        let x: CGFloat = c * (1 - abs(h6.truncatingRemainder(dividingBy: 2) - 1))
        let m: CGFloat = l - (c / 2)
        
        func rgbaFrom(_ r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> RGBA { (red: r+m, green: g+m, blue: b+m, alpha: a) }
        func color(from rgba: RGBA) -> UIColor { .init(red: rgba.red, green: rgba.green, blue: rgba.blue, alpha: rgba.alpha) }
        
        switch h6 {
        case 0..<1: return color(from: rgbaFrom(c,x,0))
        case 1..<2: return color(from: rgbaFrom(x,c,0))
        case 2..<3: return color(from: rgbaFrom(0,c,x))
        case 3..<4: return color(from: rgbaFrom(0,x,c))
        case 4..<5: return color(from: rgbaFrom(x,0,c))
        case 5..<6: return color(from: rgbaFrom(c,0,x))
        default:    return .black
        }
    }
}