pixel_sort/render/
solver.rs1use crate::{Result, Vrellis, VrellisCanvas, VrellisPoint};
2use image::{imageops::FilterType, io::Reader, DynamicImage, GenericImageView, GrayImage, Luma};
3use std::{io::Cursor, path::Path};
4
5impl Vrellis {
6 pub fn render_path(&self, path: impl AsRef<Path>) -> Result<VrellisCanvas> {
7 let img = Reader::open(path)?.decode()?;
8 Ok(self.render(img))
9 }
10 pub fn render_bytes(&self, bytes: &[u8]) -> Result<VrellisCanvas> {
11 let img = Reader::new(Cursor::new(bytes)).decode()?;
12 Ok(self.render(img))
13 }
14 pub fn render(&self, img: DynamicImage) -> VrellisCanvas {
15 let img = img.resize_exact(1000, 1000, FilterType::Triangle);
16 let canvas = match self.inverted_color {
17 true => DynamicImage::new_luma_a8(img.width(), img.height()),
18 false => DynamicImage::new_luma_a8(img.width(), img.height()),
19 };
20 let points_sample = self.convex_shape.sample(self.points, img.width(), img.height());
21 let initial_point = match self.inverted_color {
22 true => points_sample.iter().min_by_key(|p| p.n).unwrap(),
23 false => points_sample.iter().min_by_key(|p| p.n).unwrap(),
24 };
25 let mut current_composite_image = img.to_luma();
26 quantify_color(&mut current_composite_image);
27 VrellisCanvas {
28 algorithm: self.algorithm,
29 min_distance: self.min_distance,
30 inverted_color: self.inverted_color,
31 target_image: img.to_rgb(),
32 current_image: canvas.to_luma_alpha(),
33 current_composite_image,
34 points: points_sample.clone(),
35 path: vec![initial_point.n],
36 path_banned: Default::default(),
37 last_point: initial_point.clone(),
38 line_width: self.line_width,
39 }
40 }
41}
42
43impl Iterator for VrellisCanvas {
44 type Item = DynamicImage;
45 fn next(&mut self) -> Option<Self::Item> {
46 let mut selected = None;
47 let mut max_score = 0.0;
48 for point in self.points.iter() {
49 if self.should_skip(point) {
50 continue;
51 }
52 let score = self.algorithm.line_score(
53 &self.current_composite_image,
54 self.last_point.x,
55 self.last_point.y,
56 point.x,
57 point.y,
58 self.inverted_color,
59 );
60 if score > max_score {
61 max_score = score;
62 selected = Some(point)
63 }
64 }
65 if let None = selected {
67 return None;
68 };
69 let selected = selected.unwrap().clone();
70 self.algorithm.draw_line(
71 &mut self.current_composite_image,
72 self.last_point.x,
73 self.last_point.y,
74 selected.x,
75 selected.y,
76 self.inverted_color,
77 );
78 self.draw_canvas_line(self.last_point.x, self.last_point.y, selected.x, selected.y, self.inverted_color);
79 self.last_point = selected;
80 let new = selected.n;
81 let old = *self.path.last().unwrap();
82 self.path.push(new);
83 self.path_banned.insert((new, old));
84 self.path_banned.insert((old, new));
85 return Some(DynamicImage::ImageLumaA8(self.current_image.clone()));
86 }
87}
88
89impl VrellisCanvas {
90 fn should_skip(&self, this: &VrellisPoint) -> bool {
91 if self.last_point.x == this.x || self.last_point.y == this.y {
92 return true;
93 }
94 let old = self.last_point.n;
95 let this = this.n;
96 if old == this {
97 true
98 }
99 else if old > this && old - this <= self.min_distance {
100 true
101 }
102 else if self.path_banned.contains(&(old, this)) {
103 true
104 }
105 else {
106 false
107 }
108 }
109}
110
111pub fn quantify_color(img: &mut GrayImage) {
112 for p in img.pixels_mut() {
113 unsafe { nearest_color(p) }
114 }
115}
116
117pub unsafe fn nearest_color(pixel: &mut Luma<u8>) {
118 let colors = &[0u8, 64, 128, 192, 255];
119 let pixel = pixel.0.get_unchecked_mut(0);
120 let raw = *pixel;
121 let mut min_delta = 255;
122 for &item in colors.iter().rev() {
123 let mid = if raw >= item { raw - item } else { item - raw };
124 if mid < min_delta {
125 min_delta = mid;
127 *pixel = item
128 }
129 }
130 }