Use Number as to represent color channels so precision is not lost
This commit is contained in:
parent
0ec2c46744
commit
f6b27177ba
132
src/color/mod.rs
132
src/color/mod.rs
@ -10,9 +10,9 @@ mod name;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub(crate) struct Color {
|
pub(crate) struct Color {
|
||||||
red: u8,
|
red: Number,
|
||||||
green: u8,
|
green: Number,
|
||||||
blue: u8,
|
blue: Number,
|
||||||
alpha: Number,
|
alpha: Number,
|
||||||
repr: String,
|
repr: String,
|
||||||
}
|
}
|
||||||
@ -20,32 +20,32 @@ pub(crate) struct Color {
|
|||||||
impl Color {
|
impl Color {
|
||||||
pub fn new(red: u8, green: u8, blue: u8, alpha: u8, repr: String) -> Self {
|
pub fn new(red: u8, green: u8, blue: u8, alpha: u8, repr: String) -> Self {
|
||||||
Color {
|
Color {
|
||||||
red,
|
red: red.into(),
|
||||||
green,
|
green: green.into(),
|
||||||
blue,
|
blue: blue.into(),
|
||||||
alpha: alpha.into(),
|
alpha: alpha.into(),
|
||||||
repr,
|
repr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn red(&self) -> u8 {
|
pub fn red(&self) -> Number {
|
||||||
self.red
|
self.red.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn blue(&self) -> u8 {
|
pub fn blue(&self) -> Number {
|
||||||
self.blue
|
self.blue.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn green(&self) -> u8 {
|
pub fn green(&self) -> Number {
|
||||||
self.green
|
self.green.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate hue from RGBA values
|
/// Calculate hue from RGBA values
|
||||||
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||||
pub fn hue(&self) -> Number {
|
pub fn hue(&self) -> Number {
|
||||||
let red = Number::ratio(self.red, 255);
|
let red = self.red.clone() / Number::from(255);
|
||||||
let green = Number::ratio(self.green, 255);
|
let green = self.green.clone() / Number::from(255);
|
||||||
let blue = Number::ratio(self.blue, 255);
|
let blue = self.blue.clone() / Number::from(255);
|
||||||
let min = red.clone().min(green.clone().min(blue.clone()));
|
let min = red.clone().min(green.clone().min(blue.clone()));
|
||||||
let max = red.clone().max(green.clone().max(blue.clone()));
|
let max = red.clone().max(green.clone().max(blue.clone()));
|
||||||
if &min == &max {
|
if &min == &max {
|
||||||
@ -70,33 +70,29 @@ impl Color {
|
|||||||
/// Calculate saturation from RGBA values
|
/// Calculate saturation from RGBA values
|
||||||
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||||
pub fn saturation(&self) -> Number {
|
pub fn saturation(&self) -> Number {
|
||||||
let red = Number::ratio(self.red, 255);
|
let red = self.red.clone() / Number::from(255);
|
||||||
let green = Number::ratio(self.green, 255);
|
let green = self.green.clone() / Number::from(255);
|
||||||
let blue = Number::ratio(self.blue, 255);
|
let blue = self.blue.clone() / Number::from(255);
|
||||||
let mut min = red.clone().min(green.clone().min(blue.clone()));
|
|
||||||
min = Number::ratio((min * Number::from(100)).to_integer(), 100);
|
let min = red.clone().min(green.clone().min(blue.clone()));
|
||||||
let mut max = red.max(green.max(blue));
|
let 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 {
|
if &min == &max {
|
||||||
return Number::from(0);
|
return Number::from(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let saturation = if luminance < Number::ratio(1, 2) {
|
let d = max.clone() - min.clone();
|
||||||
(max.clone() - min.clone()) / (max + min)
|
let mm = max + min;
|
||||||
} else {
|
let s = d / if mm > Number::from(1) { Number::from(2) - mm } else { mm };
|
||||||
(max.clone() - min.clone()) / (Number::from(2) - max - min)
|
(s * Number::from(100)).round()
|
||||||
} * Number::from(100);
|
|
||||||
|
|
||||||
saturation.round()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate luminance from RGBA values
|
/// Calculate luminance from RGBA values
|
||||||
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
/// Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||||
pub fn lightness(&self) -> Number {
|
pub fn lightness(&self) -> Number {
|
||||||
let red = Number::ratio(self.red, 255);
|
let red = self.red.clone() / Number::from(255);
|
||||||
let green = Number::ratio(self.green, 255);
|
let green = self.green.clone() / Number::from(255);
|
||||||
let blue = Number::ratio(self.blue, 255);
|
let blue = self.blue.clone() / Number::from(255);
|
||||||
let min = red.clone().min(green.clone().min(blue.clone()));
|
let min = red.clone().min(green.clone().min(blue.clone()));
|
||||||
let max = red.max(green.max(blue));
|
let max = red.max(green.max(blue));
|
||||||
(((min + max) / Number::from(2)) * Number::from(100)).round()
|
(((min + max) / Number::from(2)) * Number::from(100)).round()
|
||||||
@ -114,7 +110,6 @@ impl Color {
|
|||||||
mut luminance: Number,
|
mut luminance: Number,
|
||||||
mut alpha: Number,
|
mut alpha: Number,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
||||||
macro_rules! clamp {
|
macro_rules! clamp {
|
||||||
($c:ident, $min:literal, $max:literal) => {
|
($c:ident, $min:literal, $max:literal) => {
|
||||||
if $c > Number::from($max) {
|
if $c > Number::from($max) {
|
||||||
@ -122,7 +117,7 @@ impl Color {
|
|||||||
} else if $c < Number::from($min) {
|
} else if $c < Number::from($min) {
|
||||||
$c = Number::from($min)
|
$c = Number::from($min)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clamp!(hue, 0, 360);
|
clamp!(hue, 0, 360);
|
||||||
@ -136,14 +131,11 @@ impl Color {
|
|||||||
} else {
|
} else {
|
||||||
luminance
|
luminance
|
||||||
};
|
};
|
||||||
let val = (luminance.clone() * Number::from(255))
|
let val = luminance.clone() * Number::from(255);
|
||||||
.to_integer()
|
let repr = repr(&val, &val, &val, &alpha);
|
||||||
.to_u8()
|
|
||||||
.unwrap();
|
|
||||||
let repr = repr(val, val, val, &alpha);
|
|
||||||
return Color {
|
return Color {
|
||||||
red: val,
|
red: val.clone(),
|
||||||
green: val,
|
green: val.clone(),
|
||||||
blue: val,
|
blue: val,
|
||||||
alpha: Number::from(alpha),
|
alpha: Number::from(alpha),
|
||||||
repr,
|
repr,
|
||||||
@ -176,7 +168,7 @@ impl Color {
|
|||||||
|
|
||||||
macro_rules! channel {
|
macro_rules! channel {
|
||||||
($name:ident, $temp:ident, $temp1:ident, $temp2:ident) => {
|
($name:ident, $temp:ident, $temp1:ident, $temp2:ident) => {
|
||||||
let $name = (if Number::from(6) * $temp.clone() < Number::from(1) {
|
let $name = if Number::from(6) * $temp.clone() < Number::from(1) {
|
||||||
$temp2.clone()
|
$temp2.clone()
|
||||||
+ ($temp1.clone() - $temp2.clone()) * Number::from(6) * $temp.clone()
|
+ ($temp1.clone() - $temp2.clone()) * Number::from(6) * $temp.clone()
|
||||||
} else if Number::from(2) * $temp.clone() < Number::from(1) {
|
} else if Number::from(2) * $temp.clone() < Number::from(1) {
|
||||||
@ -188,11 +180,7 @@ impl Color {
|
|||||||
* Number::from(6)
|
* Number::from(6)
|
||||||
} else {
|
} else {
|
||||||
$temp2.clone()
|
$temp2.clone()
|
||||||
} * Number::from(255))
|
} * Number::from(255);
|
||||||
.round()
|
|
||||||
.to_integer()
|
|
||||||
.to_u8()
|
|
||||||
.expect("expected channel to fit inside u8");
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +188,7 @@ impl Color {
|
|||||||
channel!(green, temporary_g, temporary_1, temporary_2);
|
channel!(green, temporary_g, temporary_1, temporary_2);
|
||||||
channel!(blue, temporary_b, temporary_1, temporary_2);
|
channel!(blue, temporary_b, temporary_1, temporary_2);
|
||||||
|
|
||||||
let repr = repr(red, green, blue, &alpha);
|
let repr = repr(&red, &green, &blue, &alpha);
|
||||||
Color {
|
Color {
|
||||||
red,
|
red,
|
||||||
green,
|
green,
|
||||||
@ -214,11 +202,11 @@ impl Color {
|
|||||||
macro_rules! clamp {
|
macro_rules! clamp {
|
||||||
($channel:ident) => {
|
($channel:ident) => {
|
||||||
let $channel = if $channel > Number::from(255) {
|
let $channel = if $channel > Number::from(255) {
|
||||||
255_u8
|
Number::from(255)
|
||||||
} else if $channel < Number::from(0) {
|
} else if $channel < Number::from(0) {
|
||||||
0_u8
|
Number::from(0)
|
||||||
} else {
|
} else {
|
||||||
$channel.round().to_integer().to_u8().unwrap()
|
$channel
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -235,7 +223,7 @@ impl Color {
|
|||||||
alpha
|
alpha
|
||||||
};
|
};
|
||||||
|
|
||||||
let repr = repr(red, green, blue, &alpha);
|
let repr = repr(&red, &green, &blue, &alpha);
|
||||||
Color {
|
Color {
|
||||||
red,
|
red,
|
||||||
green,
|
green,
|
||||||
@ -253,22 +241,10 @@ impl Color {
|
|||||||
} else {
|
} else {
|
||||||
weight
|
weight
|
||||||
};
|
};
|
||||||
let red = (Number::from(std::u8::MAX) - (Number::from(self.red) * weight.clone()))
|
let red = Number::from(std::u8::MAX) - (Number::from(self.red.clone()) * weight.clone());
|
||||||
.round()
|
let green = Number::from(std::u8::MAX) - (Number::from(self.green.clone()) * weight.clone());
|
||||||
.to_integer()
|
let blue = Number::from(std::u8::MAX) - (Number::from(self.blue.clone()) * weight);
|
||||||
.to_u8()
|
let repr = repr(&red, &green, &blue, &self.alpha);
|
||||||
.unwrap();
|
|
||||||
let green = (Number::from(std::u8::MAX) - (Number::from(self.green) * weight.clone()))
|
|
||||||
.round()
|
|
||||||
.to_integer()
|
|
||||||
.to_u8()
|
|
||||||
.unwrap();
|
|
||||||
let blue = (Number::from(std::u8::MAX) - (Number::from(self.blue) * weight))
|
|
||||||
.round()
|
|
||||||
.to_integer()
|
|
||||||
.to_u8()
|
|
||||||
.unwrap();
|
|
||||||
let repr = repr(red, green, blue, &self.alpha);
|
|
||||||
Color {
|
Color {
|
||||||
red,
|
red,
|
||||||
green,
|
green,
|
||||||
@ -280,7 +256,23 @@ impl Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the proper representation from RGBA values
|
/// Get the proper representation from RGBA values
|
||||||
fn repr(red: u8, green: u8, blue: u8, alpha: &Number) -> String {
|
fn repr(red: &Number, green: &Number, blue: &Number, alpha: &Number) -> String {
|
||||||
|
macro_rules! into_u8 {
|
||||||
|
($channel:ident) => {
|
||||||
|
let $channel = if $channel > &Number::from(255) {
|
||||||
|
255_u8
|
||||||
|
} else if $channel < &Number::from(0) {
|
||||||
|
0_u8
|
||||||
|
} else {
|
||||||
|
$channel.clone().round().to_integer().to_u8().unwrap()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
into_u8!(red);
|
||||||
|
into_u8!(green);
|
||||||
|
into_u8!(blue);
|
||||||
|
|
||||||
if alpha < &Number::from(1) {
|
if alpha < &Number::from(1) {
|
||||||
format!("rgba({}, {}, {}, {})", red, green, blue, alpha)
|
format!("rgba({}, {}, {}, {})", red, green, blue, alpha)
|
||||||
} else if let Ok(c) = ColorName::try_from([red, green, blue]) {
|
} else if let Ok(c) = ColorName::try_from([red, green, blue]) {
|
||||||
|
@ -171,6 +171,11 @@ test!(
|
|||||||
"$a: hsl(193, 67%, 28%);\n\na {\n color: saturation($a);\n}\n",
|
"$a: hsl(193, 67%, 28%);\n\na {\n color: saturation($a);\n}\n",
|
||||||
"a {\n color: 67%;\n}\n"
|
"a {\n color: 67%;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
saturation_2,
|
||||||
|
"$a: hsl(1, 1, 10);\n\na {\n color: saturation($a);\n}\n",
|
||||||
|
"a {\n color: 1%;\n}\n"
|
||||||
|
);
|
||||||
test!(
|
test!(
|
||||||
lightness,
|
lightness,
|
||||||
"$a: hsl(193, 67%, 28%);\n\na {\n color: lightness($a);\n}\n",
|
"$a: hsl(193, 67%, 28%);\n\na {\n color: lightness($a);\n}\n",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user