Implement builtin functions hue()
, saturation()
, and lightness()
This commit is contained in:
parent
835fe61bb4
commit
8638e2f251
@ -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)),
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user