sar_core/renderer/
draw.rs

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
14/// A trait defining the core rendering capabilities for SymbolArt compositions
15pub 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
24/// A drawer that renders SymbolArt compositions into images
25///
26/// The `SymbolArtDrawer` is responsible for converting SymbolArt compositions into
27/// raster images. It handles the rendering of individual layers, applying transformations,
28/// and compositing them into a final image.
29///
30/// # Examples
31///
32/// ```rust
33/// use sar_core::{SymbolArtDrawer, parse};
34/// use sar_core::renderer::draw::Drawer;
35///
36/// // Parse a SAR file into a SymbolArt instance
37/// let bytes = include_bytes!("../../../fixture/sa0a1d081b8a108bb8c9847c4cd83db662.sar");
38/// let symbol_art = parse(Vec::from(*bytes)).unwrap();
39///
40/// // Create a drawer and render the SymbolArt
41/// let drawer = SymbolArtDrawer::new();
42/// let image = drawer.draw(&symbol_art).unwrap();
43/// ```
44///
45/// # Configuration
46///
47/// The drawer can be configured with various options:
48/// - `with_raise_error`: Controls whether rendering errors should be raised or suppressed
49/// - Canvas size: Default is 256x256 pixels
50/// - `with_chunk_size`: Controls parallel processing of layers (default: 10)
51///
52/// # Performance
53///
54/// The drawer uses parallel processing to render layers efficiently. The chunk size
55/// can be adjusted to balance between parallelization overhead and throughput.
56///
57/// # Error Handling
58///
59/// By default, the drawer suppresses rendering errors and continues processing.
60/// This can be changed using `with_raise_error(true)` to make errors fatal.
61///
62/// # Resource Management
63///
64/// The drawer maintains a cache of symbol resources to improve rendering performance.
65/// These resources are loaded when the drawer is created and shared across all
66/// rendering operations.
67pub 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        // Assert
289        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        // Assert
305        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}