Implement builtin function mix()
This commit is contained in:
parent
c04f83ddcf
commit
af95658953
@ -67,4 +67,4 @@ pub(crate) fn register(f: &mut BTreeMap<String, Builtin>) {
|
|||||||
};
|
};
|
||||||
Some(Value::Color(color.fade_out(amount)))
|
Some(Value::Color(color.fade_out(amount)))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -119,4 +119,22 @@ pub(crate) fn register(f: &mut BTreeMap<String, Builtin>) {
|
|||||||
_ => todo!("non-color given to builtin function `blue()`")
|
_ => todo!("non-color given to builtin function `blue()`")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
decl!(f "mix", |args, _| {
|
||||||
|
let color1 = match arg!(args, 0, "color1").eval() {
|
||||||
|
Value::Color(c) => c,
|
||||||
|
_ => todo!("non-color given to builtin function `mix()`")
|
||||||
|
};
|
||||||
|
|
||||||
|
let color2 = match arg!(args, 1, "color2").eval() {
|
||||||
|
Value::Color(c) => c,
|
||||||
|
_ => todo!("non-color given to builtin function `mix()`")
|
||||||
|
};
|
||||||
|
|
||||||
|
let weight = match arg!(args, 2, "weight"=Value::Dimension(Number::ratio(1, 2), Unit::None)) {
|
||||||
|
Value::Dimension(n, Unit::None) => n,
|
||||||
|
Value::Dimension(n, Unit::Percent) => n / Number::from(100),
|
||||||
|
_ => todo!("expected either unitless or % number for $weight")
|
||||||
|
};
|
||||||
|
Some(Value::Color(color1.mix(color2, weight)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -8,6 +8,18 @@ use num_traits::cast::ToPrimitive;
|
|||||||
|
|
||||||
mod name;
|
mod name;
|
||||||
|
|
||||||
|
macro_rules! clamp {
|
||||||
|
($c:expr, $min:literal, $max:literal) => {
|
||||||
|
if $c > Number::from($max) {
|
||||||
|
Number::from($max)
|
||||||
|
} else if $c < Number::from($min) {
|
||||||
|
Number::from($min)
|
||||||
|
} else {
|
||||||
|
$c
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub(crate) struct Color {
|
pub(crate) struct Color {
|
||||||
red: Number,
|
red: Number,
|
||||||
@ -77,6 +89,32 @@ impl Color {
|
|||||||
pub fn green(&self) -> Number {
|
pub fn green(&self) -> Number {
|
||||||
self.green.clone()
|
self.green.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mix two colors together with weight
|
||||||
|
/// Algorithm adapted from
|
||||||
|
/// <https://github.com/sass/dart-sass/blob/0d0270cb12a9ac5cce73a4d0785fecb00735feee/lib/src/functions/color.dart#L718>
|
||||||
|
pub fn mix(self, other: Color, weight: Number) -> Self {
|
||||||
|
let weight = clamp!(weight, 0, 100);
|
||||||
|
let normalized_weight = weight.clone() * Number::from(2) - Number::from(1);
|
||||||
|
let alpha_distance = self.alpha.clone() - other.alpha.clone();
|
||||||
|
|
||||||
|
let combined_weight1 =
|
||||||
|
if normalized_weight.clone() * alpha_distance.clone() == Number::from(-1) {
|
||||||
|
normalized_weight
|
||||||
|
} else {
|
||||||
|
(normalized_weight.clone() + alpha_distance.clone())
|
||||||
|
/ (Number::from(1) + normalized_weight * alpha_distance)
|
||||||
|
};
|
||||||
|
let weight1 = (combined_weight1 + Number::from(1)) / Number::from(2);
|
||||||
|
let weight2 = Number::from(1) - weight1.clone();
|
||||||
|
|
||||||
|
Color::from_rgba(
|
||||||
|
self.red * weight1.clone() + other.red * weight2.clone(),
|
||||||
|
self.green * weight1.clone() + other.green * weight2.clone(),
|
||||||
|
self.blue * weight1.clone() + other.blue * weight2,
|
||||||
|
self.alpha * weight.clone() + other.alpha * (Number::from(1) - weight),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HSLA color functions
|
/// HSLA color functions
|
||||||
@ -207,26 +245,11 @@ impl Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create RGBA representation from HSLA values
|
/// Create RGBA representation from HSLA values
|
||||||
pub fn from_hsla(
|
pub fn from_hsla(hue: Number, saturation: Number, luminance: Number, alpha: Number) -> Self {
|
||||||
mut hue: Number,
|
let mut hue = clamp!(hue, 0, 360);
|
||||||
mut saturation: Number,
|
let saturation = clamp!(saturation, 0, 1);
|
||||||
mut luminance: Number,
|
let luminance = clamp!(luminance, 0, 1);
|
||||||
mut alpha: Number,
|
let alpha = clamp!(alpha, 0, 1);
|
||||||
) -> Self {
|
|
||||||
macro_rules! clamp {
|
|
||||||
($c:ident, $min:literal, $max:literal) => {
|
|
||||||
if $c > Number::from($max) {
|
|
||||||
$c = Number::from($max)
|
|
||||||
} else if $c < Number::from($min) {
|
|
||||||
$c = Number::from($min)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
clamp!(hue, 0, 360);
|
|
||||||
clamp!(saturation, 0, 1);
|
|
||||||
clamp!(luminance, 0, 1);
|
|
||||||
clamp!(alpha, 0, 1);
|
|
||||||
|
|
||||||
if saturation.clone() == Number::from(0) {
|
if saturation.clone() == Number::from(0) {
|
||||||
let luminance = if luminance > Number::from(100) {
|
let luminance = if luminance > Number::from(100) {
|
||||||
|
@ -356,3 +356,23 @@ test!(
|
|||||||
"a {\n color: complement(red);\n}\n",
|
"a {\n color: complement(red);\n}\n",
|
||||||
"a {\n color: aqua;\n}\n"
|
"a {\n color: aqua;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
mix_no_weight,
|
||||||
|
"a {\n color: mix(#f00, #00f);\n}\n",
|
||||||
|
"a {\n color: purple;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
mix_weight_25,
|
||||||
|
"a {\n color: mix(#f00, #00f, 25%);\n}\n",
|
||||||
|
"a {\n color: #4000bf;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
mix_opacity,
|
||||||
|
"a {\n color: mix(rgba(255, 0, 0, 0.5), #00f);\n}\n",
|
||||||
|
"a {\n color: rgba(64, 0, 191, 0.75);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
mix_sanity_check,
|
||||||
|
"a {\n color: mix(black, white);\n}\n",
|
||||||
|
"a {\n color: gray;\n}\n"
|
||||||
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user