1#![allow(clippy::type_complexity)]
2#![allow(clippy::useless_conversion)]
3pub mod args;
5pub mod backends;
6pub mod cache;
7pub mod colors;
8pub mod colorspaces;
9pub mod config;
10pub mod palettes;
11pub mod template;
12pub mod themes;
13pub mod sequences;
14
15use std::path::Path;
16
17use anyhow::Result;
18use spinners::{Spinner, Spinners};
19use owo_colors::OwoColorize;
20
21use self::colors::Colors;
22use self::colorspaces::FallbackGenerator;
23use self::args::Globals;
24
25
26pub struct SpiWrap {
29 s: Option<Spinner>,
30}
31
32impl SpiWrap {
34 pub fn new(quiet: bool) -> Self {
35 let s = match quiet {
36 false => Some(Spinner::with_timer(Spinners::Pong, "Generating color scheme..".into())),
37 true => None
38 };
39 Self { s }
40 }
41
42 pub fn stop_warn(&mut self, gen: &FallbackGenerator) {
43 if let Some(sp) = &mut self.s {
44 let symbol = "[ 🗸 🗸 ]";
45 sp.stop_with_symbol(symbol);
46 print!("[{info}] Not enough colors in the image, artificially generating new colors...\n[{info}] {method}: Using {g} to fill the palette\n",
47 g = gen.to_string().color(gen.col()),
48 info = "I".blue().bold(),
49 method = "fallback generation method".magenta().bold()
50 );
51 }
52 }
53
54 pub fn stop(&mut self) {
55 if let Some(sp) = &mut self.s {
56 let symbol = "[ 🗸 ]";
57 sp.stop_with_symbol(symbol);
58 print!("[{info}] Color scheme palette generated!", info = "I".blue().bold());
59 }
60 }
61
62 pub fn stop_cache(&mut self, cname: &Path) {
63 if let Some(sp) = &mut self.s {
64 let symbol = "[ 🗸 ]";
65 let cname = cname.display();
66 sp.stop_with_symbol(symbol);
67 print!("[{info}] Color scheme palette generated!\n[{info}] Using cache at {cname}", info = "I".blue().bold());
68 }
69 }
70}
71
72impl Globals {
74 pub fn set_seq(&self, colors: &Colors, cache_path: &Path) -> Result<()> {
75 let info = "I".blue();
76 let info = info.bold();
77 let g = self;
78 if !g.skip_sequences && !g.update_current {
79 if !g.quiet { println!("[{info}] {}: Setting terminal colors.", "sequences".magenta().bold()); }
80 colors.sequences(cache_path, g.ignore_sequence.as_deref())?;
81 }
82 Ok(())
83 }
84
85 pub fn update_cur(&self, colors: &Colors) -> Result<()> {
86 let info = "I".blue();
87 let info = info.bold();
88 let g = self;
89 if g.update_current {
90 if !g.quiet { println!("[{info}] {seq}: Setting colors {b} in the current terminal.", seq = "sequences".magenta().bold(), b = "only".bold()); }
91 print!("{}", colors.to_seq(g.ignore_sequence.as_deref()));
92 }
93 Ok(())
94 }
95
96}
97
98
99pub fn gen_colors(file: &std::path::Path, c: &crate::config::Config, dynamic_th: bool, cache_path: &std::path::Path, no_cache: bool, quiet: bool, overwrite_cache: bool) -> anyhow::Result<crate::colors::Colors> {
102
103 let gen = &c.fallback_generator.unwrap_or_default();
104 let ord = &c.palette.sort_ord();
105 let dynamic = if c.threshold.is_some() && !dynamic_th { false } else { true };
106
107 let cache = cache::Cache::new(file, c, cache_path)?;
108 use cache::IsCached as C;
109
110 let quiet = quiet || (matches!(cache.is_cached_all(), C::BackendnCSnPalette) && !overwrite_cache);
112 let mut spi = SpiWrap::new(quiet);
113 if overwrite_cache {
116 let rgb8s = c.backend.main()(file)?;
117 if !no_cache { cache.write_backend(&rgb8s)? } let cs = match c.color_space.run(dynamic, &rgb8s, c.threshold.unwrap_or_default(), gen, ord) {
120 Some(s) => s,
121 None => anyhow::bail!("Not enough colors!"),
122 };
123
124 let (ref top, ref orig, warn) = cs;
125 if !no_cache { cache.write_cs(&cs)? } let mut colors = c.palette.run(top.to_vec(), orig.to_vec());
128 if !no_cache { cache.write_palette(&colors)? } postcolor(c, &mut colors);
130 if warn { spi.stop_warn(gen) } else { spi.stop() }
131 Ok(colors)
132 } else {
133 match cache.is_cached_all() {
134 C::BackendnCSnPalette => { let mut colors = cache.read_palette()?;
136 postcolor(c, &mut colors);
137 spi.stop_cache(&cache.name);
138 Ok(colors)
139 },
140 C::BackendnCS => { let (top, orig, warn) = cache.read_cs()?;
142 let mut colors = c.palette.run(top, orig);
143 if !no_cache { cache.write_palette(&colors)? } postcolor(c, &mut colors);
145 if warn { spi.stop_warn(gen) } else { spi.stop_cache(&cache.name) }
146 Ok(colors)
147 },
148 C::Backend => { let rgb8s = cache.read_backend()?;
150
151 let cs = match c.color_space.run(dynamic, &rgb8s, c.threshold.unwrap_or_default(), gen, ord) {
152 Some(s) => s,
153 None => anyhow::bail!("Not enough colors!"),
154 };
155
156 let (ref top, ref orig, warn) = cs;
157 if !no_cache { cache.write_cs(&cs)? } let mut colors = c.palette.run(top.to_vec(), orig.to_vec());
160 if !no_cache { cache.write_palette(&colors)? } postcolor(c, &mut colors);
162 if warn { spi.stop_warn(gen); } else { spi.stop(); } Ok(colors)
164 },
165 C::None => { let rgb8s = c.backend.main()(file)?;
167 if !no_cache { cache.write_backend(&rgb8s)? } let cs = match c.color_space.run(dynamic, &rgb8s, c.threshold.unwrap_or_default(), gen, ord) {
170 Some(s) => s,
171 None => anyhow::bail!("Not enough colors!"),
172 };
173
174 let (ref top, ref orig, warn) = cs;
175 if !no_cache { cache.write_cs(&cs)? } let mut colors = c.palette.run(top.to_vec(), orig.to_vec());
178 if !no_cache { cache.write_palette(&colors)? } postcolor(c, &mut colors);
180 if warn { spi.stop_warn(gen) } else { spi.stop() }
181 Ok(colors)
182 },
183 }
184
185 }
186}
187
188pub fn postcolor(c: &crate::config::Config, colors: &mut crate::colors::Colors) {
191 if c.check_contrast.unwrap_or(false) {
192 colors.check_contrast_all();
193 }
194
195 if let Some(s) = c.saturation {
196 colors.saturate_colors(f32::from(s) / 100.0);
197 }
198}