1use image::imageops::FilterType;
4use rusttype::{point, Font, Scale, PositionedGlyph};
5use std::fs::File;
6use std::io::{self, BufWriter, Write, Read};
7use std::cmp;
8use image::GenericImageView;
9use rand::{thread_rng, Rng};
10
11pub static ASCIIS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-^\\=~|@[`{;:]+*},./_<>?_ ";
13pub struct Char2DArray {
17 pub buffer: Vec<Vec<char>>,
19 height: usize,
20 width: usize,
21}
22
23#[derive(Debug, Copy, Clone)]
24pub struct CharPosition {
25 pub x: i32,
26 pub y: i32,
27}
28
29impl Char2DArray {
30 pub fn new(width: usize, height: usize) -> Char2DArray {
33 let mut array2d = Char2DArray{
34 buffer: Vec::with_capacity(height),
35 height: height,
36 width: width,
37 };
38 for _ in 0..height {
39 let mut line: Vec<char> = Vec::with_capacity(width);
40 for _ in 0..width {
41 line.push(' ');
42 }
43 array2d.buffer.push(line);
44 }
45 array2d
46 }
47
48 pub fn debug_print(&self) {
49 let lines = self.to_lines();
50 for line in lines {
51 println!("{}", line);
52 }
53 }
54
55 pub fn from(src: Vec<Vec<char>>) -> Char2DArray {
57 let mut c2d = Char2DArray::new(src[0].len(), src.len());
58 for y in 0..c2d.height() {
59 for x in 0..c2d.width() {
60 c2d.buffer[y][x] = src[y][x];
61 }
62 }
63 c2d
64 }
65
66 pub fn to_lines(&self) -> Vec<String> {
68 let mut lines: Vec<String> = Vec::new();
69 for chars in self.buffer.iter() {
70 let line: String = chars.into_iter().collect();
71 lines.push(line);
72 }
73 lines
74 }
75
76 pub fn save(&self, save_file: &str) -> io::Result<()>{
78 let file = File::create(save_file)?;
79 let mut writer = BufWriter::new(file);
80 let lines = self.to_lines();
81 for line in lines.iter() {
82 writer.write_all(line.as_bytes())?;
83 writer.write(&[10u8])?; }
85 writer.flush()?;
86 Ok(())
87 }
88
89 pub fn height(&self) -> usize {
91 self.height
92 }
93
94 pub fn width(&self) -> usize {
96 self.width
97 }
98
99 pub fn overwrite_rect(&mut self, rect: &Char2DArray, position: CharPosition, transparent: Option<char>) {
101 let y_start = cmp::max(0, position.y);
102 let y_end = cmp::min(self.height() as i32, position.y + rect.height() as i32);
103 let x_start = cmp::max(0, position.x);
104 let x_end = cmp::min(self.width() as i32, position.x + rect.width() as i32);
105
106 for y in y_start..y_end {
107 for x in x_start..x_end {
108 match transparent {
109 Some(ch) => {
110 if rect.buffer[(y - position.y) as usize][(x - position.x) as usize] == ch {
111 continue;
112 }
113 },
114 _ => {}
115 }
116 self.buffer[y as usize][x as usize] = rect.buffer[(y - position.y) as usize][(x - position.x) as usize];
117 }
118 }
119 }
120
121
122 pub fn overwrite_rect_center(&mut self, rect: &Char2DArray, position: CharPosition, transparent: Option<char>) {
123 let center_x = (self.width() / 2) - (rect.width() / 2);
124 let center_y = (self.height() / 2) - (rect.height() / 2);
125 let offset_posi = CharPosition {
126 x: position.x + center_x as i32,
127 y: position.y + center_y as i32,
128 };
129 self.overwrite_rect(rect, offset_posi, transparent)
130 }
131
132
133 pub fn overwrite_line(&mut self, ch: char, line_index: usize) {
134 for x in 0..self.width() {
135 self.buffer[line_index][x] = ch;
136 }
137 }
138
139 pub fn overwrite_char(&mut self, ch: char, position: CharPosition){
140 self.buffer[position.y as usize][position.x as usize] = ch;
141 }
142
143 pub fn overwrite_char_all(&mut self, ch: char) {
144 for y in 0..self.height() {
145 for x in 0..self.width() {
146 self.buffer[y][x] = ch;
147 }
148 }
149 }
150
151 pub fn copy_from(&mut self, src: &Char2DArray) {
152 self.overwrite_rect(src, CharPosition{x:0,y:0}, Option::None);
153 }
154
155 pub fn overwrite_fn<F: Fn(usize, usize, char) -> bool>(&mut self, ch: char, f: F){
156 for y in 0..self.height() {
157 for x in 0..self.width() {
158 if f(x, y, self.buffer[y as usize][x as usize]) {
159 self.buffer[y as usize][x as usize] = ch;
160 }
161 }
162 }
163 }
164
165 pub fn overwrite_random_fn<F: Fn(usize, usize, char) -> bool>(&mut self, chars: Vec<char>, f: F){
166 let mut rng = thread_rng();
167 for y in 0..self.height() {
168 for x in 0..self.width() {
169 if f(x, y, self.buffer[y as usize][x as usize]) {
170 let index: usize = rng.gen_range(0, chars.len());
171 self.buffer[y as usize][x as usize] = chars[index];
172 }
173 }
174 }
175 }
176
177
178}
179
180fn pad(ascii_list: &str, target_size: usize) -> String {
182 let mut padded_list = String::from(ascii_list);
183 let list_size = ascii_list.len();
184 let diff = target_size - list_size;
185 let n = diff / list_size;
186 let m = diff % list_size;
187 for _ in 0..n {
188 padded_list.push_str(ascii_list);
189 }
190 padded_list.push_str(&ascii_list[0..m]);
191 padded_list
192}
193
194fn ascii2density(ascii_list: &String) -> Vec<(u32, char)> {
196 let font = Vec::from(include_bytes!("../font/OpenSans-Regular.ttf") as &[u8]);
197 let font = Font::try_from_vec(font).unwrap();
198
199 let scale = Scale {
200 x: 20.0,
201 y: 20.0,
202 };
203
204 let mut density_ascii: Vec<(u32,char)> = Vec::new();
205 for ch in ascii_list.chars() {
206 let v_metrics = font.v_metrics(scale);
207 let offset = point(0.0, v_metrics.ascent);
208 let glyphs: Vec<PositionedGlyph<'_>> = font.layout(&ch.to_string(), scale, offset).collect();
209
210 for g in glyphs {
211 let mut target_pixels = 0;
212 g.draw(|_, _, _|{
213 target_pixels = target_pixels + 1;
214 });
215 density_ascii.push((target_pixels, ch));
216 }
217 }
218 density_ascii.sort_by(|a, b| (b.0).cmp(&a.0));
219 density_ascii
220}
221
222pub fn string2ascii(message: &str, height: f32, ch: char, ch2nd: Option<(usize, char)>, font_file: Option<&str>) -> Result<Char2DArray, String> {
224 let font = if let Some(file) = font_file {
225 let font_data = std::fs::read(&file).unwrap();
226 Font::try_from_vec(font_data).unwrap()
227 } else {
228 let font_data = include_bytes!("../font/OpenSans-Regular.ttf");
229 Font::try_from_bytes(font_data as &[u8]).unwrap()
230 };
231 let pixel_height = height.ceil() as usize;
232 let scale = Scale {
233 x: height * 2.0,
234 y: height,
235 };
236 let v_metrics = font.v_metrics(scale);
237 let offset = point(0.0, v_metrics.ascent);
238
239 let glyphs: Vec<_> = font.layout(&message, scale, offset).collect();
240
241 let width = glyphs
242 .iter()
243 .rev()
244 .map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
245 .next()
246 .unwrap_or(0.0)
247 .ceil() as usize;
248
249 let mut c2d = Char2DArray::new(width, pixel_height);
250 let latter = ch2nd.unwrap_or((0, ch));
251 for (i, g) in glyphs.iter().enumerate() {
252 if let Some(bb) = g.pixel_bounding_box() {
253 g.draw(|x, y, v| {
254 let mut c = ' ';
255 if v > 0.5 {
256 if i < latter.0 {
257 c = ch;
258 } else {
259 c = latter.1;
260 }
261 }
262 let x = x as i32 + bb.min.x;
263 let y = y as i32 + bb.min.y;
264 if x >= 0 && x < width as i32 && y >= 0 && y < pixel_height as i32 {
265 c2d.buffer[y as usize][x as usize] = c;
266 }
267 });
268 }
269 }
270 Ok(c2d)
271}
272
273
274pub fn image2ascii(image_file: &str, target_width: u32, contrast: Option<f32>, characters: Option<&str>) -> Result<Char2DArray, String> {
276 let density_ascii = ascii2density(&pad(characters.unwrap_or(ASCIIS), 256));
277 if let Ok(img) = image::open(image_file){
278 let img = img.adjust_contrast(contrast.unwrap_or(30.0));
280
281 let height = img.height();
283 let width = img.width();
284 let scale: f32 = target_width as f32 / width as f32;
285 let target_height: f32 = (height as f32 * scale) / 2.0;
286 let target_height = target_height as u32;
287 let resized_img = img.resize_exact(target_width, target_height, FilterType::Lanczos3);
288
289 let luma_img = resized_img.to_luma();
291
292 let mut char2d: Char2DArray = Char2DArray::new(target_width as usize, target_height as usize);
294 for (x, y, pixel) in luma_img.enumerate_pixels() {
295 let index = pixel[0] as usize;
296 char2d.buffer[y as usize][x as usize] = density_ascii[index].1;
297 }
298
299 Ok(char2d)
300 } else {
301 Err(format!("can not open file {}",image_file))
302 }
303}
304
305#[test]
306fn char2darray_new_works() {
307 let w = 10;
308 let h = 20;
309 let c2d = Char2DArray::new(w, h);
310 assert_eq!(c2d.buffer.len(), h);
311 for line in c2d.buffer.iter() {
312 assert_eq!(line.len(), w);
313 for ch in line.iter() {
314 assert_eq!(ch, &' ');
315 }
316 }
317}
318
319#[test]
320fn char2darray_to_lines_works() {
321 let w = 3;
322 let h = 2;
323 let mut c2d = Char2DArray::new(w, h);
324
325 c2d.buffer[0][0] = 'A';
326 c2d.buffer[0][1] = 'B';
327 c2d.buffer[0][2] = 'C';
328
329 c2d.buffer[1][0] = 'X';
330 c2d.buffer[1][1] = 'Y';
331 c2d.buffer[1][2] = 'Z';
332
333 let lines = c2d.to_lines();
334 assert_eq!(lines[0], String::from("ABC"));
335 assert_eq!(lines[1], String::from("XYZ"));
336}
337
338#[test]
339fn char2darray_save() {
340 let w = 3;
341 let h = 2;
342 let mut c2d = Char2DArray::new(w, h);
343
344 c2d.buffer[0][0] = 'A';
345 c2d.buffer[0][1] = 'B';
346 c2d.buffer[0][2] = 'C';
347
348 c2d.buffer[1][0] = 'X';
349 c2d.buffer[1][1] = 'Y';
350 c2d.buffer[1][2] = 'Z';
351
352 let tmp = tempfile::NamedTempFile::new().unwrap();
353 let ret = c2d.save(tmp.path().to_str().unwrap());
354 assert!(ret.is_ok());
355
356 let mut save_file = tmp.reopen().unwrap();
357 let mut data = String::new();
359 let ret = save_file.read_to_string(&mut data);
360 assert!(ret.is_ok());
361 assert_eq!(data, "ABC\nXYZ\n");
362
363}
364
365#[test]
366fn pad_works() {
367 let text = String::from("ABC");
368 let padded = pad(&text, 10);
369 assert_eq!(padded, "ABCABCABCA");
370}
371
372#[test]
373fn ascii2density_works() {
374 let asciis = String::from("A.@=");
375 let density_ascii = ascii2density(&asciis);
376 assert_eq!(density_ascii.len(), 4);
377 assert_eq!(density_ascii[0].1, '@');
378 assert_eq!(density_ascii[1].1, 'A');
379 assert_eq!(density_ascii[2].1, '=');
380 assert_eq!(density_ascii[3].1, '.');
381}
382#[test]
383fn char2darray_height_width_works() {
384 let c2d = Char2DArray::new(5, 10);
385 assert_eq!(c2d.height(), 10);
386 assert_eq!(c2d.width(), 5);
387}
388
389#[test]
390fn char2darray_from_works() {
391 let c2d = Char2DArray::from(
392 vec![
393 vec!['A', 'B', 'C'],
394 vec!['X', 'Y', 'Z'],
395 ]
396 );
397 assert_eq!(c2d.width(), 3);
398 assert_eq!(c2d.height(), 2);
399 assert_eq!(c2d.buffer[0], ['A', 'B', 'C']);
400 assert_eq!(c2d.buffer[1], ['X', 'Y', 'Z']);
401}
402
403#[test]
404fn char2darray_overwrite_rect_works() {
405 let mut base = Char2DArray::from(vec![
406 vec!['A', 'B', 'C'],
407 vec!['X', 'Y', 'Z'],
408 ]);
409 let rect = Char2DArray::from(vec![
410 vec!['1', '2'],
411 vec!['3', '4'],
412 ]);
413
414 let pos = CharPosition{x: 0, y: 0};
415 base.overwrite_rect(&rect, pos, Option::None);
416
417 assert_eq!(base.buffer[0], ['1', '2', 'C']);
418 assert_eq!(base.buffer[1], ['3', '4', 'Z']);
419
420 let mut base = Char2DArray::from(vec![
421 vec!['A', 'B', 'C'],
422 vec!['X', 'Y', 'Z'],
423 ]);
424
425 let pos = CharPosition{x: -1, y: -1};
426 base.overwrite_rect(&rect, pos, Option::None);
427
428 assert_eq!(base.buffer[0], ['4', 'B', 'C']);
429 assert_eq!(base.buffer[1], ['X', 'Y', 'Z']);
430
431 let mut base = Char2DArray::from(vec![
432 vec!['A', 'B', 'C'],
433 vec!['X', 'Y', 'Z'],
434 ]);
435
436 let pos = CharPosition{x: 1, y: 1};
437 base.overwrite_rect(&rect, pos, Option::None);
438
439 assert_eq!(base.buffer[0], ['A', 'B', 'C']);
440 assert_eq!(base.buffer[1], ['X', '1', '2']);
441
442}
443#[test]
444fn char2darray_overwrite_rect_center_works() {
445 let mut base = Char2DArray::from(vec![
446 vec!['A', 'B', 'C', 'D'],
447 vec!['E', 'F', 'G', 'H'],
448 vec!['I', 'J', 'K', 'L'],
449 vec!['M', 'N', 'O', 'P'],
450 ]);
451 let rect = Char2DArray::from(vec![
452 vec!['1', '2'],
453 vec!['3', '4'],
454 ]);
455
456 let pos = CharPosition{x: 0, y: 0};
457 base.overwrite_rect_center(&rect, pos, Option::None);
458
459 assert_eq!(base.buffer[0], ['A', 'B', 'C', 'D']);
460 assert_eq!(base.buffer[1], ['E', '1', '2', 'H']);
461 assert_eq!(base.buffer[2], ['I', '3', '4', 'L']);
462 assert_eq!(base.buffer[3], ['M', 'N', 'O', 'P']);
463
464}
465
466#[test]
467fn char2darray_overwrite_rect_center_trans_works() {
468 let mut base = Char2DArray::from(vec![
469 vec!['A', 'B', 'C', 'D'],
470 vec!['E', 'F', 'G', 'H'],
471 vec!['I', 'J', 'K', 'L'],
472 vec!['M', 'N', 'O', 'P'],
473 ]);
474 let rect = Char2DArray::from(vec![
475 vec!['\u{0000}', '2'],
476 vec!['3', '4'],
477 ]);
478
479 let pos = CharPosition{x: 0, y: 0};
480 base.overwrite_rect_center(&rect, pos, Some('\u{0000}'));
481
482 assert_eq!(base.buffer[0], ['A', 'B', 'C', 'D']);
483 assert_eq!(base.buffer[1], ['E', 'F', '2', 'H']);
484 assert_eq!(base.buffer[2], ['I', '3', '4', 'L']);
485 assert_eq!(base.buffer[3], ['M', 'N', 'O', 'P']);
486
487}
488#[test]
489fn char2darray_overwrite_line_works() {
490 let mut base = Char2DArray::from(vec![
491 vec!['A', 'B', 'C', 'D'],
492 vec!['E', 'F', 'G', 'H'],
493 vec!['I', 'J', 'K', 'L'],
494 vec!['M', 'N', 'O', 'P'],
495 ]);
496 base.overwrite_line('@', 1);
497 assert_eq!(base.buffer[0], ['A', 'B', 'C', 'D']);
498 assert_eq!(base.buffer[1], ['@', '@', '@', '@']);
499 assert_eq!(base.buffer[2], ['I', 'J', 'K', 'L']);
500 assert_eq!(base.buffer[3], ['M', 'N', 'O', 'P']);
501}
502#[test]
503fn char2darray_overwrite_char_works(){
504 let mut base = Char2DArray::from(vec![
505 vec!['A', 'B', 'C', 'D'],
506 vec!['E', 'F', 'G', 'H'],
507 vec!['I', 'J', 'K', 'L'],
508 vec!['M', 'N', 'O', 'P'],
509 ]);
510 base.overwrite_char('@', CharPosition{x: 2, y:1});
511 assert_eq!(base.buffer[0], ['A', 'B', 'C', 'D']);
512 assert_eq!(base.buffer[1], ['E', 'F', '@', 'H']);
513 assert_eq!(base.buffer[2], ['I', 'J', 'K', 'L']);
514 assert_eq!(base.buffer[3], ['M', 'N', 'O', 'P']);
515}
516
517#[test]
518fn char2darray_overwrite_fn_works(){
519 let mut base = Char2DArray::from(vec![
520 vec!['A', 'B', 'C', 'D'],
521 vec!['E', 'F', 'G', 'H'],
522 vec!['I', 'J', 'K', 'L'],
523 vec!['M', 'N', 'O', 'P'],
524 ]);
525 let threshold = 2;
526 base.overwrite_fn('@', |_, y,_| {
527 if y >= threshold {
528 true
529 }else {
530 false
531 }
532 });
533 assert_eq!(base.buffer[0], ['A', 'B', 'C', 'D']);
534 assert_eq!(base.buffer[1], ['E', 'F', 'G', 'H']);
535 assert_eq!(base.buffer[2], ['@', '@', '@', '@']);
536 assert_eq!(base.buffer[3], ['@', '@', '@', '@']);
537
538}