1use anyhow::Error;
2use itertools::Itertools;
3use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
4mod conversions;
5pub use conversions::*;
6
7const ASCII_SIZE: usize = 8;
8const DEFAULT_TEXTURE: &str = " .,:-=*#@";
9pub const UNICODE_TEXTURE: &str = " ░▒▓█";
10pub const MIXED_TEXTURE: &str = " .-=*#@░▒▓█";
11
12pub struct ImageRepr {
13 pub image: zune_image::image::Image,
14 pub dimensions: ImgDimensions,
15}
16
17impl ImageRepr {
18 pub fn width(&self) -> usize {
19 self.dimensions.width
20 }
21 pub fn height(&self) -> usize {
22 self.dimensions.height
23 }
24}
25
26pub struct ImgDimensions {
27 pub width: usize,
28 pub height: usize,
29}
30
31pub struct AppState {
32 pub og_image: ImageRepr,
33 pub resized_image: Option<ImageRepr>,
34 pub ascii_size: usize,
35 pub texture: String,
36}
37
38impl AppState {
39 pub fn new(buffer: &[u8], resize_width: Option<usize>) -> anyhow::Result<Self> {
40 let decode_options = DecoderOptions::default();
41
42 let image = zune_image::image::Image::read(buffer, decode_options).unwrap();
43
44 let meta = image.metadata();
45 let (width, height) = meta.get_dimensions();
46
47 let mut state = AppState {
48 og_image: ImageRepr {
49 image,
50 dimensions: ImgDimensions { width, height },
51 },
52 ascii_size: ASCII_SIZE,
53 resized_image: None,
54 texture: DEFAULT_TEXTURE.to_owned(),
55 };
56
57 let aspect_ratio = width / height;
58
59 let resize_dimensions = resize_width.and_then(|w| {
60 Some(ImgDimensions {
61 width: w,
62 height: w / aspect_ratio,
63 })
64 });
65 state.resize(resize_dimensions)?;
66
67 Ok(state)
68 }
69
70 pub fn set_pixel_size(&mut self, ascii_pixel_size: usize) {
71 self.ascii_size = ascii_pixel_size;
72 }
73
74 pub fn set_texture(&mut self, texture: &str) {
75 self.texture = texture.to_owned()
76 }
77
78 fn resize(&mut self, dimensions: Option<ImgDimensions>) -> anyhow::Result<()> {
79 let og_dims = &self.og_image.dimensions;
80 let out_dims = dimensions.or_else(|| {
81 term_size::dimensions().map(|d| ImgDimensions {
82 width: d.0,
83 height: d.1,
84 })
85 });
86 let out_dims = out_dims.ok_or(Error::msg("failed to get term dimentions"))?;
87 let (resized_width, resized_height) = if og_dims.width > og_dims.height {
88 let new_width = out_dims.width - ASCII_SIZE;
89 let new_height = (new_width * og_dims.height) / og_dims.width;
90 (new_width, new_height)
91 } else {
92 let new_height = out_dims.height - ASCII_SIZE;
93 let new_width = (new_height * og_dims.width) / og_dims.height;
94 (new_width, new_height)
95 };
96 let resized = resize_image(&self.og_image.image, resized_width, resized_height);
97
98 self.resized_image = Some(ImageRepr {
99 image: resized,
100 dimensions: ImgDimensions {
101 width: resized_width,
102 height: resized_height,
103 },
104 });
105 Ok(())
106 }
107
108 fn resized_rgb_channels(&self) -> anyhow::Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
109 if let Some(resized) = self.resized_image.as_ref() {
110 let channels = resized
111 .image
112 .channels_ref(true)
113 .into_iter()
114 .map(|c| c.reinterpret_as::<u8>())
115 .collect::<Result<Vec<_>, _>>()
116 .map_err(|_| anyhow::Error::msg("cannot get channels"))?;
117 Ok((
119 channels[0].to_vec(),
120 channels[1].to_vec(),
121 channels[2].to_vec(),
122 ))
123 } else {
124 Err(anyhow::Error::msg("please resize image first"))
125 }
126 }
127
128 fn quantized_level(&self, value: u8) -> u32 {
129 let distance_bw_levels = (255 - 0) / self.texture.chars().count() as u32;
132 let value = value as u32 / distance_bw_levels;
134 value.saturating_sub(1)
136 }
137
138 fn to_luma(&self) -> anyhow::Result<Vec<u8>> {
139 let (r, g, b) = self.resized_rgb_channels()?;
140 Ok(itertools::izip!(r, g, b)
141 .map(|(r, g, b)| (0.2126 * r as f32 + 0.7152 * g as f32 + 0.0722 * b as f32) as u8)
143 .collect::<Vec<_>>())
144 }
145
146 pub fn apply_texture(&self) -> anyhow::Result<String> {
147 let luma = self.to_luma()?;
148 let mapped_texture = self.texture.chars().collect::<Vec<_>>();
149 let l = luma
150 .iter()
151 .map(|&l| {
152 mapped_texture
153 .get(self.quantized_level(l) as usize)
154 .unwrap()
155 })
156 .collect::<Vec<_>>();
157 Ok(l.chunks(self.resized_image.as_ref().unwrap().width())
158 .map(|cs| cs.iter().join(""))
159 .join("\n"))
160 }
161}