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