Skip to main content

epd_datafuri/graphics/
display290_gray4_t5.rs

1//! Grayscale graphics buffer for the IL0373-based 2.9" display (ThinkInk T5 / MagTag).
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 T5 2.9" panel in pixels.
12pub const WIDTH: u16 = 128;
13/// Display height of the T5 2.9" panel in pixels.
14pub const HEIGHT: u16 = 296;
15
16/// Grayscale (2-bit) graphics buffer for the IL0373-based 2.9" T5 display.
17///
18/// Encodes 4-level grayscale across two RAM planes (DTM1 and DTM2). The IL0373
19/// uses non-inverted polarity, but requires the Gray2 storage bits mapped with
20/// LSB → DTM1 (high buffer) and MSB → DTM2 (low buffer) to match the
21/// controller's waveform LUT:
22///
23/// | Gray2 | DTM1 (high) | DTM2 (low) | Displayed  |
24/// |-------|-------------|------------|------------|
25/// | 0     | 0           | 0          | Black      |
26/// | 1     | 1           | 0          | Light gray |
27/// | 2     | 0           | 1          | Dark gray  |
28/// | 3     | 1           | 1          | White      |
29///
30/// Pass [`Display2in9Gray2::high_buffer`] to `update_gray2_and_display` as the
31/// BW/DTM1 buffer and [`Display2in9Gray2::low_buffer`] as the Red/DTM2 buffer.
32pub struct Display2in9Gray2 {
33    high_buffer: [u8; buffer_len(WIDTH as usize, HEIGHT as usize)],
34    low_buffer: [u8; buffer_len(WIDTH as usize, HEIGHT as usize)],
35    rotation: DisplayRotation,
36}
37
38impl Default for Display2in9Gray2 {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl Display2in9Gray2 {
45    /// Create a new grayscale buffer initialised to white.
46    pub fn new() -> Self {
47        Self {
48            high_buffer: [0xFF; buffer_len(WIDTH as usize, HEIGHT as usize)],
49            low_buffer: [0xFF; buffer_len(WIDTH as usize, HEIGHT as usize)],
50            rotation: DisplayRotation::Rotate270,
51        }
52    }
53
54    /// Get a reference to the high buffer (DTM1 plane).
55    pub fn high_buffer(&self) -> &[u8] {
56        &self.high_buffer
57    }
58
59    /// Get a reference to the low buffer (DTM2 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        // LSB of storage → DTM1 (high), MSB → DTM2 (low); non-inverted for IL0373.
88        let dtm1_byte = if (storage & 0b01) != 0 { 0xFF } else { 0x00 };
89        let dtm2_byte = if (storage & 0b10) != 0 { 0xFF } else { 0x00 };
90
91        self.high_buffer.fill(dtm1_byte);
92        self.low_buffer.fill(dtm2_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        // LSB → DTM1 (high buffer), MSB → DTM2 (low buffer); non-inverted for IL0373.
111        let dtm1_val = (storage & 0b01) != 0;
112        let dtm2_val = (storage & 0b10) != 0;
113
114        if dtm1_val {
115            self.high_buffer[idx] |= bit;
116        } else {
117            self.high_buffer[idx] &= !bit;
118        }
119
120        if dtm2_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}