Skip to main content

epd_datafuri/graphics/
display290_gray4_mfgn.rs

1//! Grayscale graphics buffer for the SSD1680-based 2.9" display (ThinkInk MFGN / MagTag 2025).
2use display_interface::DisplayError;
3use embedded_graphics::{
4    pixelcolor::{BinaryColor, Gray2},
5    prelude::*,
6};
7
8use crate::graphics::buffer_len;
9use crate::graphics::DisplayRotation;
10
11/// Display width of the MFGN 2.9" panel in pixels.
12pub const WIDTH: u16 = 128;
13/// Display height of the MFGN 2.9" panel in pixels.
14pub const HEIGHT: u16 = 296;
15
16/// Grayscale (2-bit) graphics buffer for the SSD1680-based 2.9" MFGN display.
17///
18/// Encodes 4-level grayscale across two RAM planes (BW and Red). The SSD1680
19/// expects inverted polarity in Gray2 mode, so stored bits are the logical
20/// complement of the Gray2 storage value:
21///
22/// | Gray2 | BW RAM (high) | Red RAM (low) | Displayed |
23/// |-------|---------------|---------------|-----------|
24/// | 0     | 1             | 1             | Black     |
25/// | 1     | 1             | 0             | Light gray|
26/// | 2     | 0             | 1             | Dark gray |
27/// | 3     | 0             | 0             | White     |
28///
29/// Pass [`Display2in9Gray2::high_buffer`] to `update_gray2_and_display` as the
30/// BW buffer and [`Display2in9Gray2::low_buffer`] as the Red buffer.
31pub struct Display2in9Gray2 {
32    high_buffer: [u8; buffer_len(WIDTH as usize, HEIGHT as usize)],
33    low_buffer: [u8; buffer_len(WIDTH as usize, HEIGHT as usize)],
34    rotation: DisplayRotation,
35}
36
37impl Default for Display2in9Gray2 {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl Display2in9Gray2 {
44    /// Create a new grayscale buffer initialised to white.
45    pub fn new() -> Self {
46        // Inverted polarity: stored 0x00 = logical white.
47        Self {
48            high_buffer: [0x00; buffer_len(WIDTH as usize, HEIGHT as usize)],
49            low_buffer: [0x00; buffer_len(WIDTH as usize, HEIGHT as usize)],
50            rotation: DisplayRotation::Rotate270,
51        }
52    }
53
54    /// Get a reference to the high buffer (BW RAM plane).
55    pub fn high_buffer(&self) -> &[u8] {
56        &self.high_buffer
57    }
58
59    /// Get a reference to the low buffer (Red RAM plane).
60    pub fn low_buffer(&self) -> &[u8] {
61        &self.low_buffer
62    }
63
64    /// Get mutable access to the high buffer.
65    pub fn get_mut_high_buffer(&mut self) -> &mut [u8] {
66        &mut self.high_buffer
67    }
68
69    /// Get mutable access to the low buffer.
70    pub fn get_mut_low_buffer(&mut self) -> &mut [u8] {
71        &mut self.low_buffer
72    }
73
74    /// Set the rotation used for coordinate transforms.
75    pub fn set_rotation(&mut self, rotation: DisplayRotation) {
76        self.rotation = rotation;
77    }
78
79    /// Get the current rotation.
80    pub fn rotation(&self) -> DisplayRotation {
81        self.rotation
82    }
83
84    /// Clear both buffers to the given grayscale level.
85    pub fn clear_buffer(&mut self, level: Gray2) {
86        let storage = level.into_storage();
87        // MSB of storage → BW RAM (high), LSB → Red RAM (low); bits inverted for SSD1680.
88        let bw_byte = if (storage & 0b10) != 0 { 0x00 } else { 0xFF };
89        let red_byte = if (storage & 0b01) != 0 { 0x00 } else { 0xFF };
90
91        self.high_buffer.fill(bw_byte);
92        self.low_buffer.fill(red_byte);
93    }
94
95    /// Set a single pixel to the given 2-bit grayscale level.
96    pub fn set_pixel(&mut self, x: i32, y: i32, level: Gray2) {
97        if super::outside_display(Point::new(x, y), WIDTH.into(), HEIGHT.into(), self.rotation) {
98            return;
99        }
100        let (idx_u32, bit) = super::find_position(
101            x as u32,
102            y as u32,
103            WIDTH.into(),
104            HEIGHT.into(),
105            self.rotation,
106        );
107        let idx = idx_u32 as usize;
108
109        let storage = level.into_storage();
110        // MSB → BW RAM (high), LSB → Red RAM (low); inverted polarity for SSD1680.
111        let bw_val = (storage & 0b10) == 0; // inverted: set when MSB is 0
112        let red_val = (storage & 0b01) == 0; // inverted: set when LSB is 0
113
114        if bw_val {
115            self.high_buffer[idx] |= bit;
116        } else {
117            self.high_buffer[idx] &= !bit;
118        }
119
120        if red_val {
121            self.low_buffer[idx] |= bit;
122        } else {
123            self.low_buffer[idx] &= !bit;
124        }
125    }
126
127    /// Get an adapter that implements `DrawTarget<BinaryColor>`.
128    pub fn as_binary_draw_target(&mut self) -> BinaryDrawTarget<'_> {
129        BinaryDrawTarget::new(self)
130    }
131}
132
133impl DrawTarget for Display2in9Gray2 {
134    type Error = DisplayError;
135    type Color = Gray2;
136
137    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
138    where
139        I: IntoIterator<Item = Pixel<Self::Color>>,
140    {
141        for Pixel(point, color) in pixels {
142            self.set_pixel(point.x, point.y, color);
143        }
144        Ok(())
145    }
146}
147
148impl OriginDimensions for Display2in9Gray2 {
149    fn size(&self) -> Size {
150        match self.rotation {
151            DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
152                Size::new(WIDTH.into(), HEIGHT.into())
153            }
154            DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
155                Size::new(HEIGHT.into(), WIDTH.into())
156            }
157        }
158    }
159}
160
161/// Adapter that exposes a `BinaryColor` `DrawTarget` view over a `Display2in9Gray2` buffer.
162pub struct BinaryDrawTarget<'a>(&'a mut Display2in9Gray2);
163
164impl<'a> BinaryDrawTarget<'a> {
165    /// Create a new `BinaryDrawTarget` adapter.
166    pub fn new(display: &'a mut Display2in9Gray2) -> Self {
167        Self(display)
168    }
169}
170
171impl DrawTarget for BinaryDrawTarget<'_> {
172    type Color = BinaryColor;
173    type Error = DisplayError;
174
175    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
176    where
177        I: IntoIterator<Item = Pixel<Self::Color>>,
178    {
179        for Pixel(point, color) in pixels {
180            let level = match color {
181                BinaryColor::On => Gray2::WHITE,
182                BinaryColor::Off => Gray2::BLACK,
183            };
184            self.0.set_pixel(point.x, point.y, level);
185        }
186        Ok(())
187    }
188}
189
190impl OriginDimensions for BinaryDrawTarget<'_> {
191    fn size(&self) -> Size {
192        match self.0.rotation {
193            DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
194                Size::new(WIDTH.into(), HEIGHT.into())
195            }
196            DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
197                Size::new(HEIGHT.into(), WIDTH.into())
198            }
199        }
200    }
201}