RGB,HSL,HSV 互转

883 阅读6分钟

转换方法

RGB => HSL

/**
 * 入参取值范围 r, g, b: [0, 255]
 * 出参取值范围 h: [0, 360], s: [0, 100], l: [0, 100]
 */
const rgb2hsl = (r, g, b) => {
    r = r / 255 // [0, 1]
    g = g / 255 // [0, 1]
    b = b / 255 // [0, 1]

    const max = Math.max(r, g, b)
    const min = Math.min(r, g, b)
    const d = max - min

    const l = (max + min) / 2
    const s = d === 0 ? 0 : l > 0.5 ? d / (2 - 2 * l) : d / (2 * l)

    let h = 0
    if (d !== 0) {
        switch (max) {
            case r:
                h = (g - b) / d + (g < b ? 6 : 0)
                break
            case g:
                h = (b - r) / d + 2
                break
            case b:
                h = (r - g) / d + 4
                break
            default:
                break
        }
        h = h / 6
    }
    return { h: h * 360, s: s * 100, l: l * 100 }
}

RGB => HSV

/**
 * 入参取值范围 r, g, b: [0, 255]
 * 出参取值范围 h: [0, 360], s: [0, 1], v: [0, 1] (h`sv`和h`sl`使用不同的取值范围以区分)
 */
const rgb2hsv = (r, g, b) => {
    r = r / 255 // [0, 1]
    g = g / 255 // [0, 1]
    b = b / 255 // [0, 1]

    const max = Math.max(r, g, b)
    const min = Math.min(r, g, b)
    const d = max - min

    const v = max
    const s = max === 0 ? 0 : d / max

    let h = 0
    if (d !== 0) {
        switch (max) {
            case r:
                h = (g - b) / d + (g < b ? 6 : 0)
                break
            case g:
                h = (b - r) / d + 2
                break
            case b:
                h = (r - g) / d + 4
                break
            default:
                break
        }
        h = h / 6
    }
    return { h: h * 360, s, v }
}

HSV => HSL

/**
 * 入参取值范围 h: [0, 360], s: [0, 1], v: [0, 1] (h`sv`和h`sl`使用不同的取值范围以区分)
 * 出参取值范围 h: [0, 360], s: [0, 100], l: [0, 100]
 */
const hsv2hsl = (h, s, v) => {
    const t = (2 - s) * v
    s = v === 0 || s === 0 ? 0 : (s * v) / (t > 1 ? 2 - t : t)
    return { h, s: s * 100, l: (t / 2) * 100 }
}
公式的推导过程:

首先在 RGB => HSL 和 RGB => HSV 两个方法中唯二的两处差异,其中 max 和 min 是相同的

RGB => HSL (将 s 标记为 s1)
    const l = (max + min) / 2
    const s1 = d === 0 ? 0 : l > 0.5 ? d / (2 - 2 * l) : d / (2 * l)

RGB => HSV (将 s 标记为 s2)
    const v = max
    const s2 = max === 0 ? 0 : d / max

那么,在 HSV => HSL 推导过程中,
已知 HSV 中的 v === max,所以用 v 替换 max 带入公式
    s2 = max === 0 ? 0 : (max - min) / max
        ==> s2 = v === 0 ? 0 : (v - min) / v
    # 1)假设 v !== 0
    s2 = (v - min) / v
        ==> s2 * v = v - min
        ==> min = (1 - s2) * v
    d = max - min
        ==> d = v - (1 - s2) * v
        ==> d = s2 * v
    l = (v + min) / 2
        ==> 2 * l = v + (1 - s2) * v
        ==> 2 * l = (2 - s2) * v
    # 设变量 t = (2 - s2) * v
    s1 = d === 0 ? 0 : l > 0.5 ? d / (2 - 2 * l) : d / (2 * l)
        ==> s1 = s2 * v === 0 ? 0 : t > 1 ? (s2 * v) / (2 - t) : (s2 * v) / t
    # 2)假设 s2 !== 0 (v !== 0)
    s1 = t > 1 ? (s2 * v) / (2 - t) : (s2 * v) / t
        ==> (s2 * v) / (t > 1 ? 2 - t : t)
    # 得出 hsl 的 l 和 s 值分别为
    l = t / 2
    s1 = (s2 * v) / (t > 1 ? 2 - t : t)
    # 3)假设 v === 0
    s2 === 0
    t === 0
        ==> l === 0
        ==> s1 === 0
    # 4)假设 s2 === 0 (v !== 0)
    t === 2 * v
        ==> l === v
        ==> s1 === 0
    # 最终得出:
    t = (2 - s2) * v
    l = t / 2
    s1 = (v === 0 || s === 0) ? 0 : (s2 * v) / (t > 1 ? 2 - t : t)

HSL => HSV

/**
 * 入参取值范围 h: [0, 360], s: [0, 100], l: [0, 100]
 * 出参取值范围 h: [0, 360], s: [0, 1], v: [0, 1] (h`sv`和h`sl`使用不同的取值范围以区分)
 */
const hsl2hsv = (h, s, l) => {
    s = s / 100 // [0, 1]
    l = l / 100 // [0, 1]
    let v = 0
    if (s === 0) {
        v = l
    } else if (l > 0.5) {
        v = l + s * (1 - l)
        s = v === 0 ? 0 : (2 * s * (1 - l)) / v
    } else {
        v = l * (s + 1)
        s = v === 0 ? 0 : (2 * s) / (s + 1)
    }
    return { h, s, v }
}
公式的推导过程:

首先在 RGB => HSL 和 RGB => HSV 两个方法中唯二的两处差异,其中 max 和 min 是相同的

RGB => HSL (将 s 标记为 s1)
    const l = (max + min) / 2
    const s1 = d === 0 ? 0 : l > 0.5 ? d / (2 - 2 * l) : d / (2 * l)

RGB => HSV (将 s 标记为 s2)
    const v = max
    const s2 = max === 0 ? 0 : d / max

那么,在 HSL => HSV 推导过程中
已知 HSL 中的 l === (max + min) / 2,即 max + min = 2 * l
    # 1)假设 s1 === 0,那么 max === min,则 l = max
    v = max
        ==> v = l
    s2 = max === 0 ? 0 : (max - min) / max
        ==> s2 = max === 0 ? 0 : 0 / max
        ==> s2 = 0
    # 2)假设 s1 !== 0,l > 0.5
    2 * l = max + min
    s1 = (max - min) / (2 - 2 * l)
        ==> s1 * (2 - 2 * l) = max - min
    (2 * l) + (s1 * 2 * (1 - l)) = (max + min) + (max - min)
        ==> l + s1 * (1 - l) = max
    (2 * l) - (s1 * 2 * (1 - l)) = (max + min) - (max - min)
        ==> l - s1 * (1 - l) = min
    # 将 max = l + s1 * (1 - l), min = l - s1 * (1 - l) 带入公式
    v = l + s1 * (1 - l)
    s2 = max === 0 ? 0 : (max - min) / max
        ==> s2 = v === 0 ? 0 : (l + s1 * (1 - l) - (l - s1 * (1 - l))) / v
        ==> s2 = v === 0 ? 0 : (2 * s1 * (1 - l)) / v
    # 3)假设 s1 !== 0,l <= 0.5
    2 * l = max + min
    s1 = (max - min) / (2 * l)
        ==> 2 * l * s1 = max - min
    (2 * l) + (2 * l * s1) = (max + min) + (max - min)
        ==> l * (1 + s1) = max
    (2 * l) - (2 * l * s1) = (max + min) - (max - min)
        ==> l * (1 - s1) = min
    # 将 max = l * (1 + s1), min = l * (1 - s1) 带入公式
    v = l * (1 + s1)
    s2 = max === 0 ? 0 : d / max
        ==> s2 = v === 0 ? 0 : (l * (1 + s1) - l * (1 - s1)) / (l * (1 + s1))
        ==> s2 = v === 0 ? 0 : (2 * s1) / (1 + s1)

HSL => RGB

/**
 * 入参取值范围 h: [0, 360], s: [0, 100], l: [0, 100]
 * 出参取值范围 r, g, b: [0, 255]
 */
const hsl2rgb = (h, s, l) => {
    h = h / 360 // [0, 1]
    s = s / 100 // [0, 1]
    l = l / 100 // [0, 1]

    let r = l
    let g = l
    let b = l
    if (s !== 0) {
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s
        const p = 2 * l - q
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1
            if (t > 1) t -= 1
            if (t < 1 / 6) return p + (q - p) * 6 * t
            if (t < 1 / 2) return q
            if (t < 2 / 3) return p + (q - p) * 6 * (2 / 3 - t)
            return p
        }

        r = hue2rgb(p, q, h + 1 / 3)
        g = hue2rgb(p, q, h)
        b = hue2rgb(p, q, h - 1 / 3)
    }
    return { r: r * 255, g: g * 255, b: b * 255 }
}

HSV => RGB

/**
 * 入参取值范围 h: [0, 360], s: [0, 1], v: [0, 1] (h`sv`和h`sl`使用不同的取值范围以区分)
 * 出参取值范围 r, g, b: [0, 255]
 */
const hsv2rgb = (h, s, v) => {
    h = h / 360 // [0, 1]
    h = h < 1 ? h * 6 : 0 // [0, 6)
    const mod = Math.floor(h) // {0, 1, 2, 3, 4, 5}
    const f = h - mod
    const p = v * (1 - s)
    const q = v * (1 - f * s)
    const t = v * (1 - (1 - f) * s)
    const r = [v, q, p, p, t, v][mod]
    const g = [t, v, v, q, p, p][mod]
    const b = [p, p, t, v, v, q][mod]
    return { r: r * 255, g: g * 255, b: b * 255 }
}

测试

// case 1:
let rgb = [255, 255, 255]
let hsl = rgb2hsl(...rgb) // {h: 0, s: 0, l: 100}
let hsv = rgb2hsv(...rgb) // {h: 0, s: 0, v: 1}
hsl2rgb(...Object.values(hsl)) // {r: 255, g: 255, b: 255}
hsv2rgb(...Object.values(hsv)) // {r: 255, g: 255, b: 255}
hsl2hsv(...Object.values(hsl)) // {h: 0, s: 0, v: 1}
hsv2hsl(...Object.values(hsv)) // {h: 0, s: 0, l: 100}
// case 2:
let rgb = [0, 0, 0]
let hsl = rgb2hsl(...rgb) // {h: 0, s: 0, l: 0}
let hsv = rgb2hsv(...rgb) // {h: 0, s: 0, v: 0}
hsl2rgb(...Object.values(hsl)) // {r: 0, g: 0, b: 0}
hsv2rgb(...Object.values(hsv)) // {r: 0, g: 0, b: 0}
hsl2hsv(...Object.values(hsl)) // {h: 0, s: 0, v: 0}
hsv2hsl(...Object.values(hsv)) // {h: 0, s: 0, l: 0}
// case 3:
let rgb = [255, 0, 255]
let hsl = rgb2hsl(...rgb) // {h: 300, s: 100, l: 50}
let hsv = rgb2hsv(...rgb) // {h: 300, s: 1, v: 1}
hsl2rgb(...Object.values(hsl)) // {r: 255, g: 0, b: 254.99999999999994}
hsv2rgb(...Object.values(hsv)) // {r: 255, g: 0, b: 255}
hsl2hsv(...Object.values(hsl)) // {h: 300, s: 1, v: 1}
hsv2hsl(...Object.values(hsv)) // {h: 300, s: 100, l: 50}
// case 4:
let rgb = [0, 0, 255]
let hsl = rgb2hsl(...rgb) // {h: 240, s: 100, l: 50}
let hsv = rgb2hsv(...rgb) // {h: 240, s: 1, v: 1}
hsl2rgb(...Object.values(hsl)) // {r: 0, g: 0, b: 255}
hsv2rgb(...Object.values(hsv)) // {r: 0, g: 0, b: 255}
hsl2hsv(...Object.values(hsl)) // {h: 240, s: 1, v: 1}
hsv2hsl(...Object.values(hsv)) // {h: 240, s: 100, l: 50}
// case 5:
let rgb = [72, 201, 176]
let hsl = rgb2hsl(...rgb) // {h: 168.37209302325581, s: 54.43037974683544, l: 53.529411764705884}
let hsv = rgb2hsv(...rgb) // {h: 168.37209302325581, s: 0.6417910447761194, v: 0.788235294117647}
hsl2rgb(...Object.values(hsl)) // {r: 71.99999999999997, g: 201.00000000000003, b: 176.00000000000006}
hsv2rgb(...Object.values(hsv)) // {r: 72, g: 201, b: 176}
hsl2hsv(...Object.values(hsl)) // {h: 168.37209302325581, s: 0.6417910447761194, v: 0.788235294117647}
hsv2hsl(...Object.values(hsv)) // {h: 168.37209302325581, s: 54.43037974683544, l: 53.529411764705884}
// case 6:
let rgb = [97, 106, 107]
let hsl = rgb2hsl(...rgb) // {h: 185.99999999999997, s: 4.9019607843137285, l: 40}
let hsv = rgb2hsv(...rgb) // {h: 185.99999999999997, s: 0.09345794392523371, v: 0.4196078431372549}
hsl2rgb(...Object.values(hsl)) // {r: 97, g: 106.00000000000001, b: 107.00000000000001}
hsv2rgb(...Object.values(hsv)) // {r: 97, g: 106, b: 107}
hsl2hsv(...Object.values(hsl)) // {h: 185.99999999999997, s: 0.0934579439252337, v: 0.41960784313725497}
hsv2hsl(...Object.values(hsv)) // {h: 185.99999999999997, s: 4.9019607843137285, l: 40}
// case 7:
let rgb = [3, 155, 229]
let hsl = rgb2hsl(...rgb) // {h: 199.64601769911502, s: 97.41379310344828, l: 45.490196078431374}
let hsv = rgb2hsv(...rgb) // {h: 199.64601769911502, s: 0.9868995633187774, v: 0.8980392156862745}
hsl2rgb(...Object.values(hsl)) // {r: 2.9999999999999893, g: 155.00000000000006, b: 229}
hsv2rgb(...Object.values(hsv)) // {r: 2.999999999999982, g: 155.00000000000006, b: 229}
hsl2hsv(...Object.values(hsl)) // {h: 199.64601769911502, s: 0.9868995633187774, v: 0.8980392156862745}
hsv2hsl(...Object.values(hsv)) // {h: 199.64601769911502, s: 97.41379310344828, l: 45.490196078431374}

参考