devotee_backend_softbuffer/
lib.rs

1#![deny(missing_docs)]
2
3//! [Softbuffer](https://crates.io/crates/softbuffer)-based backend for the [devotee](https://crates.io/crates/devotee) project.
4
5use std::num::NonZeroU32;
6
7use devotee_backend::winit::dpi::PhysicalPosition;
8use devotee_backend::winit::window::Window;
9use devotee_backend::{Backend, BackendImage, Converter};
10use softbuffer::{Context, Surface};
11
12/// [Softbuffer](https://crates.io/crates/softbuffer)-based backend.
13pub struct SoftbufferBackend {
14    surface: Surface,
15}
16
17impl Backend for SoftbufferBackend {
18    fn new(window: &Window, resolution: (u32, u32), scale: u32) -> Option<Self> {
19        // SAFETY: context and window are stored in the same struct, so the window will be valid for all context lifetime.
20        let context = unsafe { Context::new(&window) }.ok()?;
21        // SAFETY: surface, context and window are in the same struct.
22        // Surface will be valid for a proper lifetime.
23        let mut surface = unsafe { Surface::new(&context, &window) }.ok()?;
24        // SAFETY: arguments won't be zero, checked in advance.
25        unsafe {
26            surface.resize(
27                NonZeroU32::new_unchecked(resolution.0 * scale),
28                NonZeroU32::new_unchecked(resolution.1 * scale),
29            )
30        }
31        .ok()?;
32
33        Some(SoftbufferBackend { surface })
34    }
35
36    fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Option<()> {
37        self.surface.resize(width, height).ok()
38    }
39
40    fn draw_image<'a, P: 'a, I>(
41        &mut self,
42        image: &'a dyn BackendImage<'a, P, Iterator = I>,
43        converter: &dyn Converter<Palette = P>,
44        window: &Window,
45        background: u32,
46    ) -> Option<()>
47    where
48        I: Iterator<Item = &'a P>,
49    {
50        let mut buffer = self.surface.buffer_mut().ok()?;
51
52        let surface_size = window.inner_size();
53
54        if buffer.len() != (surface_size.width * surface_size.height) as usize {
55            return Some(());
56        }
57
58        buffer.fill(background);
59
60        let scale_x = surface_size.width / image.width();
61        let scale_y = surface_size.height / image.height();
62
63        let minimal_scale = scale_x.min(scale_y);
64
65        if minimal_scale < 1 {
66        } else {
67            let start_x = (surface_size.width - image.width() * minimal_scale) as usize / 2;
68            let start_y = (surface_size.height - image.height() * minimal_scale) as usize / 2;
69
70            for y in 0..image.height() {
71                for x in 0..image.width() {
72                    // Safety: we are sure that we are in a proper range due to for loops proper arguments.
73                    let pixel = unsafe { image.pixel_unsafe(x, y) };
74                    let rgb = converter.convert(pixel);
75
76                    for iy in 0..minimal_scale {
77                        let index = (start_x + (x * minimal_scale) as usize)
78                            + (iy as usize + start_y + (y * minimal_scale) as usize)
79                                * surface_size.width as usize;
80
81                        buffer[index..index + minimal_scale as usize].fill(rgb);
82                    }
83                }
84            }
85        }
86
87        buffer.present().ok()
88    }
89
90    fn window_pos_to_inner(
91        &self,
92        position: PhysicalPosition<f64>,
93        window: &Window,
94        resolution: (u32, u32),
95    ) -> Result<(i32, i32), (i32, i32)> {
96        let size = window.inner_size();
97        let scale_x = size.width / resolution.0;
98        let scale_y = size.height / resolution.1;
99
100        let minimal_scale = scale_x.min(scale_y);
101
102        if minimal_scale < 1 {
103            Err((0, 0))
104        } else {
105            let position = (position.x as i32, position.y as i32);
106            let start_x = ((size.width - resolution.0 * minimal_scale) / 2) as i32;
107            let start_y = ((size.height - resolution.1 * minimal_scale) / 2) as i32;
108
109            let position = (
110                (position.0 - start_x) / minimal_scale as i32,
111                (position.1 - start_y) / minimal_scale as i32,
112            );
113
114            if position.0 < 0
115                || position.0 >= resolution.0 as i32
116                || position.1 < 0
117                || position.1 >= resolution.1 as i32
118            {
119                Err(position)
120            } else {
121                Ok(position)
122            }
123        }
124    }
125}