1use color::OklabColor;
2use color::OklabResult;
3use color::RGBColor;
4use plotters::prelude::BitMapBackend;
5use plotters::prelude::DrawingBackend;
6use std::cmp;
7use std::error::Error;
8
9pub mod color;
10
11type Palette = Vec<RGBColor>;
12const MAX_PALETTE_SIZE: u32 = 256;
13
14pub enum GeneratorMode {
15 Auto,
16 }
18
19pub struct GeneratorConfig {
20 pub mode: GeneratorMode,
21 pub similarity: f32,
22 pub max_iterations: u32,
23}
24
25impl Default for GeneratorConfig {
26 fn default() -> Self {
27 Self {
28 mode: GeneratorMode::Auto,
29 similarity: 0.6,
30 max_iterations: 50,
31 }
32 }
33}
34
35fn generate_with_fps(colors: &mut Palette, step_size: f32, max_iterations: u32) {
38 let last_color = colors.last().unwrap().to_oklab();
39
40 const POOL_SIZE: usize = 8; let mut candidates = [OklabColor::new(); POOL_SIZE];
43 {
44 let mut num_candidates = 032;
46 for i in 0..candidates.len() {
47 let step_result = last_color.random_step_sgrb(step_size, max_iterations);
48 if let OklabResult::Ok(c) = step_result {
49 candidates[i] = c;
50 num_candidates += 1;
51 }
52 }
53
54 if num_candidates == 0 {
56 candidates[0] = last_color
57 .random_step_sgrb(step_size, max_iterations)
58 .unwrap();
59 for i in 1..candidates.len() {
60 candidates[i] = candidates[0];
61 }
62 }
63 }
64
65 let mut min_distances = [f32::INFINITY; POOL_SIZE];
67 for (i, candidate) in candidates.iter().enumerate() {
68 for color in colors.iter() {
69 let color = color.to_oklab();
70 let dist = candidate.squared_distance(&color);
71 if dist < min_distances[i] {
72 min_distances[i] = dist;
73 }
74 }
75 }
76
77 let mut max_idx: usize = 0;
78 let mut max_dist: f32 = min_distances[0];
79 for i in 1..min_distances.len() {
80 if min_distances[i] > max_dist {
81 max_dist = min_distances[i];
82 max_idx = i;
83 }
84 }
85 colors.push(RGBColor::from_oklab(candidates[max_idx]));
86}
87
88fn distance_from_similarity(similarity: f32) -> f32 {
89 let similarity = similarity.clamp(0.0, 1.0);
90
91 const MIN_DIST: f32 = 0.01;
92 const MAX_DIST: f32 = 0.26;
93
94 (MAX_DIST - similarity * (MAX_DIST - MIN_DIST)).max(0.0)
95}
96
97pub fn generate_palette(
98 num_colors: u32,
99 config: &GeneratorConfig,
100) -> Result<Palette, Box<dyn Error>> {
101 if num_colors == 0 || num_colors > MAX_PALETTE_SIZE {
102 return Err(format!(
103 "Number of colors must be number between [1-{}].",
104 MAX_PALETTE_SIZE
105 )
106 .into());
107 }
108
109 match config.mode {
110 GeneratorMode::Auto => return Ok(generate_auto(num_colors, config)),
111 }
112}
113
114fn generate_auto(num_colors: u32, config: &GeneratorConfig) -> Palette {
115 let mut palette: Palette = Palette::with_capacity(num_colors as usize);
116 let first_color = OklabColor::random_in_sgrb(0.3, 0.6, 100);
117 palette.push(RGBColor::from_oklab(first_color.unwrap()));
118
119 let color_distance = distance_from_similarity(config.similarity);
120 for _ in 1..num_colors {
121 generate_with_fps(&mut palette, color_distance, config.max_iterations);
122 }
123
124 palette
125}
126
127pub fn save_palette(palette: &Palette, path: &str) {
128 if palette.len() == 0 {
129 panic!("palette is length 0!");
130 }
131
132 let num_colors = palette.len() as u32;
133 let squares_per_row = 256;
134 let num_rows = (num_colors as f32 / squares_per_row as f32).ceil() as u32;
135
136 let square_size: u32 = 32;
137 let canvas_width = square_size * cmp::min(squares_per_row, num_colors);
138 let canvas_height = square_size * num_rows;
139 let mut backend = BitMapBackend::new(path, (canvas_width, canvas_height));
140
141 let square_size = square_size as i32;
142 for (i, &color) in palette.iter().enumerate() {
143 let i = i as i32;
144 let r: i32 = i / squares_per_row as i32;
145 let c: i32 = i % squares_per_row as i32;
146
147 let upper_left: (i32, i32) = (c * square_size, r * square_size);
148 let bottom_right: (i32, i32) = (upper_left.0 + square_size, upper_left.1 + square_size);
149 backend
150 .draw_rect(
151 upper_left,
152 bottom_right,
153 &plotters::style::RGBColor(color.r, color.g, color.b),
154 true,
155 )
156 .unwrap();
157 }
158}