1use crate::{core::sa::Color, Result};
2use image::{imageops, GenericImage, ImageBuffer, Pixel, Rgba, RgbaImage};
3use imageproc::geometric_transformations::Projection;
4use std::sync::mpsc;
5
6use crate::core::{
7 result::SARError,
8 sa::{SymbolArt, SymbolArtLayer},
9};
10use rayon::prelude::*;
11
12use super::resource::{self};
13
14pub trait Drawer<S, L>
16where
17 S: SymbolArt<Layer = L>,
18 L: SymbolArtLayer,
19{
20 fn draw(&self, sa: &S) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>>;
21 fn draw_with_scale(&self, sa: &S, scale: f32) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>>;
22}
23
24pub struct SymbolArtDrawer {
68 resource: resource::Resource,
69 canvas_size: (u32, u32),
70 chunk_size: usize,
71 suppress_failure: bool,
72}
73
74impl SymbolArtDrawer {
75 pub fn new() -> Self {
76 let resource = resource::Resource::new().unwrap();
77 let canvas_size = (256, 256);
78
79 Self {
80 resource,
81 canvas_size,
82 chunk_size: 10,
83 suppress_failure: true,
84 }
85 }
86
87 pub fn with_raise_error(mut self, raise_error: bool) -> Self {
88 self.suppress_failure = !raise_error;
89 self
90 }
91
92 pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
93 self.chunk_size = chunk_size;
94 self
95 }
96
97 fn calc_canvas_size(&self, scale: f32) -> (u32, u32) {
98 (
99 (self.canvas_size.0 as f32 * scale) as u32,
100 (self.canvas_size.1 as f32 * scale) as u32,
101 )
102 }
103
104 fn calc_view_size<S>(sa: &S, scale: f32) -> (u32, u32)
105 where
106 S: SymbolArt,
107 {
108 (
109 (sa.width() as f32 * scale) as u32,
110 (sa.height() as f32 * scale) as u32,
111 )
112 }
113
114 fn get_projection<L>(&self, layer: &L, scale: f32) -> Result<Projection>
115 where
116 L: SymbolArtLayer,
117 {
118 let top_left = layer.top_left();
119 let bottom_left = layer.bottom_left();
120 let top_right = layer.top_right();
121 let bottom_right = layer.bottom_right();
122
123 let symbol_width = self.resource.symbol_pixels as f32;
124 let from = [
125 (0.0, 0.0),
126 (symbol_width, 0.0),
127 (symbol_width, symbol_width),
128 (0.0, symbol_width),
129 ];
130 let to = [
131 (top_left.x as f32 * scale, top_left.y as f32 * scale),
132 (top_right.x as f32 * scale, top_right.y as f32 * scale),
133 (bottom_right.x as f32 * scale, bottom_right.y as f32 * scale),
134 (bottom_left.x as f32 * scale, bottom_left.y as f32 * scale),
135 ];
136
137 let projection =
138 imageproc::geometric_transformations::Projection::from_control_points(from, to)
139 .ok_or(SARError::ProjectionError(from, to))?;
140
141 Ok(projection)
142 }
143
144 fn render_symbol(base: &mut RgbaImage, symbol: &mut RgbaImage, color: RenderColor) {
145 for (x, y, pixel) in base.enumerate_pixels_mut() {
146 let symbol_pixel = symbol.get_pixel(x, y);
147 if symbol_pixel[3] > 0 {
148 match color {
149 RenderColor::Color(color) => pixel.blend(&color.into()),
150 RenderColor::None => {
151 pixel.blend(symbol_pixel);
152 }
153 }
154 }
155 }
156 }
157}
158
159enum RenderColor {
160 Color(Color),
161 None,
162}
163
164impl Default for SymbolArtDrawer {
165 fn default() -> Self {
166 Self {
167 resource: resource::Resource::new().unwrap(),
168 canvas_size: (256, 256),
169 chunk_size: 10,
170 suppress_failure: true,
171 }
172 }
173}
174
175impl<S, L> Drawer<S, L> for SymbolArtDrawer
176where
177 S: SymbolArt<Layer = L>,
178 L: SymbolArtLayer + Sync,
179{
180 fn draw(&self, sa: &S) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
181 self.draw_with_scale(sa, 1.0)
182 }
183
184 fn draw_with_scale(&self, sa: &S, scale: f32) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
185 let canvas_size = self.calc_canvas_size(scale);
186 let mut canvas = RgbaImage::from_pixel(canvas_size.0, canvas_size.1, image::Rgba([0; 4]));
187
188 let (tx, rx) = mpsc::channel();
189 let mut overlays = sa
190 .layers()
191 .par_chunks(self.chunk_size)
192 .rev()
193 .enumerate()
194 .filter_map(|(i, chunk)| {
195 let tx = tx.clone();
196 let mut canvas = RgbaImage::new(canvas_size.0, canvas_size.1);
197 for layer in chunk.iter().rev() {
198 if layer.is_hidden() {
199 continue;
200 }
201
202 let image = match self.resource.get_image(layer.symbol().id()) {
203 Some(image) => image,
204 None => {
205 if self.suppress_failure {
206 continue;
207 }
208
209 tx.send(SARError::SymbolNotFound(layer.symbol().id()))
210 .unwrap();
211 return None;
212 }
213 };
214
215 let mut symbol = RgbaImage::new(canvas_size.0, canvas_size.1);
216 let projection = match self.get_projection(layer, scale) {
217 Ok(projection) => projection,
218 Err(e) => {
219 if self.suppress_failure {
220 continue;
221 }
222
223 tx.send(e).unwrap();
224 return None;
225 }
226 };
227
228 imageproc::geometric_transformations::warp_into(
229 &image.inner().to_image(),
230 &projection,
231 imageproc::geometric_transformations::Interpolation::Nearest,
232 image::Rgba([0; 4]),
233 &mut symbol,
234 );
235
236 if let resource::Image::Color(_) = image {
237 SymbolArtDrawer::render_symbol(&mut canvas, &mut symbol, RenderColor::None);
238 } else {
239 SymbolArtDrawer::render_symbol(
240 &mut canvas,
241 &mut symbol,
242 RenderColor::Color(layer.color()),
243 );
244 }
245 }
246
247 Some((i, canvas))
248 })
249 .collect::<Vec<_>>();
250
251 drop(tx);
252 if let Ok(e) = rx.recv() {
253 return Err(e);
254 }
255
256 overlays.sort_by_key(|(i, _)| *i);
257 for (_, overlay) in overlays {
258 imageops::overlay(&mut canvas, &overlay, 0, 0);
259 }
260
261 let view_size = Self::calc_view_size(sa, scale);
262 Ok(canvas
263 .sub_image(
264 canvas_size.0 / 2 - view_size.0 / 2,
265 canvas_size.1 / 2 - view_size.1 / 2,
266 view_size.0,
267 view_size.1,
268 )
269 .to_image())
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use image::codecs::png::PngEncoder;
276
277 use super::*;
278 use crate::{parse, test::RAW_FILE};
279
280 #[test]
281 fn test_drawer() {
282 let bytes = Vec::from(RAW_FILE);
283 let sa = parse(bytes).unwrap();
284
285 let drawer = SymbolArtDrawer::new().with_raise_error(true);
286 let image = drawer.draw(&sa).unwrap();
287
288 let mut buff = Vec::new();
290 image
291 .write_with_encoder(PngEncoder::new(&mut buff))
292 .unwrap();
293 assert_eq!(buff.len(), include_bytes!("fixture/test.png").len());
294 }
295
296 #[test]
297 fn test_drawer_with_scale() {
298 let bytes = Vec::from(RAW_FILE);
299 let sa = parse(bytes).unwrap();
300
301 let drawer = SymbolArtDrawer::default();
302 let image = drawer.draw_with_scale(&sa, 2.0).unwrap();
303
304 let mut buff = Vec::new();
306 image
307 .write_with_encoder(PngEncoder::new(&mut buff))
308 .unwrap();
309 assert_eq!(buff.len(), include_bytes!("fixture/testx2.png").len());
310 }
311}