Implement builtin functions hue(), saturation(), and lightness()

This commit is contained in:
ConnorSkees 2020-02-09 12:18:41 -05:00
parent 835fe61bb4
commit 8638e2f251
5 changed files with 122 additions and 13 deletions

View File

@ -1,7 +1,5 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use num_bigint::BigInt;
use super::Builtin; use super::Builtin;
use crate::color::Color; use crate::color::Color;
use crate::common::QuoteKind; use crate::common::QuoteKind;
@ -104,23 +102,41 @@ pub(crate) fn register(f: &mut BTreeMap<String, Builtin>) {
Some(Value::Color(Color::from_hsla(hue, saturation, luminance, 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, "color") {
Value::Color(c) => Some(Value::Dimension(Number::from(BigInt::from(c.red())), Unit::None)), Value::Color(c) => Some(Value::Dimension(Number::from(c.red()), Unit::None)),
_ => todo!("non-color given to builtin function `red()`") _ => todo!("non-color given to builtin function `red()`")
} }
}); });
decl!(f "green", |args, _| { decl!(f "green", |args, _| {
match arg!(args, 0, "green") { match arg!(args, 0, "color") {
Value::Color(c) => Some(Value::Dimension(Number::from(BigInt::from(c.green())), Unit::None)), Value::Color(c) => Some(Value::Dimension(Number::from(c.green()), Unit::None)),
_ => todo!("non-color given to builtin function `green()`") _ => todo!("non-color given to builtin function `green()`")
} }
}); });
decl!(f "blue", |args, _| { decl!(f "blue", |args, _| {
match arg!(args, 0, "blue") { match arg!(args, 0, "color") {
Value::Color(c) => Some(Value::Dimension(Number::from(BigInt::from(c.blue())), Unit::None)), Value::Color(c) => Some(Value::Dimension(Number::from(c.blue()), Unit::None)),
_ => todo!("non-color given to builtin function `blue()`") _ => todo!("non-color given to builtin function `blue()`")
} }
}); });
decl!(f "hue", |args, _| {
match arg!(args, 0, "color") {
Value::Color(c) => Some(Value::Dimension(c.hue(), Unit::Deg)),
_ => todo!("non-color given to builtin function `hue()`")
}
});
decl!(f "saturation", |args, _| {
match arg!(args, 0, "color") {
Value::Color(c) => Some(Value::Dimension(c.saturation(), Unit::Percent)),
_ => todo!("non-color given to builtin function `saturation()`")
}
});
decl!(f "lightness", |args, _| {
match arg!(args, 0, "color") {
Value::Color(c) => Some(Value::Dimension(c.lightness(), Unit::Percent)),
_ => todo!("non-color given to builtin function `lightness()`")
}
});
decl!(f "opacity", |args, _| { decl!(f "opacity", |args, _| {
match arg!(args, 0, "color") { match arg!(args, 0, "color") {
Value::Color(c) => Some(Value::Dimension(c.alpha() / Number::from(255), Unit::None)), Value::Color(c) => Some(Value::Dimension(c.alpha() / Number::from(255), Unit::None)),

View File

@ -40,6 +40,68 @@ impl Color {
self.green self.green
} }
/// Calculate hue from RGBA values
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
pub fn hue(&self) -> Number {
let red = Number::ratio(self.red, 255);
let green = Number::ratio(self.green, 255);
let blue = Number::ratio(self.blue, 255);
let min = red.clone().min(green.clone().min(blue.clone()));
let max = red.clone().max(green.clone().max(blue.clone()));
if &min == &max {
return Number::from(0);
}
let mut hue = if &red == &max {
(green - blue) / (max - min)
} else if &green == &max {
Number::from(2) + (blue - red) / (max - min)
} else {
Number::from(4) + (red - green) / (max - min)
};
if hue < Number::from(0) {
hue += Number::from(360);
}
(hue * Number::from(60)).round()
}
/// Calculate saturation from RGBA values
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
pub fn saturation(&self) -> Number {
let red = Number::ratio(self.red, 255);
let green = Number::ratio(self.green, 255);
let blue = Number::ratio(self.blue, 255);
let mut min = red.clone().min(green.clone().min(blue.clone()));
min = Number::ratio((min * Number::from(100)).to_integer(), 100);
let mut max = red.max(green.max(blue));
max = Number::ratio((max * Number::from(100)).to_integer(), 100);
let luminance = (min.clone() + max.clone()) / Number::from(2);
if &min == &max {
return Number::from(0);
}
let saturation = if luminance < Number::ratio(1, 2) {
(max.clone() - min.clone()) / (max + min)
} else {
(max.clone() - min.clone()) / (Number::from(2) - max - min)
} * Number::from(100);
saturation.round()
}
/// Calculate luminance from RGBA values
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
pub fn lightness(&self) -> Number {
let red = Number::ratio(self.red, 255);
let green = Number::ratio(self.green, 255);
let blue = Number::ratio(self.blue, 255);
let min = red.clone().min(green.clone().min(blue.clone()));
let max = red.max(green.max(blue));
(((min + max) / Number::from(2)) * Number::from(100)).round()
}
pub fn alpha(&self) -> Number { pub fn alpha(&self) -> Number {
self.alpha.clone() self.alpha.clone()
} }
@ -77,7 +139,7 @@ impl Color {
luminance.clone() + saturation.clone() - luminance.clone() * saturation.clone() luminance.clone() + saturation.clone() - luminance.clone() * saturation.clone()
}; };
let temporary_2 = Number::from(2) * luminance.clone() - temporary_1.clone(); let temporary_2 = Number::from(2) * luminance.clone() - temporary_1.clone();
hue = hue / Number::from(360); hue /= Number::from(360);
let mut temporary_r = hue.clone() + Number::ratio(1, 3); let mut temporary_r = hue.clone() + Number::ratio(1, 3);
let mut temporary_g = hue.clone(); let mut temporary_g = hue.clone();
let mut temporary_b = hue.clone() - Number::ratio(1, 3); let mut temporary_b = hue.clone() - Number::ratio(1, 3);

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, AddAssign, Div, Mul, Sub, SubAssign}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
use num_bigint::BigInt; use num_bigint::BigInt;
use num_rational::BigRational; use num_rational::BigRational;
@ -132,6 +132,12 @@ impl Mul for Number {
} }
} }
impl MulAssign for Number {
fn mul_assign(&mut self, other: Self) {
self.val *= other.val
}
}
impl Div for Number { impl Div for Number {
type Output = Self; type Output = Self;
@ -141,3 +147,9 @@ impl Div for Number {
} }
} }
} }
impl DivAssign for Number {
fn div_assign(&mut self, other: Self) {
self.val /= other.val
}
}

View File

@ -31,7 +31,7 @@ impl Add for Value {
Self::Null => Value::Ident(c.to_string(), QuoteKind::None), Self::Null => Value::Ident(c.to_string(), QuoteKind::None),
Self::Color(..) => todo!("figure out if it's possible to add colors"), Self::Color(..) => todo!("figure out if it's possible to add colors"),
_ => Value::Ident(format!("{}{}", c, other), QuoteKind::None), _ => Value::Ident(format!("{}{}", c, other), QuoteKind::None),
} },
// Self::BinaryOp(..) => todo!(), // Self::BinaryOp(..) => todo!(),
// Self::Paren(..) => todo!(), // Self::Paren(..) => todo!(),
Self::Ident(s1, quotes1) => match other { Self::Ident(s1, quotes1) => match other {
@ -97,7 +97,7 @@ impl Sub for Value {
} }
Self::Null => Value::Ident(format!("{}-", c), QuoteKind::None), Self::Null => Value::Ident(format!("{}-", c), QuoteKind::None),
Self::Dimension(..) => todo!("investigate adding numbers and colors"), Self::Dimension(..) => todo!("investigate adding numbers and colors"),
_ => Value::Ident(format!("{}-{}", c, other), QuoteKind::None) _ => Value::Ident(format!("{}-{}", c, other), QuoteKind::None),
}, },
// Self::BinaryOp(..) => todo!(), // Self::BinaryOp(..) => todo!(),
// Self::Paren(..) => todo!(), // Self::Paren(..) => todo!(),
@ -116,7 +116,11 @@ impl Sub for Value {
QuoteKind::None, QuoteKind::None,
) )
} }
Self::Important | Self::True | Self::False | Self::Dimension(..) | Self::Color(..) => { Self::Important
| Self::True
| Self::False
| Self::Dimension(..)
| Self::Color(..) => {
let quotes = match quotes1 { let quotes = match quotes1 {
QuoteKind::Double | QuoteKind::Single => QuoteKind::Double, QuoteKind::Double | QuoteKind::Single => QuoteKind::Double,
QuoteKind::None => QuoteKind::None, QuoteKind::None => QuoteKind::None,

View File

@ -156,3 +156,18 @@ test!(
"a {\n color: foo - red;\n}\n", "a {\n color: foo - red;\n}\n",
"a {\n color: foo-red;\n}\n" "a {\n color: foo-red;\n}\n"
); );
test!(
hue,
"$a: hsl(193, 67%, 28%);\n\na {\n color: hue($a);\n}\n",
"a {\n color: 193deg;\n}\n"
);
test!(
saturation,
"$a: hsl(193, 67%, 28%);\n\na {\n color: saturation($a);\n}\n",
"a {\n color: 67%;\n}\n"
);
test!(
lightness,
"$a: hsl(193, 67%, 28%);\n\na {\n color: lightness($a);\n}\n",
"a {\n color: 28%;\n}\n"
);