fn normal_xy(x: f64, y: f64) -> f64 {
let mut v = (x * 12.9898 + y * 78.233).sin() * 43758.5453;
v = v - v.floor();
(v - 0.5) * 2.0 }
use image::{DynamicImage, GenericImageView};
use num_traits::AsPrimitive;
use palette::{
rgb::{Rgb, Rgba},
Alpha, FromColor, Hsl, Hsla, Hsluv, Hsluva, Hsv, Hsva, Hwb, Hwba, IntoColor, Lab, Laba, Lch,
Lcha, Lighten, Mix, Okhsl, Okhsla, Okhsv, Okhsva, Saturate, ShiftHue, Srgb, Srgba, Xyz, Xyza,
};
use rand::{Rng, RngCore};
use rand_distr::{Distribution, Normal};
use tiny_skia::{Color, Point};
pub trait ConvertColor {
fn to_color(&self) -> Color;
fn from_color(color: &Color) -> Self;
}
impl ConvertColor for Color {
fn to_color(&self) -> Color {
*self
}
fn from_color(color: &Color) -> Self {
*color
}
}
impl ConvertColor for Srgb {
fn to_color(&self) -> Color {
Color::from_rgba(self.red, self.green, self.blue, 1.0).unwrap()
}
fn from_color(color: &Color) -> Self {
let (r, g, b, _) = color.as_f32s();
Rgb::new(r, g, b)
}
}
impl ConvertColor for Srgba {
fn to_color(&self) -> Color {
Color::from_rgba(self.red, self.green, self.blue, self.alpha).unwrap()
}
fn from_color(color: &Color) -> Self {
let (r, g, b, a) = color.as_f32s();
Rgba::new(r, g, b, a)
}
}
macro_rules! convert_color {
($c:ty) => {
impl ConvertColor for $c {
fn to_color(&self) -> Color {
let srgb =
<palette::rgb::Srgb as FromColor<$c>>::from_color(*self).into_components();
Color::from_rgba(srgb.0, srgb.1, srgb.2, 1.0).unwrap()
}
fn from_color(color: &Color) -> $c {
let (r, g, b, _) = color.as_f32s();
let srgb: Alpha<Rgb, f32> = Rgba::new(r, g, b, 1.0);
srgb.into_color()
}
}
};
}
macro_rules! convert_color_alpha {
($c:ty) => {
impl ConvertColor for $c {
fn to_color(&self) -> Color {
let srgba =
<palette::rgb::Srgba as FromColor<$c>>::from_color(*self).into_components();
Color::from_rgba(srgba.0, srgba.1, srgba.2, srgba.3).unwrap()
}
fn from_color(color: &Color) -> Self {
let (r, g, b, a) = color.as_f32s();
let srgb: Alpha<Rgb, f32> = Rgba::new(r, g, b, a);
srgb.into_color()
}
}
};
}
convert_color!(Hsluv);
convert_color_alpha!(Hsluva);
convert_color!(Hsl);
convert_color_alpha!(Hsla);
convert_color!(Lch);
convert_color_alpha!(Lcha);
convert_color!(Lab);
convert_color_alpha!(Laba);
convert_color!(Xyz);
convert_color_alpha!(Xyza);
convert_color!(Hsv);
convert_color_alpha!(Hsva);
convert_color!(Hwb);
convert_color_alpha!(Hwba);
convert_color!(Okhsl);
convert_color_alpha!(Okhsla);
convert_color!(Okhsv);
convert_color_alpha!(Okhsva);
impl ConvertColor for image::Rgb<u8> {
fn to_color(&self) -> Color {
rgb8(self.0[0], self.0[1], self.0[2])
}
fn from_color(color: &Color) -> image::Rgb<u8> {
let r = color.red() * 255.0 + 0.5;
let g = color.green() * 255.0 + 0.5;
let b = color.blue() * 255.0 + 0.5;
image::Rgb([r as u8, g as u8, b as u8])
}
}
impl ConvertColor for image::Rgba<u8> {
fn to_color(&self) -> Color {
Color::from_rgba8(self.0[0], self.0[1], self.0[2], self.0[3])
}
fn from_color(color: &Color) -> image::Rgba<u8> {
let r = color.red() * 255.0 + 0.5;
let g = color.green() * 255.0 + 0.5;
let b = color.blue() * 255.0 + 0.5;
let a = color.alpha() * 255.0 + 0.5;
image::Rgba([r as u8, g as u8, b as u8, a as u8])
}
}
pub struct ColorMapping {
pub red_fn: Box<dyn Fn(f32) -> f32>,
pub green_fn: Box<dyn Fn(f32) -> f32>,
pub blue_fn: Box<dyn Fn(f32) -> f32>,
pub color_grad: Box<dyn Fn(Point) -> f32>,
}
impl ColorMapping {
pub fn new(
red_fn: Box<dyn Fn(f32) -> f32>,
green_fn: Box<dyn Fn(f32) -> f32>,
blue_fn: Box<dyn Fn(f32) -> f32>,
color_grad: Box<dyn Fn(Point) -> f32>,
) -> Self {
Self {
red_fn,
green_fn,
blue_fn,
color_grad,
}
}
pub fn get(&self, x: f32, y: f32) -> Color {
let t = (self.color_grad)(Point::from_xy(x, y));
let r = (self.red_fn)(t);
let g = (self.green_fn)(t);
let b = (self.blue_fn)(t);
Color::from_rgba(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0), 1.0).unwrap()
}
}
pub trait Colorful {
fn opacity(&self, alpha: f32) -> Self;
fn as_f32s(&self) -> (f32, f32, f32, f32);
fn as_u8s(&self) -> (u8, u8, u8, u8);
fn lerp(&self, color2: &Self, t: f32) -> Self;
fn jiggle_xy(&self, x: u32, y: u32, mean: f32, std: f32) -> Self;
fn jiggle_xy_lightness(&self, x: u32, y: u32, mean: f32, std: f32) -> Self;
fn jiggle_xy_saturation(&self, x: u32, y: u32, mean: f32, std: f32) -> Self;
fn jiggle_xy_hue(&self, x: u32, y: u32, mean: f32, std: f32) -> Self;
fn grayscale(&self) -> Self;
fn rotate_hue(&self, degrees: f32) -> Self;
fn tighten(&self) -> Self;
fn spread(&self) -> Self;
fn tint(&self, t: f32) -> Self;
fn tone(&self, t: f32) -> Self;
fn shade(&self, t: f32) -> Self;
fn lighten(&self, factor: f32) -> Self;
fn lighten_fixed(&self, amount: f32) -> Self;
fn darken(&self, factor: f32) -> Self
where
Self: Sized,
{
self.lighten(-factor)
}
fn darken_fixed(&self, amount: f32) -> Self
where
Self: Sized,
{
self.lighten_fixed(-amount)
}
fn saturate(&self, factor: f32) -> Self;
fn saturate_fixed(&self, amount: f32) -> Self;
fn desaturate(&self, factor: f32) -> Self
where
Self: Sized,
{
self.saturate(-factor)
}
fn desaturate_fixed(&self, amount: f32) -> Self
where
Self: Sized,
{
self.saturate_fixed(-amount)
}
}
impl Colorful for Color {
fn opacity(&self, alpha: f32) -> Self {
let mut c = *self;
c.set_alpha(alpha);
c
}
fn as_f32s(&self) -> (f32, f32, f32, f32) {
(self.red(), self.green(), self.blue(), self.alpha())
}
fn as_u8s(&self) -> (u8, u8, u8, u8) {
let r = self.red() * 255.0 + 0.5;
let g = self.green() * 255.0 + 0.5;
let b = self.blue() * 255.0 + 0.5;
let a = self.alpha() * 255.0 + 0.5;
(r as u8, g as u8, b as u8, a as u8)
}
fn jiggle_xy(&self, x: u32, y: u32, mean: f32, std: f32) -> Color {
let (r, g, b, a) = self.as_f32s();
Color::from_rgba(
(r + (std * normal_xy(x as f64, y as f64) as f32 + mean)).clamp(0.0, 1.0),
(g + (std * normal_xy(x as f64 + 1.0, y as f64) as f32 + mean)).clamp(0.0, 1.0),
(b + (std * normal_xy(x as f64, y as f64 + 1.0) as f32 + mean)).clamp(0.0, 1.0),
a,
)
.unwrap()
}
fn jiggle_xy_lightness(&self, x: u32, y: u32, mean: f32, std: f32) -> Color {
let mut hsluva: Hsluva = <Hsluva as ConvertColor>::from_color(self);
hsluva.l += (std * normal_xy(x as f64, y as f64) as f32 + mean) * 100.0;
hsluva.to_color()
}
fn jiggle_xy_saturation(&self, x: u32, y: u32, mean: f32, std: f32) -> Color {
let mut hsluva: Hsluva = <Hsluva as ConvertColor>::from_color(self);
hsluva.saturation += (std * normal_xy(x as f64, y as f64) as f32 + mean) * 100.0;
hsluva.to_color()
}
fn jiggle_xy_hue(&self, x: u32, y: u32, mean: f32, std: f32) -> Color {
let mut hsluva: Hsluva = <Hsluva as ConvertColor>::from_color(self);
hsluva.hue += (std * normal_xy(x as f64, y as f64) as f32 + mean) * 360.0;
hsluva.to_color()
}
fn grayscale(&self) -> Self {
let (r, g, b, _) = self.as_f32s();
let c = (255.0 * (0.2989 * r + 0.5870 * g + 0.1140 * b)).clamp(0.0, 255.0) as u8;
rgb8(c, c, c)
}
fn rotate_hue(&self, degrees: f32) -> Color {
let okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(self);
okhsla.shift_hue(degrees).to_color()
}
fn tighten(&self) -> Color {
let mut okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(self);
let l1 = okhsla.lightness / 50.0 - 1.0;
let l2 = l1.abs() * l1.abs() * l1.signum();
let l3 = 50.0 * (l2 + 1.0);
okhsla.lightness = l3;
okhsla.to_color()
}
fn spread(&self) -> Color {
let mut okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(self);
let l1 = okhsla.lightness / 50.0 - 1.0;
let l2 = l1.abs().sqrt() * l1.signum();
let l3 = 50.0 * (l2 + 1.0);
okhsla.lightness = l3;
okhsla.to_color()
}
fn tint(&self, t: f32) -> Color {
self.lerp(&rgb8(255, 255, 255), t)
}
fn tone(&self, t: f32) -> Color {
self.lerp(&rgb8(127, 127, 127), t)
}
fn shade(&self, t: f32) -> Color {
self.lerp(&rgb8(0, 0, 0), t)
}
fn saturate(&self, factor: f32) -> Color {
let okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(self);
okhsla.saturate(factor).to_color()
}
fn saturate_fixed(&self, amount: f32) -> Self {
let okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(self);
okhsla.saturate_fixed(amount).to_color()
}
fn lighten(&self, factor: f32) -> Self {
let okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(self);
okhsla.lighten(factor).to_color()
}
fn lighten_fixed(&self, amount: f32) -> Self {
let okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(self);
okhsla.lighten_fixed(amount).to_color()
}
fn lerp(&self, color2: &Color, t: f32) -> Self {
let s = t.clamp(0.0, 1.0);
let c1 = <Srgba as ConvertColor>::from_color(self).into_linear();
let c2 = <Srgba as ConvertColor>::from_color(color2).into_linear();
Srgba::from_linear(c1.mix(c2, s)).to_color()
}
}
pub fn rgb(r: f32, g: f32, b: f32) -> Color {
Color::from_rgba(r, g, b, 1.0).unwrap_or_else(|| {
panic!(
"color components must be between 0 and 1: ({}, {}, {}",
r, g, b
)
})
}
pub fn rgb8(r: u8, g: u8, b: u8) -> Color {
Color::from_rgba8(r, g, b, 255)
}
pub fn blacks(alpha: f32) -> Color {
Color::from_rgba(0.0, 0.0, 0.0, alpha).unwrap()
}
pub fn whites(alpha: f32) -> Color {
Color::from_rgba(1.0, 1.0, 1.0, alpha).unwrap()
}
pub fn grays(n: u8) -> Color {
Color::from_rgba8(n, n, n, 255)
}
pub fn jiggle<R: RngCore>(rng: &mut R, std_dev: f32, color: Color) -> Color {
let normal = Normal::new(0.0, std_dev).unwrap();
let (r, g, b, a) = color.as_f32s();
Color::from_rgba(
(r + normal.sample(rng)).clamp(0.0, 1.0),
(g + normal.sample(rng)).clamp(0.0, 1.0),
(b + normal.sample(rng)).clamp(0.0, 1.0),
a,
)
.unwrap()
}
pub fn jiggle_lightness<R: RngCore>(rng: &mut R, std_dev: f32, color: Color) -> Color {
let normal = Normal::new(0.0, std_dev).unwrap();
let mut okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(&color);
okhsla.lightness += normal.sample(rng) * 100.0;
okhsla.to_color()
}
pub fn jiggle_saturation<R: RngCore>(rng: &mut R, std_dev: f32, color: Color) -> Color {
let normal = Normal::new(0.0, std_dev).unwrap();
let mut okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(&color);
okhsla.saturation += normal.sample(rng) * 100.0;
okhsla.to_color()
}
pub fn jiggle_hue<R: RngCore>(rng: &mut R, std_dev: f32, color: Color) -> Color {
let normal = Normal::new(0.0, std_dev).unwrap();
let mut okhsla: Okhsla = <Okhsla as ConvertColor>::from_color(&color);
okhsla.hue += normal.sample(rng) * 360.0;
okhsla.to_color()
}
pub fn rand_okhsl<R: RngCore>(rng: &mut R) -> Color {
let normal = Normal::new(0.0, 0.25).unwrap();
let h: f32 = rng.random_range(0.0..360.0);
let s: f32 = 0.65 + normal.sample(rng);
let l: f32 = 0.5 + normal.sample(rng);
Okhsl::new(h, s.clamp(0.0, 1.0), l.clamp(0.0, 1.0)).to_color()
}
pub fn rand_okhsla<R: RngCore>(rng: &mut R) -> Color {
let normal = Normal::new(0.0, 0.25).unwrap();
let h: f32 = rng.random_range(0.0..360.0);
let s: f32 = 0.7 + normal.sample(rng);
let l: f32 = 0.5 + normal.sample(rng);
let a: f32 = rng.random_range(0.0..1.0);
Okhsla::new(h, s, l, a).to_color()
}
pub fn get_color<T: AsPrimitive<f32>>(
img: &DynamicImage,
width: T,
height: T,
p: Point,
) -> Option<Color> {
if p.x < 0.0 || p.x >= width.as_() || p.y < 0.0 || p.y >= height.as_() {
None
} else {
let x = (p.x * img.width() as f32 / width.as_()) as u32;
let y = (p.y * img.height() as f32 / height.as_()) as u32;
let p = img.get_pixel(x, y);
Some(p.to_color())
}
}
pub fn get_color_wrap<T: AsPrimitive<f32>>(
img: &DynamicImage,
width: T,
height: T,
p: Point,
) -> Color {
let x = ((p.x * img.width() as f32 / width.as_()) as i32).rem_euclid(img.width() as i32);
let y = ((p.y * img.height() as f32 / height.as_()) as i32).rem_euclid(img.height() as i32);
let p = img.get_pixel(x as u32, y as u32);
p.to_color()
}
pub fn get_color_reflect<T: AsPrimitive<f32>>(
img: &DynamicImage,
width: T,
height: T,
p: Point,
) -> Color {
let px = p.x * img.width() as f32 / width.as_();
let py = p.y * img.height() as f32 / height.as_();
let x = if px < 0.0 {
px.abs()
} else if px > img.width() as f32 {
2.0 * img.width() as f32 - px
} else {
px
};
let y = if py < 0.0 {
py.abs()
} else if py > img.height() as f32 {
2.0 * img.height() as f32 - py
} else {
py
};
let p = img.get_pixel(
(x as u32).clamp(0, img.width() - 1),
(y as u32).clamp(0, img.height() - 1),
);
p.to_color()
}
pub fn get_color_clamp<T: AsPrimitive<f32>>(
img: &DynamicImage,
width: T,
height: T,
p: Point,
) -> Color {
let x = ((p.x * img.width() as f32 / width.as_()) as u32).clamp(0, img.width() - 1);
let y = ((p.y * img.height() as f32 / height.as_()) as u32).clamp(0, img.height() - 1);
let p = img.get_pixel(x, y);
p.to_color()
}
pub fn get_color_tile<T: AsPrimitive<f32>>(img: &DynamicImage, p: Point) -> Color {
let x = (p.x as u32).rem_euclid(img.width());
let y = (p.y as u32).rem_euclid(img.height());
let p = img.get_pixel(x, y);
p.to_color()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lerp_test() {
let c1 = Color::BLACK;
let c2 = grays(255);
let result = c1.lerp(&c2, 0.5);
let expected = rgb8(187, 187, 187);
let diff = (result.red() - expected.red()).abs() +
(result.green() - expected.green()).abs() +
(result.blue() - expected.blue()).abs();
assert!(diff < 0.01, "Color difference too large: {:?} vs {:?}", result, expected);
}
}