Skip to main content

sar_core/renderer/
resource.rs

1use std::collections::HashMap;
2
3use crate::core::result::Result;
4use image::{imageops, DynamicImage, SubImage};
5
6use crate::core::symbol::SymbolId;
7
8const SYMBOLS_R: &[u8] = include_bytes!("../../assets/symbols_r.png");
9const SYMBOLS_G: &[u8] = include_bytes!("../../assets/symbols_g.png");
10const SYMBOLS_B: &[u8] = include_bytes!("../../assets/symbols_b.png");
11const SYMBOLS_COLOR: &[u8] = include_bytes!("../../assets/symbols_color.png");
12
13const SYMBOL_PIXELS: u32 = 64;
14const SYMBOL_WIDTH_NUM: u32 = 16;
15
16#[derive(Eq, Hash, PartialEq)]
17enum ImageSheet {
18    R,
19    G,
20    B,
21    Color,
22}
23
24/// A resource manager that handles image sheets for symbol rendering.
25///
26/// This struct manages multiple image sheets (R, G, B, and Color) that contain
27/// symbols used for rendering. Each sheet is stored as a DynamicImage and can be
28/// accessed to extract individual symbol images.
29pub struct Resource {
30    /// A map of image sheets, where each sheet contains a collection of symbols
31    sheets: HashMap<ImageSheet, DynamicImage>,
32    /// The size of each symbol in pixels (width and height are equal)
33    pub symbol_pixels: u32,
34}
35
36impl Resource {
37    pub fn new() -> Result<Self> {
38        let r = image::load_from_memory(SYMBOLS_R)?;
39        let g = image::load_from_memory(SYMBOLS_G)?;
40        let b = image::load_from_memory(SYMBOLS_B)?;
41        let color = image::load_from_memory(SYMBOLS_COLOR)?;
42
43        let mut sheets = HashMap::new();
44        sheets.insert(ImageSheet::R, r);
45        sheets.insert(ImageSheet::G, g);
46        sheets.insert(ImageSheet::B, b);
47        sheets.insert(ImageSheet::Color, color);
48
49        Ok(Self {
50            sheets,
51            symbol_pixels: SYMBOL_PIXELS,
52        })
53    }
54
55    pub(crate) fn get_image(&self, id: SymbolId) -> Option<Image> {
56        let index = ImageIndex::get(id)?;
57        let sheet = self.sheets.get(&index.sheet)?;
58        let (x, y) = Self::get_coordinates(&index);
59        let image = imageops::crop_imm(sheet, x, y, SYMBOL_PIXELS, SYMBOL_PIXELS);
60        Some(Image::new_with_sheet(image, index))
61    }
62
63    fn get_coordinates(index: &ImageIndex) -> (u32, u32) {
64        let x = index.index % SYMBOL_WIDTH_NUM * SYMBOL_PIXELS;
65        let y = index.index / SYMBOL_WIDTH_NUM * SYMBOL_PIXELS;
66        (x, y)
67    }
68}
69
70pub(crate) enum Image<'a> {
71    R(SubImage<&'a DynamicImage>),
72    G(SubImage<&'a DynamicImage>),
73    B(SubImage<&'a DynamicImage>),
74    Color(SubImage<&'a DynamicImage>),
75}
76
77impl<'a> Image<'a> {
78    pub(crate) fn inner(&self) -> &SubImage<&'a DynamicImage> {
79        match self {
80            Image::R(image) => image,
81            Image::G(image) => image,
82            Image::B(image) => image,
83            Image::Color(image) => image,
84        }
85    }
86}
87
88impl std::fmt::Debug for Image<'_> {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Image::R(_) => write!(f, "R"),
92            Image::G(_) => write!(f, "G"),
93            Image::B(_) => write!(f, "B"),
94            Image::Color(_) => write!(f, "Color"),
95        }
96    }
97}
98
99impl<'a> Image<'a> {
100    fn new_with_sheet(image: SubImage<&'a DynamicImage>, index: ImageIndex) -> Self {
101        match index.sheet {
102            ImageSheet::R => Self::R(image),
103            ImageSheet::G => Self::G(image),
104            ImageSheet::B => Self::B(image),
105            ImageSheet::Color => Self::Color(image),
106        }
107    }
108}
109
110struct ImageIndex {
111    sheet: ImageSheet,
112    index: u32,
113}
114
115impl ImageIndex {
116    fn get(id: SymbolId) -> Option<Self> {
117        match id.id() {
118            id @ 1..=80 => Some(Self {
119                sheet: ImageSheet::R,
120                index: id,
121            }),
122            id @ 240..=480 => {
123                let offset = match id {
124                    305..400 => 240 + 16,
125                    _ if id > 400 => 240 + 16 * 2,
126                    _ => 240,
127                };
128                Some(Self {
129                    sheet: ImageSheet::G,
130                    index: id - offset,
131                })
132            }
133            id @ 481..=720 => {
134                let offset = match id {
135                    561..641 => 480 + 16,
136                    _ if id > 641 => 480 + 16 * 2,
137                    _ => 480,
138                };
139                Some(Self {
140                    sheet: ImageSheet::B,
141                    index: id - offset,
142                })
143            }
144            id @ 721..=768 => Some(Self {
145                sheet: ImageSheet::Color,
146                index: id - 720,
147            }),
148            _ => None,
149        }
150    }
151}
152
153#[cfg(test)]
154mod test {
155    use image::GenericImageView;
156
157    use super::*;
158
159    #[test]
160    fn test_get_image_index() {
161        let index = ImageIndex::get(SymbolId::new(1));
162        assert!(index.is_some());
163    }
164
165    #[test]
166    fn test_get_image() {
167        let resource = Resource::new().unwrap();
168        let image = resource.get_image(SymbolId::new(40)).unwrap();
169        let image2 = resource.get_image(SymbolId::new(41)).unwrap();
170        if let Image::R(image) = image {
171            assert_eq!(image.width(), SYMBOL_PIXELS);
172        } else {
173            panic!("image is not R");
174        }
175        if let Image::R(image) = image2 {
176            assert_eq!(image.width(), SYMBOL_PIXELS);
177        } else {
178            panic!("image2 is not R");
179        }
180    }
181}