Implement hsl() and hsla() functions

This commit is contained in:
ConnorSkees 2020-02-09 03:13:31 -05:00
parent 86173a3ca7
commit 5cab99cd6e
5 changed files with 192 additions and 3 deletions

View File

@ -34,9 +34,50 @@ pub(crate) fn register(f: &mut BTreeMap<String, Builtin>) {
}; };
Some(Value::Color(Color::from_values(red, green, blue, alpha))) Some(Value::Color(Color::from_values(red, green, blue, alpha)))
} else { } else {
todo!("channels variable in `rgb`") todo!("channels variable in `rgba`")
} }
}); });
decl!(f "hsl", |args, _| {
let hue = match arg!(args, 0, "hue").clone().eval() {
Value::Dimension(n, Unit::None)
| Value::Dimension(n, Unit::Percent) => n,
_ => todo!("expected either unitless or % number for alpha"),
};
let saturation = match arg!(args, 1, "saturation").clone().eval() {
Value::Dimension(n, Unit::None)
| Value::Dimension(n, Unit::Percent) => n / Number::from(100),
_ => todo!("expected either unitless or % number for alpha"),
};
let luminance = match arg!(args, 2, "luminance").clone().eval() {
Value::Dimension(n, Unit::None)
| Value::Dimension(n, Unit::Percent) => n / Number::from(100),
_ => todo!("expected either unitless or % number for alpha"),
};
Some(Value::Color(Color::from_hsla(hue, saturation, luminance, Number::from(1))))
});
decl!(f "hsla", |args, _| {
let hue = match arg!(args, 0, "hue").clone().eval() {
Value::Dimension(n, Unit::None)
| Value::Dimension(n, Unit::Percent) => n,
_ => todo!("expected either unitless or % number for alpha"),
};
let saturation = match arg!(args, 1, "saturation").clone().eval() {
Value::Dimension(n, Unit::None)
| Value::Dimension(n, Unit::Percent) => n / Number::from(100),
_ => todo!("expected either unitless or % number for alpha"),
};
let luminance = match arg!(args, 2, "luminance").clone().eval() {
Value::Dimension(n, Unit::None)
| Value::Dimension(n, Unit::Percent) => n / Number::from(100),
_ => todo!("expected either unitless or % number for alpha"),
};
let alpha = match arg!(args, 3, "alpha").clone().eval() {
Value::Dimension(n, Unit::None) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100),
_ => todo!("expected either unitless or % number for alpha"),
};
Some(Value::Color(Color::from_hsla(hue, saturation, luminance, alpha)))
});
decl!(f "red", |args, _| { decl!(f "red", |args, _| {
match arg!(args, 0, "red") { match arg!(args, 0, "red") {
Value::Color(c) => Some(Value::Dimension(Number::from(BigInt::from(c.red())), Unit::None)), Value::Color(c) => Some(Value::Dimension(Number::from(BigInt::from(c.red())), Unit::None)),

View File

@ -3,6 +3,8 @@ use std::fmt::{self, Display};
use crate::value::Number; use crate::value::Number;
pub(crate) use name::ColorName; pub(crate) use name::ColorName;
use num_traits::cast::ToPrimitive;
mod name; mod name;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -41,6 +43,104 @@ impl Color {
self.alpha.clone() self.alpha.clone()
} }
/// Create RGBA representation from HSLA values
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
pub fn from_hsla(
mut hue: Number,
saturation: Number,
luminance: Number,
alpha: Number,
) -> Self {
println!("{}-{}-{}", &hue, &saturation, &luminance);
if saturation.clone() == Number::from(0) {
let luminance = if luminance > Number::from(100) {
Number::from(100)
} else {
luminance
};
let val = (luminance.clone() * Number::from(255))
.to_integer()
.to_u16()
.unwrap();
let repr = if alpha >= Number::from(1) {
format!("#{:0>2x}{:0>2x}{:0>2x}", val, val, val)
} else {
format!("rgba({}, {}, {}, {})", val, val, val, alpha)
};
return Color {
red: val,
green: val,
blue: val,
alpha: Number::from(alpha),
repr,
};
}
let temporary_1 = if luminance.clone() < Number::ratio(1, 2) {
luminance.clone() * (Number::from(1) + saturation)
} else {
luminance.clone() + saturation.clone() - luminance.clone() * saturation.clone()
};
let temporary_2 = Number::from(2) * luminance.clone() - temporary_1.clone();
hue = hue / Number::from(360);
let mut temporary_r = hue.clone() + Number::ratio(1, 3);
let mut temporary_g = hue.clone();
let mut temporary_b = hue.clone() - Number::ratio(1, 3);
macro_rules! clamp {
($temp:ident) => {
if $temp > Number::from(1) {
$temp -= Number::from(1);
} else if $temp < Number::from(0) {
$temp += Number::from(1);
}
};
}
clamp!(temporary_r);
clamp!(temporary_g);
clamp!(temporary_b);
macro_rules! channel {
($name:ident, $temp:ident, $temp1:ident, $temp2:ident) => {
let $name = (if Number::from(6) * $temp.clone() < Number::from(1) {
$temp2.clone()
+ ($temp1.clone() - $temp2.clone()) * Number::from(6) * $temp.clone()
} else if Number::from(2) * $temp.clone() < Number::from(1) {
$temp1.clone()
} else if Number::from(3) * $temp.clone() < Number::from(2) {
$temp2.clone()
+ ($temp1.clone() - $temp2.clone())
* (Number::ratio(2, 3) - $temp)
* Number::from(6)
} else {
$temp2.clone()
} * Number::from(255))
.round()
.to_integer()
.to_u16()
.expect("expected channel to fit inside u16");
};
}
channel!(red, temporary_r, temporary_1, temporary_2);
channel!(green, temporary_g, temporary_1, temporary_2);
channel!(blue, temporary_b, temporary_1, temporary_2);
let repr = if alpha >= Number::from(1) {
format!("#{:0>2x}{:0>2x}{:0>2x}", red, green, blue)
} else {
dbg!("hi");
format!("rgba({}, {}, {}, {})", red, green, blue, alpha)
};
Color {
red,
green,
blue,
alpha,
repr,
}
}
pub fn from_values(red: u16, green: u16, blue: u16, alpha: Number) -> Self { pub fn from_values(red: u16, green: u16, blue: u16, alpha: Number) -> Self {
let repr = if alpha >= Number::from(1) { let repr = if alpha >= Number::from(1) {
format!("#{:0>2x}{:0>2x}{:0>2x}", red, green, blue) format!("#{:0>2x}{:0>2x}{:0>2x}", red, green, blue)

View File

@ -1,6 +1,6 @@
use std::convert::From; use std::convert::From;
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use std::ops::{Add, Div, Mul, Sub}; use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign};
use num_bigint::BigInt; use num_bigint::BigInt;
use num_rational::BigRational; use num_rational::BigRational;
@ -20,6 +20,22 @@ impl Number {
pub fn to_integer(&self) -> BigInt { pub fn to_integer(&self) -> BigInt {
self.val.to_integer() self.val.to_integer()
} }
pub fn ratio<A: Into<BigInt>, B: Into<BigInt>>(a: A, b: B) -> Self {
Number::new(BigRational::new(a.into(), b.into()))
}
pub fn round(self) -> Self {
Number {
val: self.val.round(),
}
}
}
impl fmt::LowerHex for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:0>2x}", self.val.to_integer())
}
} }
impl From<BigInt> for Number { impl From<BigInt> for Number {
@ -84,6 +100,12 @@ impl Add for Number {
} }
} }
impl AddAssign for Number {
fn add_assign(&mut self, other: Self) {
self.val += other.val
}
}
impl Sub for Number { impl Sub for Number {
type Output = Self; type Output = Self;
@ -94,6 +116,12 @@ impl Sub for Number {
} }
} }
impl SubAssign for Number {
fn sub_assign(&mut self, other: Self) {
self.val -= other.val
}
}
impl Mul for Number { impl Mul for Number {
type Output = Self; type Output = Self;

View File

@ -76,3 +76,23 @@ test!(
"a {\n color: rgba(1, 2, 3, 50%);\n}\n", "a {\n color: rgba(1, 2, 3, 50%);\n}\n",
"a {\n color: rgba(1, 2, 3, 0.5);\n}\n" "a {\n color: rgba(1, 2, 3, 0.5);\n}\n"
); );
test!(
hsl_basic,
"a {\n color: hsl(193, 67%, 99);\n}\n",
"a {\n color: #fbfdfe;\n}\n"
);
test!(
hsla_basic,
"a {\n color: hsla(193, 67%, 99, .6);\n}\n",
"a {\n color: rgba(251, 253, 254, 0.6);\n}\n"
);
test!(
hsl_named,
"a {\n color: hsl($hue: 193, $saturation: 67%, $luminance: 99);\n}\n",
"a {\n color: #fbfdfe;\n}\n"
);
test!(
hsla_named,
"a {\n color: hsla($hue: 193, $saturation: 67%, $luminance: 99, $alpha: .6);\n}\n",
"a {\n color: rgba(251, 253, 254, 0.6);\n}\n"
);