mini_oled/screen/
canvas.rs

1//! # Canvas
2//!
3//! The `Canvas` module provides the drawing surface for the display.
4//! It handles the pixel buffer, dirty area tracking for efficient updates, and
5//! integration with `embedded-graphics` if the feature is enabled.
6//!
7//! ## Example
8//!
9//! ```rust,ignore
10//! use mini_oled::screen::canvas::Canvas;
11//! // Canvas is normally obtained from the display driver, not created directly.
12//! // let canvas = display.get_mut_canvas();
13//!
14//! // set_pixel(x, y, on/off)
15//! // canvas.set_pixel(10, 20, true);
16//! ```
17
18use crate::screen::fast_mul;
19
20use crate::error::MiniOledError;
21
22use crate::screen::properties::{DisplayProperties, DisplayRotation};
23
24/// A drawing canvas that manages the pixel buffer and dirty area tracking.
25///
26/// # Example
27///
28/// ```rust,ignore
29/// // Assuming you have a canvas instance from the Sh1106 driver
30/// // let mut canvas = screen.get_mut_canvas();
31///
32/// // Set a pixel
33/// canvas.set_pixel(10, 20, true);
34///
35/// // Access raw buffer
36/// let buffer = canvas.get_buffer();
37/// ```
38pub struct Canvas<const N: usize, const W: u32, const H: u32, const O: u8> {
39    buffer: [u8; N],
40    dirty_area_min: (u32, u32),
41    dirty_area_max: (u32, u32),
42    display_properties: DisplayProperties<W, H, O>,
43}
44
45impl<const N: usize, const W: u32, const H: u32, const O: u8> Canvas<N, W, H, O> {
46    pub(crate) fn new(display_properties: DisplayProperties<W, H, O>) -> Self {
47        Canvas {
48            buffer: [0; N],
49            dirty_area_max: (0, 0),
50            dirty_area_min: display_properties.get_display_size(),
51            display_properties,
52        }
53    }
54
55    pub(crate) fn get_column_offset(&self) -> u8 {
56        self.display_properties.get_column_offset()
57    }
58
59    pub(crate) const fn get_display_size(&self) -> (u32, u32) {
60        self.display_properties.get_display_size()
61    }
62
63    pub(crate) fn get_rotation(&self) -> &DisplayRotation {
64        self.display_properties.get_rotation()
65    }
66
67    pub(crate) fn set_rotation(&mut self, display_rotation: DisplayRotation) {
68        self.display_properties.set_rotation(display_rotation);
69    }
70
71    /// Returns a reference to the pixel buffer.
72    pub fn get_buffer(&self) -> &[u8; N] {
73        &self.buffer
74    }
75
76    /// Returns a mutable reference to the pixel buffer.
77    pub fn get_mut_buffer(&mut self) -> &mut [u8; N] {
78        &mut self.buffer
79    }
80
81    pub(crate) fn get_dirty_area(&self) -> ((u32, u32), (u32, u32)) {
82        (self.dirty_area_min, self.dirty_area_max)
83    }
84
85    pub(crate) fn force_full_dirty_area(&mut self) {
86        self.dirty_area_min = (0, 0);
87        self.dirty_area_max = (W - 1, H - 1);
88    }
89
90    pub(crate) fn reset_dirty_area(&mut self) {
91        self.dirty_area_min = self.display_properties.get_display_size();
92        self.dirty_area_max = (0, 0);
93    }
94
95    #[inline]
96    /// Sets the state of a single pixel.
97    ///
98    /// # Arguments
99    ///
100    /// * `x` - The X coordinate of the pixel.
101    /// * `y` - The Y coordinate of the pixel.
102    /// * `pixel_status` - `true` to turn the pixel on, `false` to turn it off.
103    pub fn set_pixel(&mut self, x: u32, y: u32, pixel_status: bool) {
104        let (physical_width, physical_height) = self.display_properties.get_display_size();
105        let display_rotation = self.display_properties.get_rotation();
106
107        let (calculated_width_for_rotation, calculated_height_for_rotation) = match display_rotation
108        {
109            DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
110                (physical_width, physical_height)
111            }
112            DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
113                (physical_height, physical_width)
114            }
115        };
116
117        if x >= calculated_width_for_rotation || y >= calculated_height_for_rotation {
118            return;
119        }
120
121        if x < self.dirty_area_min.0 {
122            self.dirty_area_min.0 = x;
123        }
124        if y < self.dirty_area_min.1 {
125            self.dirty_area_min.1 = y;
126        }
127        if x > self.dirty_area_max.0 {
128            self.dirty_area_max.0 = x;
129        }
130        if y > self.dirty_area_max.1 {
131            self.dirty_area_max.1 = y;
132        }
133
134        let (idx, bit_mask) = match *display_rotation {
135            DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
136                let idx = fast_mul!((y >> 3), W) + x; // y >> 3 is equal to y / 8
137                let bit = 1 << (y & 7); // y & 7 is equal to y % 8
138                (idx as usize, bit)
139            }
140            DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
141                let idx = fast_mul!((x >> 3), W) + y; // y >> 3 is equal to y / 8
142                let bit = 1 << (x & 7); // y & 7 is equal to y % 8
143                (idx as usize, bit)
144            }
145        };
146        /*
147           match pixel_status {
148               true => self.buffer[idx as usize] |= bit_mask,
149               false => self.buffer[idx as usize] &= !bit_mask,
150           }
151           It's same to above code, it's better for branching but not reading
152        */
153        if idx < N {
154            let pixel_status_mask = (-(pixel_status as i8)) as u8;
155            self.buffer[idx] = (self.buffer[idx] & !bit_mask) | (pixel_status_mask & bit_mask);
156        }
157    }
158}
159#[cfg(feature = "embedded-graphics-core")]
160use embedded_graphics_core::{
161    Pixel,
162    pixelcolor::BinaryColor,
163    prelude::{Dimensions, DrawTarget, OriginDimensions, Size},
164};
165
166#[cfg(feature = "embedded-graphics-core")]
167impl<const N: usize, const W: u32, const H: u32, const O: u8> DrawTarget for Canvas<N, W, H, O> {
168    type Color = BinaryColor;
169
170    type Error = MiniOledError;
171
172    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
173    where
174        I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
175    {
176        let bb = self.bounding_box();
177
178        pixels
179            .into_iter()
180            .filter(|Pixel(pos, _color)| bb.contains(*pos))
181            .for_each(|Pixel(pos, color)| {
182                self.set_pixel(pos.x as u32, pos.y as u32, color.is_on())
183            });
184
185        Ok(())
186    }
187}
188
189#[cfg(feature = "embedded-graphics-core")]
190impl<const N: usize, const W: u32, const H: u32, const O: u8> OriginDimensions
191    for Canvas<N, W, H, O>
192{
193    fn size(&self) -> Size {
194        let (width, height) = self.display_properties.get_display_size();
195
196        Size::new(width, height)
197    }
198}