1 use std::io::{self, Write};
2
3 use crate::commands::prelude::*;
4
5 use pastel::ansi::Stream;
6 use pastel::distinct::{self, DistanceMetric, IterationStatistics};
7 use pastel::{Fraction, HSLA};
8
9 pub struct DistinctCommand;
10
print_iteration(out: &mut dyn Write, brush: Brush, stats: &IterationStatistics) -> Result<()>11 fn print_iteration(out: &mut dyn Write, brush: Brush, stats: &IterationStatistics) -> Result<()> {
12 let result = stats.distance_result;
13 write!(
14 out,
15 "[{:10.}] D_mean = {:<6.2}; D_min = {:<6.2}; T = {:.6} ",
16 stats.iteration,
17 result.mean_closest_distance,
18 result.min_closest_distance,
19 stats.temperature
20 )?;
21 print_colors(out, brush, &stats.colors, Some(result.closest_pair))?;
22 Ok(())
23 }
24
print_colors( out: &mut dyn Write, brush: Brush, colors: &[Color], closest_pair: Option<(usize, usize)>, ) -> Result<()>25 fn print_colors(
26 out: &mut dyn Write,
27 brush: Brush,
28 colors: &[Color],
29 closest_pair: Option<(usize, usize)>,
30 ) -> Result<()> {
31 for (ci, c) in colors.iter().enumerate() {
32 let tc = c.text_color();
33 let mut style = tc.ansi_style();
34 style.on(c);
35
36 if let Some(pair) = closest_pair {
37 if pair.0 == ci || pair.1 == ci {
38 style.bold(true);
39 style.underline(true);
40 }
41 }
42
43 write!(
44 out,
45 "{} ",
46 brush.paint(c.to_rgb_hex_string(false).to_string(), style)
47 )?;
48 }
49 writeln!(out)?;
50 Ok(())
51 }
52
blue_red_yellow(f: f64) -> Color53 fn blue_red_yellow(f: f64) -> Color {
54 let blue = Color::from_rgb(0, 0, 120);
55 let red = Color::from_rgb(224, 0, 119);
56 let yellow = Color::from_rgb(255, 255, 0);
57
58 if f < 0.5 {
59 blue.mix::<HSLA>(&red, Fraction::from(2.0 * f))
60 } else {
61 red.mix::<HSLA>(&yellow, Fraction::from(2.0 * (f - 0.5)))
62 }
63 }
64
print_distance_matrix( out: &mut dyn Write, brush: Brush, colors: &[Color], metric: DistanceMetric, ) -> Result<()>65 fn print_distance_matrix(
66 out: &mut dyn Write,
67 brush: Brush,
68 colors: &[Color],
69 metric: DistanceMetric,
70 ) -> Result<()> {
71 let count = colors.len();
72
73 let distance = |c1: &Color, c2: &Color| match metric {
74 DistanceMetric::CIE76 => c1.distance_delta_e_cie76(c2),
75 DistanceMetric::CIEDE2000 => c1.distance_delta_e_ciede2000(c2),
76 };
77
78 let mut min = std::f64::MAX;
79 let mut max = 0.0;
80 for i in 0..count {
81 for j in 0..count {
82 if i != j {
83 let dist = distance(&colors[i], &colors[j]);
84 if dist < min {
85 min = dist;
86 }
87 if dist > max {
88 max = dist;
89 }
90 }
91 }
92 }
93
94 let color_to_string = |c: &Color| -> String {
95 let tc = c.text_color();
96 let mut style = tc.ansi_style();
97 style.on(c);
98 brush.paint(c.to_rgb_hex_string(false), style)
99 };
100
101 write!(out, "\n\n{:6} ", "")?;
102 for c in colors {
103 write!(out, "{} ", color_to_string(c))?;
104 }
105 writeln!(out, "\n")?;
106
107 for c1 in colors {
108 write!(out, "{} ", color_to_string(c1))?;
109 for c2 in colors {
110 if c1 == c2 {
111 write!(out, "{:6} ", "")?;
112 } else {
113 let dist = distance(c1, c2);
114
115 let magnitude = (dist - min) / (max - min);
116 let magnitude = 1.0 - magnitude.powf(0.3);
117
118 let bg = blue_red_yellow(magnitude);
119 let mut style = bg.text_color().ansi_style();
120 style.on(bg);
121
122 write!(out, "{} ", brush.paint(format!("{:6.2}", dist), style))?;
123 }
124 }
125 writeln!(out)?;
126 }
127 writeln!(out, "\n")?;
128
129 Ok(())
130 }
131
132 impl GenericCommand for DistinctCommand {
run(&self, out: &mut Output, matches: &ArgMatches, config: &Config) -> Result<()>133 fn run(&self, out: &mut Output, matches: &ArgMatches, config: &Config) -> Result<()> {
134 let stderr = io::stderr();
135 let mut stderr_lock = stderr.lock();
136 let brush_stderr = Brush::from_environment(Stream::Stderr);
137 let verbose_output = matches.is_present("verbose");
138
139 let count = matches.value_of("number").expect("required argument");
140 let count = count
141 .parse::<usize>()
142 .map_err(|_| PastelError::CouldNotParseNumber(count.into()))?;
143
144 if count < 2 {
145 return Err(PastelError::DistinctColorCountMustBeLargerThanOne);
146 }
147
148 let distance_metric = match matches.value_of("metric").expect("required argument") {
149 "CIE76" => DistanceMetric::CIE76,
150 "CIEDE2000" => DistanceMetric::CIEDE2000,
151 _ => unreachable!("Unknown distance metric"),
152 };
153
154 let fixed_colors = match matches.values_of("color") {
155 None => vec![],
156 Some(positionals) => {
157 ColorArgIterator::FromPositionalArguments(config, positionals, PrintSpectrum::Yes)
158 .collect::<Result<Vec<_>>>()?
159 }
160 };
161
162 let num_fixed_colors = fixed_colors.len();
163 if num_fixed_colors > count {
164 return Err(PastelError::DistinctColorFixedColorsCannotBeMoreThanCount);
165 }
166
167 let mut callback: Box<dyn FnMut(&IterationStatistics)> = if verbose_output {
168 Box::new(|stats: &IterationStatistics| {
169 print_iteration(&mut stderr_lock, brush_stderr, stats).ok();
170 })
171 } else {
172 Box::new(|_: &IterationStatistics| {})
173 };
174
175 let (mut colors, distance_result) =
176 distinct::distinct_colors(count, distance_metric, fixed_colors, callback.as_mut());
177
178 if matches.is_present("print-minimal-distance") {
179 writeln!(out.handle, "{:.3}", distance_result.min_closest_distance)?;
180 } else {
181 distinct::rearrange_sequence(&mut colors, distance_metric);
182
183 if verbose_output {
184 print_distance_matrix(&mut stderr.lock(), brush_stderr, &colors, distance_metric)?;
185 }
186
187 for color in colors {
188 out.show_color(config, &color)?;
189 }
190 }
191
192 Ok(())
193 }
194 }
195