nojiko icon indicating copy to clipboard operation
nojiko copied to clipboard

函数 - hsv... - 一套用于处理 HSV 模式的函数

Open BiosSun opened this issue 6 years ago • 0 comments

/// 构建一个 HSV 数据集合
/// 
/// @param {number/deg} $h - 色相,值须在 [0deg, 360deg] 区间内,否则将以 360deg 取模;
/// @param {number/%} $s - 对比度,值须在 [0%, 100%] 区间内,否则将抛出异常;
/// @param {number/%} $v - 明度,值须在 [0%, 100%] 区间内,否则将抛出异常;
///
/// @return {Map} 
/// 
///     一个包含三个键值对的集合,分别为:
///     
///     - `h` `{number/deg}` 色相;
///     - `s` `{number/%}` 饱和度;
///     - `v` `{number/%}` 明度;
@function hsv($h, $s, $v) {
    @if $s < 0 or $s > 100 or $v < 0 or $v > 100 {
        @error "must be between 0 and 100.";
    }
    
    @return (
        h: $h % 360deg,
        s: $s + 0%,
        v: $v + 0%,
        __is-hsv: true
    )
}

/// 将一个色值转换为 HSV 模式
///
/// TODO 不支持含有不透明度的色值,转换后将丢失其不透明度。
///
/// @link http://ariya.blogspot.com/2008/07/converting-between-hsl-and-hsv.html Converting between HSL and HSV
/// 
/// @param {Color} $color - 待转换的色值;
/// 
/// @return {Map}
/// 
///     一个包含三个键值对的集合,分别为:
///     
///     - `h` `{number/deg}` 色相;
///     - `s` `{number/%}` 饱和度;
///     - `v` `{number/%}` 明度;
@function to-hsv($color) {
    @if (is-hsv($color)) {
        @return $color;
    }

    $h: hue($color);
    $s: saturation($color);
    $l: lightness($color);
    
    @if (unit($s) == '%') {
        $s: strip-unit($s) / 100;
    }
    
    @if (unit($l) == '%') {
        $l: strip-unit($l) / 100;
    }
    
    $l: $l * 2;
    
    $s: $s * ( if($l <= 1, $l, 2 - $l) );
    
    @return hsv(
        $h,
        percentage(limit( 2 * $s / ( $l + $s ), 0, 1 )),
        percentage(limit( ($l + $s) / 2, 0, 1)),
    );
}

/// 判断所传入的数据是否是由 `to-hsv` 方法所生成的 HSV 色值数据;
/// @param {Map} $val - 待判断的值;
/// @return {boolean}
@function is-hsv($val) {
    @return type-of($val) == 'map' and map-get($val, __is-hsv) == true;
}

/// 获取一个色值所对应 HSV 模式中的色相值;
/// @param {Color} $color - 一个颜色值(RGB,HSL 或是由 to-hsv 生成的 HSV 数据);
/// @return {number/deg}
///
/// @example scss
///     $hue: hsv-hue(#f 0);                     // --> 0deg
///     $hue: hsv-hue(hsl(120deg, 100%, 50%);    // --> 120deg
///     $hue: hsv-hue(to-hsv(#00f));             // --> 240deg
@function hsv-hue($color) {
    @return map-get(to-hsv($color), h); 
}

/// 获取一个色值所对应 HSV 模式中的饱和度值;
/// @param {Color} $color - 一个颜色值(RGB,HSL 或是由 to-hsv 生成的 HSV 数据);
/// @return {number/%}
///
/// @example scss
///     $hue: hsv-saturation(#f00);                    // --> 100%
///     $hue: hsv-saturation(hsl(240deg, 60%, 50%);    // --> 75%
///     $hue: hsv-saturation(to-hsv(#ff8080));         // --> 49.8039215686%
@function hsv-saturation($color) {
    @return map-get(to-hsv($color), s); 
}

/// 获取一个色值所对应 HSV 模式中的明度值;
/// @param {Color} $color - 一个颜色值(RGB,HSL 或是由 to-hsv 生成的 HSV 数据);
/// @return {number/%}
///
/// @example scss
///     $hue: hsv-value(#f00);                    // --> 100%
///     $hue: hsv-value(hsl(0deg, 100%, 37.5%);   // --> 75%
///     $hue: hsv-value(to-hsv(#800000));         // --> 50.1960784314%
@function hsv-value($color) {
    @return map-get(to-hsv($color), v);
}

/// 将 hsv 转换为 hsl
/// @param {Map} $val - 待转换的 HSV 色值数据;
/// @return {Color<HSL>}
@function hsv-to-hsl($hsv) {
    $h: hsv-hue($hsv);
    $s: hsv-saturation($hsv);
    $v: hsv-value($hsv);
    
    $s: 0 + ($s / 100%);
    $v: 0 + ($v / 100%);
    
    $l: (2 - $s) * $v;
    
    @return hsl(
        $h,
        percentage(limit($s * $v / (if($l <= 1, $l, 2 - $l)), 0, 1)),
        percentage(limit($l / 2, 0, 1))
    );
}

/// 将 hsv 转换为 rgb
/// @link https://www.rapidtables.com/convert/color/hsv-to-rgb.html HSV to RGB
/// @param {Map} $val - 待转换的 HSV 色值数据;
/// @return {Color<RGB>}
@function hsv-to-rgb($hsv) {
    $h: hsv-hue($hsv);
    $s: hsv-saturation($hsv);
    $v: hsv-value($hsv);
    
    $h: strip-unit($h);
    $s: strip-unit($s) / 100;
    $v: strip-unit($v) / 100;

    $c: $v * $s;
    $x: $c * (1 - abs(($h / 60) % 2 - 1));
    $m: $v - $c;
    
    $rgb: ();
    
    @if 0 <= $h and $h < 60 {
        $rgb: ($c, $x, 0);
    }
    @else if 60 <= $h and $h < 120  {
        $rgb: ($x, $c, 0);
    }
    @else if 120 <= $h and $h < 180 {
        $rgb: (0, $c, $x);
    }
    @else if 180 <= $h and $h < 240 {
        $rgb: (0, $x, $c);
    }
    @else if 240 <= $h and $h < 300 {
        $rgb: ($x, 0, $c);
    }
    @else if 300 <= $h and $h < 360 {
        $rgb: ($c, 0, $x);
    }
    
    @return rgb(
        (nth($rgb, 1) + $m) * 255, 
        (nth($rgb, 2) + $m) * 255, 
        (nth($rgb, 3) + $m) * 255
    );
}

BiosSun avatar Jun 17 '19 15:06 BiosSun