pmd_wan/
image_to_wan.rs

1use crate::{
2    encode_fragment_pixels, Fragment, FragmentBytes, FragmentFlip, Frame, GeneralResolution,
3    ImageBuffer, OamShape, WanImage,
4};
5use anyhow::{bail, Context};
6use std::convert::TryInto;
7
8pub fn insert_frame_in_wanimage(
9    image: Vec<u8>,
10    width: u16,
11    height: u16,
12    wanimage: &mut WanImage,
13    pal_id: u16,
14) -> anyhow::Result<Option<usize>> {
15    if height >= 256 {
16        bail!("The height of the image is {}, while only image with a height inferior to 256 can be used", height);
17    }
18    if width >= 512 {
19        bail!(
20            "The width of the image is {}, while only image with a width less than 512 can be used",
21            width
22        );
23    }
24    let position_x = -(width as i32) / 2;
25    let position_y = -(height as i32) / 2;
26    let image_buffer = ImageBuffer::new_from_vec(image, width, height)
27        .context("The input image don't correspond to the dimension of it")?;
28
29    let fragments = if let Some(fragments) =
30        insert_fragment_pos_in_wan_image(wanimage, pal_id, &image_buffer, position_x, position_y)?
31    {
32        fragments
33    } else {
34        return Ok(None);
35    };
36
37    Ok(if !fragments.is_empty() {
38        let frame_id = wanimage.frame_store.frames.len();
39        wanimage.frame_store.frames.push(Frame {
40            fragments,
41            frame_offset: None,
42        });
43        Some(frame_id)
44    } else {
45        None
46    })
47}
48
49fn insert_fragment_pos_in_wan_image(
50    wanimage: &mut WanImage,
51    pal_id: u16,
52    image_buffer: &ImageBuffer,
53    upper_image_x: i32,
54    upper_image_y: i32,
55) -> anyhow::Result<Option<Vec<Fragment>>> {
56    let mut fragments = Vec::new();
57
58    // Chunk the image into 64x64 group, the max meta frame size
59    const MAX_META_FRAME_SIZE: u16 = 64;
60    for fragment_segment_x in
61        0..(image_buffer.width() + MAX_META_FRAME_SIZE - 1) / MAX_META_FRAME_SIZE
62    {
63        for fragment_segment_y in
64            0..(image_buffer.height() + MAX_META_FRAME_SIZE - 1) / MAX_META_FRAME_SIZE
65        {
66            let mut fragment_x =
67                upper_image_x + MAX_META_FRAME_SIZE as i32 * fragment_segment_x as i32;
68            let mut fragment_y =
69                upper_image_y + MAX_META_FRAME_SIZE as i32 * fragment_segment_y as i32;
70
71            let mut cut_section = image_buffer.get_fragment(
72                MAX_META_FRAME_SIZE * fragment_segment_x,
73                MAX_META_FRAME_SIZE * fragment_segment_y,
74                MAX_META_FRAME_SIZE,
75                MAX_META_FRAME_SIZE,
76                0,
77            );
78            fragment_y += cut_section.cut_top() as i32;
79            cut_section.cut_bottom();
80            fragment_x += cut_section.cut_left() as i32;
81            cut_section.cut_right();
82
83            if !cut_section.have_pixel() {
84                continue;
85            }
86
87            //no panic: resolution should always be less than 64x64, and be an already valid resolution, to which it can fall back if no smaller images are avalaible
88            let fragment_size = OamShape::find_smallest_containing(GeneralResolution::new(
89                cut_section.width().into(),
90                cut_section.height().into(),
91            ))
92            .unwrap();
93
94            let buffer_to_write = cut_section.get_fragment(
95                0,
96                0,
97                fragment_size.size().x as u16,
98                fragment_size.size().y as u16,
99                0,
100            );
101
102            let image_bytes_index = wanimage.fragment_bytes_store.fragment_bytes.len();
103            wanimage
104                .fragment_bytes_store
105                .fragment_bytes
106                .push(FragmentBytes {
107                    mixed_pixels: encode_fragment_pixels(
108                        buffer_to_write.buffer(),
109                        fragment_size.size(),
110                    )
111                    .context("failed to encode the input byte. This is an internal error")?,
112                    z_index: 1,
113                });
114
115            let offset_y = fragment_y.try_into().context("The image is too large")?;
116            fragments.push(Fragment {
117                unk1: 0,
118                unk3_4: None,
119                unk5: false,
120                fragment_bytes_index: image_bytes_index,
121                offset_y,
122                offset_x: fragment_x.try_into().context("The image is too high")?,
123                flip: FragmentFlip::standard(),
124                is_mosaic: false,
125                pal_idx: pal_id,
126                resolution: fragment_size,
127            });
128        }
129    }
130
131    if fragments.is_empty() {
132        return Ok(None);
133    }
134
135    Ok(Some(fragments))
136}
137
138#[test]
139fn imagebuffer_cut_test() {
140    // (image_buffer, x_src, y_src, target_buffer, x_target, y_target, cut_top, cut_bottom, cut_left, cut_right)
141    #[rustfmt::skip]
142    #[allow(clippy::type_complexity)]
143    let tests_to_perform: [(Vec<u8>, u16, u16, Vec<u8>, u16, u16, usize, usize, usize, usize); 2] = [
144        (
145            vec![
146                0, 0, 0, 0, 0, 0,
147                0, 0, 0, 0, 0, 0,
148                0, 0, 1, 1, 1, 0,
149                0, 0, 0, 0, 1, 0,
150                0, 0, 0, 0, 0, 0
151            ], 6, 5,
152            vec![
153                1, 1, 1,
154                0, 0, 1
155            ], 3, 2,
156            2, 1, 2, 1
157        ),
158        (
159            vec![
160                1, 1, 1,
161                1, 1, 1,
162                1, 1, 1
163            ], 3, 3,
164            vec![
165                1, 1, 1,
166                1, 1, 1,
167                1, 1, 1
168            ], 3, 3,
169            0, 0, 0, 0
170        )
171    ];
172    for (
173        buffer_src,
174        x_src,
175        y_src,
176        buffer_target,
177        x_target,
178        y_target,
179        cut_top_px,
180        cut_bottom_px,
181        cut_left_px,
182        cut_right_px,
183    ) in tests_to_perform
184    {
185        let mut image_src = ImageBuffer::new_from_vec(buffer_src, x_src, y_src).unwrap();
186        let image_target = ImageBuffer::new_from_vec(buffer_target, x_target, y_target).unwrap();
187        assert_eq!(cut_top_px, image_src.cut_top());
188        assert_eq!(cut_bottom_px, image_src.cut_bottom());
189        assert_eq!(cut_left_px, image_src.cut_left());
190        assert_eq!(cut_right_px, image_src.cut_right());
191        assert_eq!(image_src, image_target);
192    }
193
194    //test with all 0 pixels
195    let mut image_src = ImageBuffer::new_from_vec(vec![0; 4], 2, 2).unwrap();
196    image_src.cut_top();
197    image_src.cut_bottom();
198    image_src.cut_right();
199    image_src.cut_right();
200    assert_eq!(image_src, ImageBuffer::new_from_vec(vec![], 0, 0).unwrap());
201}
202
203#[test]
204fn get_image_fragment_test() {
205    let image_buffer = ImageBuffer::new_from_vec(vec![1, 1, 0, 1, 2, 3, 1, 3, 0], 3, 3).unwrap();
206    let fragment = image_buffer.get_fragment(1, 1, 3, 2, 0);
207    assert_eq!(
208        fragment,
209        ImageBuffer::new_from_vec(vec![2, 3, 0, 3, 0, 0], 3, 2).unwrap()
210    );
211}
212
213#[test]
214fn insert_frame_flat_test() {
215    let mut wanimage = WanImage::new(crate::SpriteType::PropsUI);
216    wanimage.palette.palette.push([255, 255, 255, 128]);
217    let frame_id = insert_frame_in_wanimage(vec![1; 36], 6, 6, &mut wanimage, 0)
218        .unwrap()
219        .unwrap();
220    let frame = &wanimage.frame_store.frames[frame_id];
221    let fragment = &frame.fragments[0];
222    assert_eq!(fragment.resolution, OamShape::new(0, 0).unwrap());
223    assert_eq!(fragment.pal_idx, 0);
224}
225
226#[test]
227fn insert_empty_image_test() {
228    let mut wanimage = WanImage::new(crate::SpriteType::PropsUI);
229    assert!(insert_frame_in_wanimage(vec![0; 4], 2, 2, &mut wanimage, 0)
230        .unwrap()
231        .is_none());
232}