wallust/
lib.rs

1#![allow(clippy::type_complexity)]
2#![allow(clippy::useless_conversion)]
3//! wallust - Generate a colorscheme based on an image
4pub 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
26/// Simple wrapper around spinner, to avoid allocations and the like.
27//#[derive(Debug)]
28pub struct SpiWrap {
29    s: Option<Spinner>,
30}
31
32// XXX make it chainable, `.warn().stop()` or `.cache().stop()` maybe, by editing a fmt.
33impl 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
72/// These methods are to avoid code duplication, used in main
73impl 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
99/// How [`crate::colors::Colors`] is filled, returns the colors itself and a bool that indicates whether
100/// [`backends`] had some warnings or not (ugly workaround ik)
101pub 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    // Having to only read the schemepalette is TOO FAST to have the spinner.
111    let quiet = quiet || (matches!(cache.is_cached_all(), C::BackendnCSnPalette) && !overwrite_cache);
112    let mut spi = SpiWrap::new(quiet);
113    // println!("{:?}", cache.is_cached_all());
114
115    if overwrite_cache {
116            let rgb8s = c.backend.main()(file)?;
117            if !no_cache { cache.write_backend(&rgb8s)? } //BACKEND
118
119            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)? } //COLORSPACE
126
127            let mut colors = c.palette.run(top.to_vec(), orig.to_vec());
128            if !no_cache { cache.write_palette(&colors)? } //COLORS
129            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 => { // (cache)Palette -> Done
135                let mut colors = cache.read_palette()?;
136                postcolor(c, &mut colors);
137                spi.stop_cache(&cache.name);
138                Ok(colors)
139            },
140            C::BackendnCS => { // (cached)CS -> Palette -> Done
141                let (top, orig, warn) = cache.read_cs()?;
142                let mut colors = c.palette.run(top, orig);
143                if !no_cache { cache.write_palette(&colors)? } // COLORS
144                postcolor(c, &mut colors);
145                if warn { spi.stop_warn(gen) } else { spi.stop_cache(&cache.name) }
146                Ok(colors)
147            },
148            C::Backend => { // (cached)Backend -> CS -> Palette -> Done
149                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)? } //COLORSPACE
158
159                let mut colors = c.palette.run(top.to_vec(), orig.to_vec());
160                if !no_cache { cache.write_palette(&colors)? } //COLORS
161                postcolor(c, &mut colors);
162                if warn { spi.stop_warn(gen); } else { spi.stop(); } // here simple stop, since it's very fast
163                Ok(colors)
164            },
165            C::None => { // Generate Backend from scratch => CS -> Palette -> Done.
166                let rgb8s = c.backend.main()(file)?;
167                if !no_cache { cache.write_backend(&rgb8s)? } //BACKEND
168
169                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)? } //COLORSPACE
176
177                let mut colors = c.palette.run(top.to_vec(), orig.to_vec());
178                if !no_cache { cache.write_palette(&colors)? } //COLORS
179                postcolor(c, &mut colors);
180                if warn { spi.stop_warn(gen) } else { spi.stop() }
181                Ok(colors)
182            },
183        }
184
185    }
186}
187
188/// These steps are not cached, since they are variable and cheap operations. Keep the original
189/// scheme in which this is done and then apply these.
190pub 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}