1use ab_glyph::FontArc;
2use anyhow::{Result, bail};
3use image::{DynamicImage, imageops};
4use log;
5use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
6
7use crate::correlation::correlation;
8use crate::element::Element;
9use crate::{FULL_WIDTH_SPACE, GLYPH_SCALE, IMAGE_SIZE, NUM_OF_CANDIDATES};
10
11#[derive(Debug, Clone)]
14pub struct Model {
15 image: DynamicImage,
17
18 characters: Vec<char>,
20
21 font: FontArc,
23
24 columns: u32,
26
27 lines: u32,
29}
30
31impl Model {
32 pub fn new(
34 length: u32,
35 image: &DynamicImage,
36 characters: &[char],
37 font: &[u8],
38 ) -> Result<Self> {
39 let columns = length;
40 let width = IMAGE_SIZE * columns;
41 let height = image.height() * width / image.width();
42 let img = image.resize(width, height, imageops::FilterType::Triangle);
43 let lines = height / IMAGE_SIZE;
44 log::info!(
45 "Image dimensions: {width}x{height}, size: {IMAGE_SIZE}, columns: {columns}, lines: {lines}",
46 );
47 let font = match FontArc::try_from_vec(font.to_vec()) {
48 Ok(f) => f,
49 Err(e) => bail!("Failed to load font: {}", e),
50 };
51
52 Ok(Model {
53 image: img,
54 characters: characters.to_vec(),
55 font,
56 columns,
57 lines,
58 })
59 }
60
61 pub fn convert(&mut self) -> Result<Vec<String>> {
63 let typeset_elements = self.typeset_elements(&self.characters)?;
64 let picture_elements =
65 self.picture_elements(&self.image, IMAGE_SIZE, self.columns, self.lines)?;
66 log::info!(
67 "Typeset elements: {}, Picture elements: {}",
68 typeset_elements.len(),
69 picture_elements.len()
70 );
71
72 let typist_art_elements = Self::generate_typist_art(&picture_elements, &typeset_elements);
73 log::info!("Converted picture elements to typist art.");
74
75 let mut result = vec![];
76 let mut v = vec![];
77 for (i, e) in typist_art_elements.iter().enumerate() {
78 if i % self.columns as usize == 0 && i != 0 {
79 result.push(v.iter().collect());
80 v.clear();
81 }
82 v.push(e.character().unwrap_or(FULL_WIDTH_SPACE));
83 }
84 if !v.is_empty() {
85 result.push(v.iter().collect());
86 }
87
88 Ok(result)
89 }
90
91 fn picture_elements(
94 &self,
95 image: &DynamicImage,
96 size: u32,
97 columns: u32,
98 lines: u32,
99 ) -> Result<Vec<Element>> {
100 let mut elements = vec![];
101 for y in 0..lines {
102 for x in 0..columns {
103 let block_image = image.crop_imm(x * size, y * size, size, size);
104 elements.push(Element::from_image(block_image)?);
105 }
106 }
107
108 Self::normalize_elements(&mut elements)?;
110
111 Ok(elements)
112 }
113
114 fn typeset_elements(&self, characters: &[char]) -> Result<Vec<Element>> {
117 let mut elements: Vec<Element> = characters
118 .par_iter()
119 .map(|c| Element::from_char(&self.font, *c, *GLYPH_SCALE))
120 .collect::<Result<Vec<_>>>()?;
121
122 Self::normalize_elements(&mut elements)?;
124
125 elements.sort_by(|a, b| {
127 a.luminance()
128 .partial_cmp(&b.luminance())
129 .unwrap_or(std::cmp::Ordering::Equal)
130 });
131 log::debug!("Sorted typeset elements by luminance.");
132 for e in &elements {
133 log::debug!(
134 "Character: {:?}, Luminance: {}",
135 e.character(),
136 e.luminance(),
137 );
138 }
139
140 Ok(elements)
141 }
142
143 fn normalize_elements(elements: &mut [Element]) -> Result<()> {
146 let mut min = f64::INFINITY;
147 let mut max = f64::NEG_INFINITY;
148 for e in elements.iter_mut() {
149 if e.luminance() < min {
150 min = e.luminance();
151 }
152 if e.luminance() > max {
153 max = e.luminance();
154 }
155 }
156 log::info!("Luminance range: [{min}, {max}]");
157
158 elements
159 .par_iter_mut()
160 .for_each(|e| e.normalized(min, max).unwrap());
161 log::info!("Normalized elements.");
162
163 Ok(())
164 }
165
166 fn closest_luminance_index(target: f64, typeset_elements: &[Element]) -> usize {
169 let result = typeset_elements.binary_search_by(|prove| {
170 prove
171 .luminance()
172 .partial_cmp(&target)
173 .unwrap_or(std::cmp::Ordering::Equal)
174 });
175
176 match result {
177 Ok(i) => i,
178 Err(i) => {
179 if i == 0 {
180 0
181 } else if i >= typeset_elements.len() {
182 typeset_elements.len() - 1
183 } else {
184 let diff1 = (typeset_elements[i - 1].luminance() - target).abs();
185 let diff2 = (typeset_elements[i].luminance() - target).abs();
186 if diff1 < diff2 { i - 1 } else { i }
187 }
188 }
189 }
190 }
191
192 fn best_match_element<'a>(target: &Element, candidates: &'a [Element]) -> Option<&'a Element> {
195 let mut max = -1.0;
196 let mut best: Option<&Element> = None;
197 for candidate in candidates {
198 if let Some(result) = correlation(target.characteristics(), candidate.characteristics())
199 {
200 if result > max {
201 max = result;
202 best = Some(candidate);
203 }
204 }
205 }
206
207 best
208 }
209
210 fn search_typeset_element<'a>(
213 picture_element: &'a Element,
214 typeset_elements: &'a [Element],
215 ) -> Option<&'a Element> {
216 if typeset_elements.is_empty() {
217 return None;
218 }
219
220 let index = Self::closest_luminance_index(picture_element.luminance(), typeset_elements);
222
223 let from = index.saturating_sub(NUM_OF_CANDIDATES / 2);
226 let to = std::cmp::min(typeset_elements.len(), from + NUM_OF_CANDIDATES);
227 let candidates = &typeset_elements[from..to];
228
229 if candidates.is_empty() {
230 return Some(&typeset_elements[index]);
231 }
232
233 Self::best_match_element(picture_element, candidates)
235 }
236
237 fn generate_typist_art(
240 picture_elements: &[Element],
241 typeset_elements: &[Element],
242 ) -> Vec<Element> {
243 let default = Element::default();
244 let typist_art_elements: Vec<Element> = picture_elements
245 .par_iter()
246 .map(|e| Self::search_typeset_element(e, typeset_elements).unwrap_or(&default))
247 .cloned()
248 .collect();
249
250 typist_art_elements
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn closest_luminance_index_empty_elements() {
260 let elements: Vec<Element> = vec![];
261 assert_eq!(Model::closest_luminance_index(0.5, &elements), 0);
262 }
263
264 #[test]
265 fn closest_luminance_index_single_element() {
266 let elements = vec![Element::new(vec![0.0; 10], 0.5, Some('A'), None)];
267 assert_eq!(Model::closest_luminance_index(0.5, &elements), 0);
268 }
269
270 #[test]
271 fn closest_luminance_index_multiple_elements() {
272 let elements = vec![
273 Element::new(vec![0.0; 10], 0.1, None, None),
274 Element::new(vec![0.0; 10], 0.5, None, None),
275 Element::new(vec![0.0; 10], 0.9, None, None),
276 ];
277 assert_eq!(Model::closest_luminance_index(0.5, &elements), 1);
278 assert_eq!(Model::closest_luminance_index(0.2, &elements), 0);
279 assert_eq!(Model::closest_luminance_index(0.8, &elements), 2);
280 }
281
282 #[test]
283 fn best_match_element_empty_candidates() {
284 let target = Element::new(vec![0.5; 10], 0.5, Some('A'), None);
285 let candidates: Vec<Element> = vec![];
286 assert!(Model::best_match_element(&target, &candidates).is_none());
287 }
288
289 #[test]
290 fn best_match_element_valid_candidates() {
291 let target = Element::new(vec![0.5; 10], 0.5, Some('A'), None);
292 let candidates = vec![
293 Element::new(vec![0.2; 10], 0.2, Some('B'), None),
294 Element::new(vec![0.5; 10], 0.5, Some('C'), None),
295 Element::new(vec![0.7; 10], 0.7, Some('D'), None),
296 ];
297 let best = Model::best_match_element(&target, &candidates);
298 assert!(best.is_some());
299 assert_eq!(best.unwrap().characteristics(), &vec![0.5; 10]);
300 }
301
302 #[test]
303 fn search_typeset_element_empty_typeset_returns_none() {
304 let picture_element = Element::new(vec![0.0; 10], 0.5, Some('A'), None);
305 let typeset_elements: Vec<Element> = vec![];
306 assert!(Model::search_typeset_element(&picture_element, &typeset_elements).is_none());
307 }
308
309 #[test]
310 fn search_typeset_element_valid_typeset_returns_some() {
311 let picture_element = Element::new(vec![0.5; 10], 0.5, Some('A'), None);
312 let typeset_elements = vec![
313 Element::new(vec![0.2; 10], 0.2, Some('B'), None),
314 Element::new(vec![0.2; 10], 0.2, Some('B'), None),
315 Element::new(vec![0.5; 10], 0.5, Some('C'), None),
316 Element::new(vec![0.7; 10], 0.7, Some('D'), None),
317 ];
318 let result = Model::search_typeset_element(&picture_element, &typeset_elements);
319 assert!(result.is_some());
320 let best_match = result.unwrap();
321 assert_eq!(best_match.characteristics(), &vec![0.5; 10]);
322 assert_eq!(best_match.character(), Some('C'));
323 }
324}