实现HSL颜色模型与HEX颜色模型互相转换
=====================

#### 问题现象

给定HSL颜色模型值，如何将其转换为HEX颜色模型的值？

#### 背景知识

*   HSL：HSL将颜色分解为三个参数。
    *   色相（Hue）：颜色的类型（如红、蓝、绿等），用0°~360°表示。
    *   饱和度（Saturation）：颜色的纯度或鲜艳程度，用0%~100%表示（0%为灰色，100%为纯色）。
    *   亮度（Lightness）：颜色的明暗程度，用0%~100%表示（0%为黑色，100%为白色）。
*   HEX：HEX是一种基于RGB（红、绿、蓝）的编码方式，用十六进制数表示颜色的三原色分量。

#### 解决方案

定义ColorModel类：

export class RgbType {
 _ // 红色分量，范围为0-255_
  red: number = 0;
  _// 绿色分量，范围为0-255_
  green: number = 0;
  _// 蓝色分量，范围为0-255_
  blue: number = 0;
}

export class HslType {
  _// 色相，范围为0-360_
  hue: number = 0;
 _ // 饱和度，范围为0-100_
  saturation: number = 0;
 _ // 亮度，范围为0-100_
  lightness: number = 0;
}

在ColorUtils类中实现HSL颜色模型与HEX颜色模型互相转换：

*   实现HSL转换HEX：
    1.  先将HSL饱和度和亮度百分比转换为小数，计算RGB值。
    2.  再把RGB值转换为HEX值。
*   实现HEX转换HSL：
    1.  先将HEX值转为RGB整数。
    2.  再将得到的值归一化为\[0,1\]范围的浮点数计算HSL值。

import { HslType, RgbType } from '../model/ColorModel';

_// 将HSL颜色模型转换为HEX颜色模型_
export function hslToHex(hue: number, saturation: number, lightness: number): string {
 _ // 将HSL转换为RGB_
  const rgb: RgbType = hslToRgb(hue, saturation, lightness);
  _// 返回HEX颜色值_
  return rgbToHex(rgb.red, rgb.green, rgb.blue);
}

_// 将HSL颜色值转换为RGB颜色格式_
function hslToRgb(hue: number, saturation: number, lightness: number): RgbType {
  let red: number, green: number, blue: number;
 _ // 将饱和度和亮度从百分比转换为小数_
  saturation /= 100;
  lightness /= 100;

  if (saturation === 0) {
   _ // 无饱和度，返回灰色_
    red = Math.round(lightness \* 255);
    green = Math.round(lightness \* 255);
    blue = Math.round(lightness \* 255);
  } else {
   _ // 辅助函数：根据HSL值计算RGB值，处理不同的色相区间_
    const convertHueToRgb = (baseValue: number, brightnessMultiplier: number, hueFraction: number): number => {
      _// 确保hueFraction在0到1之间_
      if (hueFraction < 0) {
        hueFraction += 1;
      }
      if (hueFraction > 1) {
        hueFraction -= 1;
      }
    _  // 第一个区间_
      if (hueFraction < 1 / 6) {
        return baseValue + (brightnessMultiplier - baseValue) \* 6 \* hueFraction;
      }
     _ // 第二个区间_
      if (hueFraction < 1 / 2) {
        return brightnessMultiplier;
      }
     _ // 第三个区间_
      if (hueFraction < 2 / 3) {
        return baseValue + (brightnessMultiplier - baseValue) \* (2 / 3 - hueFraction) \* 6;
      }
     _ // 第四个区间_
      return baseValue;
    };
   _ // 根据亮度计算中间值brightnessMultiplier和baseValue_
    const brightnessMultiplier =
      lightness < 0.5 ? lightness \* (1 + saturation) : lightness + saturation - lightness \* saturation;
    const baseValue = 2 \* lightness - brightnessMultiplier;
   _ // 计算RGB值_
    red = Math.round(convertHueToRgb(baseValue, brightnessMultiplier, hue / 360 + 1 / 3) \* 255);
    green = Math.round(convertHueToRgb(baseValue, brightnessMultiplier, hue / 360) \* 255);
    blue = Math.round(convertHueToRgb(baseValue, brightnessMultiplier, hue / 360 - 1 / 3) \* 255);
  }
  return {
    red: red,
    green: green,
    blue: blue
  };
}

_// 将RGB颜色值转换为十六进制格式_
function rgbToHex(red: number, green: number, blue: number): string {
  return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
}

_// 将十六进制颜色值转换为HSL颜色格式_
export function hexToHsl(hex: string): HslType | null {
 _ // 将HEX类型颜色转为RGB类型_
  let rgb = hexToRgb(hex);
  if (rgb === null) {
    return null;
  }
  _// 将RGB类型颜色转为HSL类型_
  return rgbToHsl(rgb.red, rgb.green, rgb.blue);
}

_// 将十六进制颜色字符串转换为RGB对象_
export function hexToRgb(hex: string): RgbType | null {
 _ // 使用正则表达式匹配十六进制颜色字符串_
  const result = /^#?(\[a-f\\d\]{2})(\[a-f\\d\]{2})(\[a-f\\d\]{2})$/i.exec(hex);
  if (result) {
    _// 将匹配的十六进制值转换为十进制RGB值_
    return {
      red: parseInt(result\[1\], 16),
      green: parseInt(result\[2\], 16),
      blue: parseInt(result\[3\], 16)
    };
  } else {
    _// 如果输入无效，返回null_
    return null;
  }
}

_// 将RGB颜色值转换为HSL颜色对象_
function rgbToHsl(red: number, green: number, blue: number): HslType {
 _ // 将RGB值归一化到0-1范围_
  red /= 255;
  green /= 255;
  blue /= 255;
 _ // 计算最大值和最小值_
  let max = Math.max(red, green, blue);
  let min = Math.min(red, green, blue);
 _ // 计算亮度lightness_
  let hue: number = (max + min) / 2;
  let saturation: number = (max + min) / 2;
  let lightness: number = (max + min) / 2;

  if (max === min) {
    _// 如果最大值和最小值相等，色相和饱和度为0_
    hue = 0;
    saturation = 0;
  } else {
    let difference = max - min; _// 计算色差_
   _ // 计算饱和度saturation_
    saturation = lightness > 0.5 ? difference / (2 - max - min) : difference / (max + min);
   _ // 计算色相hue_
    if (max === red) {
      hue = (green - blue) / difference + (green < blue ? 6 : 0);
    } else if (max === green) {
      hue = (blue - red) / difference + 2;
    } else {
      hue = (red - green) / difference + 4;
    }
    hue \*= 60; _// 将色相转换为度数_
  }
 _ // 返回HSL值，四舍五入后返回_
  return { hue: Math.round(hue), saturation: Math.round(saturation \* 100), lightness: Math.round(lightness \* 100) };
}

运行示例参考如下：

import { hexToHsl, hslToHex } from '../Utils/ColorUtils';

@Entry
@Component
struct ColorPage {
  build() {
    Row() {
      Column({ space: 10 }) {
        Button("click me").onClick(() => {
          const color\_hsl = hexToHsl("#DDDCC7");
          console.info("hexToHsl:" + JSON.stringify(color\_hsl));
          if (color\_hsl != null) {
            const color\_change = hslToHex(color\_hsl.hue, color\_hsl.saturation + 10, color\_hsl.lightness - 10);
            console.info("饱和度+10、亮度-10结果：" + color\_change);
          }
        })
      }.width('100%')

    }.height('100%')
  }
}

#### 常见FAQ

Q：将HEX转为HSL后，再将HSL转回HEX颜色值与初始不一致。

A：HEX转为HSL转换时，HEX需先转为RGB整数，再归一化为\[0,1\]范围的浮点数计算HSL值，此过程因浮点运算可能产生小数点后多位误差。HSL转为HEX时，需将HSL的百分比重新映射为RGB浮点数，再四舍五入到整数，产生误差。结合这两次转换可能导致最终结果与初始值不一致。