From 446eb6763b9a3f26d46d73585462bf535bcf023d Mon Sep 17 00:00:00 2001 From: cepetr Date: Fri, 15 Mar 2024 15:44:14 +0100 Subject: [PATCH] WIP - drawlib - bitmap refactored --- core/embed/rust/src/trezorhal/bitmap.rs | 611 ------------------ core/embed/rust/src/trezorhal/dma2d_new.rs | 211 ++++++ core/embed/rust/src/trezorhal/mod.rs | 2 +- core/embed/rust/src/ui/shape/bitmap/bitmap.rs | 317 +++++++++ core/embed/rust/src/ui/shape/bitmap/mod.rs | 6 + core/embed/rust/src/ui/shape/bitmap/mono8.rs | 48 ++ core/embed/rust/src/ui/shape/bitmap/rgb565.rs | 47 ++ .../rust/src/ui/shape/bitmap/rgba8888.rs | 47 ++ core/embed/rust/src/ui/shape/canvas/common.rs | 7 +- core/embed/rust/src/ui/shape/canvas/mod.rs | 3 - core/embed/rust/src/ui/shape/canvas/mono8.rs | 9 +- core/embed/rust/src/ui/shape/canvas/rgb565.rs | 7 +- .../rust/src/ui/shape/canvas/rgba8888.rs | 7 +- core/embed/rust/src/ui/shape/mod.rs | 11 +- .../embed/rust/src/ui/shape/model/model_tt.rs | 19 +- 15 files changed, 716 insertions(+), 636 deletions(-) delete mode 100644 core/embed/rust/src/trezorhal/bitmap.rs create mode 100644 core/embed/rust/src/trezorhal/dma2d_new.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/bitmap.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/mod.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/mono8.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/rgb565.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/rgba8888.rs diff --git a/core/embed/rust/src/trezorhal/bitmap.rs b/core/embed/rust/src/trezorhal/bitmap.rs deleted file mode 100644 index 344a407ef..000000000 --- a/core/embed/rust/src/trezorhal/bitmap.rs +++ /dev/null @@ -1,611 +0,0 @@ -use super::ffi; - -use crate::ui::{ - display::Color, - geometry::{Offset, Rect}, -}; - -use core::{cell::Cell, marker::PhantomData}; - -#[derive(Copy, Clone)] -pub enum BitmapFormat { - MONO1, - MONO1P, - MONO4, - MONO8, - RGB565, - RGBA8888, -} - -pub struct Bitmap<'a> { - /// Pointer to top-left pixel - ptr: *mut u8, - /// Stride in bytes - stride: usize, - /// Size in pixels - size: Offset, - /// Format of pixels - format: BitmapFormat, - /// Bitmap data is mutable - mutable: bool, - /// DMA operation is pending - dma_pending: Cell, - /// - _phantom: core::marker::PhantomData<&'a ()>, -} - -impl<'a> Bitmap<'a> { - /// Creates a new bitmap referencing a specified buffer. - /// - /// Optionally minimal height can be specified and then the height - /// of the new bitmap is adjusted to the buffer size. - /// - /// Returns None if the buffer is not big enough. - /// - /// The `buff` needs to be properly aligned and big enough - /// to hold a bitmap with the specified format and size - pub fn new( - format: BitmapFormat, - stride: Option, - mut size: Offset, - min_height: Option, - buff: &'a [u8], - ) -> Option { - if size.x < 0 && size.y < 0 { - return None; - } - - let min_stride = match format { - BitmapFormat::MONO1 => (size.x + 7) / 8, - BitmapFormat::MONO1P => 0, - BitmapFormat::MONO4 => (size.x + 1) / 2, - BitmapFormat::MONO8 => size.x, - BitmapFormat::RGB565 => size.x * 2, - BitmapFormat::RGBA8888 => size.x * 4, - } as usize; - - let stride = stride.unwrap_or(min_stride); - - let alignment = match format { - BitmapFormat::MONO1 => 1, - BitmapFormat::MONO1P => 1, - BitmapFormat::MONO4 => 1, - BitmapFormat::MONO8 => 1, - BitmapFormat::RGB565 => 2, - BitmapFormat::RGBA8888 => 4, - }; - - assert!(stride >= min_stride); - assert!(buff.as_ptr() as usize & (alignment - 1) == 0); - assert!(stride & (alignment - 1) == 0); - - let max_height = if stride == 0 { - size.y as usize - } else { - buff.len() / stride - }; - - if size.y as usize > max_height { - if let Some(min_height) = min_height { - if max_height >= min_height as usize { - size.y = max_height as i16; - } else { - return None; - } - } else { - return None; - } - } - - Some(Self { - ptr: buff.as_ptr() as *mut u8, - stride, - size, - format, - mutable: false, - dma_pending: Cell::new(false), - _phantom: PhantomData, - }) - } - - /// Creates a new mutable bitmap referencing a specified buffer. - /// - /// Optionally minimal height can be specified and then the height - /// of the new bitmap is adjusted to the buffer size. - /// - /// Returns None if the buffer is not big enough. - /// - /// The `buff` needs to be properly aligned and big enough - /// to hold a bitmap with the specified format and size - pub fn new_mut( - format: BitmapFormat, - stride: Option, - size: Offset, - min_height: Option, - buff: &'a mut [u8], - ) -> Option { - let mut bitmap = Self::new(format, stride, size, min_height, buff)?; - bitmap.mutable = true; - return Some(bitmap); - } - - /// Returns bitmap width in pixels. - pub fn width(&self) -> i16 { - self.size.x - } - - /// Returns bitmap height in pixels. - pub fn height(&self) -> i16 { - self.size.y - } - - /// Returns bitmap width and height in pixels. - pub fn size(&self) -> Offset { - self.size - } - - /// Returns bitmap stride in bytes. - pub fn stride(&self) -> usize { - self.stride - } - - pub fn view(&self) -> BitmapView { - BitmapView::new(&self) - } - - /// Returns the specified row as an immutable slice. - /// - /// Returns None if row is out of range. - pub fn row(&self, row: i16) -> Option<&[T]> { - if row >= 0 && row < self.size.y { - self.wait_for_dma(); - let offset = row as usize * (self.stride / core::mem::size_of::()); - Some(unsafe { - core::slice::from_raw_parts( - (self.ptr as *const T).add(offset), - self.stride / core::mem::size_of::(), - ) - }) - } else { - None - } - } - - /// Returns the specified row as a mutable slice. - /// - /// Returns None if row is out of range. - pub fn row_mut(&mut self, row: i16) -> Option<&mut [T]> { - if row >= 0 && row < self.size.y { - self.wait_for_dma(); - let offset = row as usize * (self.stride / core::mem::size_of::()); - Some(unsafe { - core::slice::from_raw_parts_mut( - (self.ptr as *mut T).add(offset), - self.stride / core::mem::size_of::(), - ) - }) - } else { - None - } - } - - /// Returns specified consecutive rows as a mutable slice - /// - /// Returns None if any of requested row is out of range. - pub fn rows_mut(&mut self, row: i16, height: i16) -> Option<&mut [T]> { - if row >= 0 && height > 0 && row < self.size.y && row + height <= self.size.y { - self.wait_for_dma(); - let offset = self.stride * row as usize; - let len = self.stride * height as usize; - - let array = unsafe { - core::slice::from_raw_parts_mut( - self.ptr as *mut T, - self.size.y as usize * self.stride / core::mem::size_of::(), - ) - }; - - Some(&mut array[offset..offset + len]) - } else { - None - } - } - - /// Fills a rectangle with the specified color. - /// - /// The function is aplicable only on bitmaps with RGB565 format. - pub fn rgb565_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { - if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) { - let dma2d = dma2d.with_dst(self); - unsafe { - ffi::rgb565_fill(&dma2d); - } - self.dma_pending.set(true); - } - } - - // - pub fn rgb565_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { - if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { - let dma2d = dma2d.with_dst(self); - unsafe { - match src.bitmap.format { - BitmapFormat::MONO4 => { - ffi::rgb565_copy_mono4(&dma2d); - } - BitmapFormat::RGB565 => { - ffi::rgb565_copy_rgb565(&dma2d); - } - _ => panic!(), - } - } - self.dma_pending.set(true); - src.bitmap.dma_pending.set(true); - } - } - - pub fn rgb565_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { - if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { - let dma2d = dma2d.with_dst(self); - unsafe { - match src.bitmap.format { - BitmapFormat::MONO1P => { - // TODO - } - BitmapFormat::MONO4 => { - ffi::rgb565_blend_mono4(&dma2d); - } - _ => panic!(), - } - } - self.dma_pending.set(true); - src.bitmap.dma_pending.set(true); - } - } - - /// Fills a rectangle with the specified color. - /// - /// The function is aplicable only on bitmaps with RGBA888 format. - pub fn rgba8888_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { - if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) { - let dma2d = dma2d.with_dst(self); - unsafe { - ffi::rgba8888_fill(&dma2d); - } - self.dma_pending.set(true); - } - } - - pub fn rgba8888_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { - if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { - let dma2d = dma2d.with_dst(self); - unsafe { - match src.bitmap.format { - BitmapFormat::MONO4 => { - ffi::rgba8888_copy_mono4(&dma2d); - } - BitmapFormat::RGB565 => { - ffi::rgba8888_copy_rgb565(&dma2d); - } - BitmapFormat::RGBA8888 => { - ffi::rgba8888_copy_rgba8888(&dma2d); - } - _ => panic!(), - } - } - self.dma_pending.set(true); - src.bitmap.dma_pending.set(true); - } - } - - pub fn rgba8888_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { - if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { - let dma2d = dma2d.with_dst(self); - unsafe { - match src.bitmap.format { - BitmapFormat::MONO4 => { - ffi::rgba8888_blend_mono4(&dma2d); - } - _ => panic!(), - } - } - self.dma_pending.set(true); - src.bitmap.dma_pending.set(true); - } - } - - /// Fills a rectangle with the specified color. - /// - /// The function is aplicable only on bitmaps with RGB565 format. - pub fn mono8_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { - if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) { - let dma2d = dma2d.with_dst(self); - unsafe { - ffi::mono8_fill(&dma2d); - } - self.dma_pending.set(true); - } - } - - // - pub fn mono8_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { - if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { - let dma2d = dma2d.with_dst(self); - unsafe { - match src.bitmap.format { - BitmapFormat::MONO1P => { - ffi::mono8_copy_mono1p(&dma2d); - } - BitmapFormat::MONO4 => { - ffi::mono8_copy_mono4(&dma2d); - } - _ => panic!(), - } - } - self.dma_pending.set(true); - src.bitmap.dma_pending.set(true); - } - } - - pub fn mono8_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { - if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { - let dma2d = dma2d.with_dst(self); - unsafe { - match src.bitmap.format { - BitmapFormat::MONO1P => { - ffi::mono8_blend_mono1p(&dma2d); - } - BitmapFormat::MONO4 => { - ffi::mono8_blend_mono4(&dma2d); - } - _ => panic!(), - } - } - self.dma_pending.set(true); - src.bitmap.dma_pending.set(true); - } - } - - /// Waits until DMA operation is finished - fn wait_for_dma(&self) { - if self.dma_pending.get() { - #[cfg(feature = "dma2d")] - unsafe { - ffi::dma2d_wait_for_transfer(); - } - self.dma_pending.set(false); - } - } -} - -impl<'a> Drop for Bitmap<'a> { - fn drop(&mut self) { - self.wait_for_dma(); - } -} - -pub struct BitmapView<'a> { - pub bitmap: &'a Bitmap<'a>, - pub offset: Offset, - pub fg_color: Color, - pub bg_color: Color, -} - -impl<'a> BitmapView<'a> { - /// Creates a new reference to the bitmap - pub fn new(bitmap: &'a Bitmap) -> Self { - Self { - bitmap, - offset: Offset::zero(), - fg_color: Color::black(), - bg_color: Color::black(), - } - } - - /// Builds a new structure with offset set to the specified value - pub fn with_offset(self, offset: Offset) -> Self { - Self { - offset: (offset + self.offset.into()).into(), - ..self - } - } - - /// Builds a new structure with foreground color set to the specified value - pub fn with_fg(self, fg_color: Color) -> Self { - Self { - fg_color: fg_color.into(), - ..self - } - } - - /// Builds a new structure with background color set to the specified value - pub fn with_bg(self, bg_color: Color) -> Self { - Self { - bg_color: bg_color.into(), - ..self - } - } - - /// Returns the bitmap width and height in pixels - pub fn size(&self) -> Offset { - self.bitmap.size - } - - /// Returns the bitmap width in pixels - pub fn width(&self) -> i16 { - self.bitmap.width() - } - - /// Returns the bitmap height in pixels - pub fn height(&self) -> i16 { - self.bitmap.height() - } - - /// Returns the bitmap format - pub fn format(&self) -> BitmapFormat { - self.bitmap.format - } - - /// Returns the specified row as an immutable slice. - /// - /// Returns None if row is out of range. - pub fn row(&self, row: i16) -> Option<&[T]> { - self.bitmap.row(row) - } -} - -pub type Dma2d = ffi::dma2d_params_t; - -impl Dma2d { - pub fn new_fill(r: Rect, clip: Rect, color: Color, alpha: u8) -> Option { - let r = r.intersect(clip); - if !r.is_empty() { - Some( - Self::default() - .with_rect(r) - .with_fg(color) - .with_alpha(alpha), - ) - } else { - None - } - } - - pub fn new_copy(r: Rect, clip: Rect, src: &BitmapView) -> Option { - let mut offset = src.offset; - let mut r_dst = r; - - // Normalize negative x & y-offset of the bitmap - if offset.x < 0 { - r_dst.x0 -= offset.x; - offset.x = 0; - } - - if offset.y < 0 { - r_dst.y0 -= offset.y; - offset.y = 0; - } - - // Clip with the canvas viewport - let mut r = r_dst.intersect(clip); - - // Clip with the bitmap top-left - if r.x0 > r_dst.x0 { - offset.x += r.x0 - r_dst.x0; - } - - if r.y0 > r_dst.y0 { - offset.y += r.y0 - r_dst.y0; - } - - // Clip with the bitmap size - r.x1 = core::cmp::min(r.x0 + src.size().x - offset.x, r.x1); - r.y1 = core::cmp::min(r.y0 + src.size().y - offset.y, r.y1); - - if !r.is_empty() { - Some( - Dma2d::default() - .with_rect(r) - .with_src(src.bitmap, offset.x, offset.y) - .with_bg(src.bg_color) - .with_fg(src.fg_color), - ) - } else { - None - } - } - - pub fn with_dst(self, dst: &mut Bitmap) -> Self { - Self { - dst_row: unsafe { dst.ptr.add(dst.stride * self.dst_y as usize) as *mut cty::c_void }, - dst_stride: dst.stride as u16, - ..self - } - } - - fn default() -> Self { - Self { - width: 0, - height: 0, - dst_row: core::ptr::null_mut(), - dst_stride: 0, - dst_x: 0, - dst_y: 0, - src_row: core::ptr::null_mut(), - src_bg: 0, - src_fg: 0, - src_stride: 0, - src_x: 0, - src_y: 0, - src_alpha: 255, - } - } - - fn with_rect(self, r: Rect) -> Self { - Self { - width: r.width() as u16, - height: r.height() as u16, - dst_x: r.x0 as u16, - dst_y: r.y0 as u16, - ..self - } - } - - fn with_src(self, bitmap: &Bitmap, x: i16, y: i16) -> Self { - let bitmap_stride = match bitmap.format { - BitmapFormat::MONO1P => bitmap.size.x as u16, // packed bits - _ => bitmap.stride as u16, - }; - - Self { - src_row: unsafe { bitmap.ptr.add(bitmap.stride * y as usize) as *mut cty::c_void }, - src_stride: bitmap_stride, - src_x: x as u16, - src_y: y as u16, - ..self - } - } - - fn with_fg(self, fg_color: Color) -> Self { - Self { - src_fg: fg_color.into(), - ..self - } - } - - fn with_bg(self, bg_color: Color) -> Self { - Self { - src_bg: bg_color.into(), - ..self - } - } - - fn with_alpha(self, alpha: u8) -> Self { - Self { - src_alpha: alpha, - ..self - } - } -} - -impl Dma2d { - pub fn wnd565_fill(r: Rect, clip: Rect, color: Color) { - if let Some(dma2d) = Dma2d::new_fill(r, clip, color, 255) { - unsafe { ffi::wnd565_fill(&dma2d) }; - } - } - - pub fn wnd565_copy(r: Rect, clip: Rect, src: &BitmapView) { - if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { - unsafe { - match src.bitmap.format { - BitmapFormat::RGB565 => { - ffi::wnd565_copy_rgb565(&dma2d); - } - _ => panic!(), - } - } - src.bitmap.dma_pending.set(true); - } - } -} diff --git a/core/embed/rust/src/trezorhal/dma2d_new.rs b/core/embed/rust/src/trezorhal/dma2d_new.rs new file mode 100644 index 000000000..4230b9533 --- /dev/null +++ b/core/embed/rust/src/trezorhal/dma2d_new.rs @@ -0,0 +1,211 @@ +use super::ffi; + +use crate::ui::{ + display::Color, + geometry::Rect, + shape::{Bitmap, BitmapFormat, BitmapView}, +}; + +pub type Dma2d = ffi::dma2d_params_t; + +impl Default for Dma2d { + fn default() -> Self { + Self { + width: 0, + height: 0, + dst_row: core::ptr::null_mut(), + dst_stride: 0, + dst_x: 0, + dst_y: 0, + src_row: core::ptr::null_mut(), + src_bg: 0, + src_fg: 0, + src_stride: 0, + src_x: 0, + src_y: 0, + src_alpha: 255, + } + } +} + +impl Dma2d { + pub fn new_fill(r: Rect, clip: Rect, color: Color, alpha: u8) -> Option { + let r = r.intersect(clip); + if !r.is_empty() { + Some( + Self::default() + .with_rect(r) + .with_fg(color) + .with_alpha(alpha), + ) + } else { + None + } + } + + pub fn new_copy(r: Rect, clip: Rect, src: &BitmapView) -> Option { + let mut offset = src.offset; + let mut r_dst = r; + + // Normalize negative x & y-offset of the bitmap + if offset.x < 0 { + r_dst.x0 -= offset.x; + offset.x = 0; + } + + if offset.y < 0 { + r_dst.y0 -= offset.y; + offset.y = 0; + } + + // Clip with the canvas viewport + let mut r = r_dst.intersect(clip); + + // Clip with the bitmap top-left + if r.x0 > r_dst.x0 { + offset.x += r.x0 - r_dst.x0; + } + + if r.y0 > r_dst.y0 { + offset.y += r.y0 - r_dst.y0; + } + + // Clip with the bitmap size + r.x1 = core::cmp::min(r.x0 + src.size().x - offset.x, r.x1); + r.y1 = core::cmp::min(r.y0 + src.size().y - offset.y, r.y1); + + if !r.is_empty() { + Some( + Dma2d::default() + .with_rect(r) + .with_src(src.bitmap, offset.x, offset.y) + .with_bg(src.bg_color) + .with_fg(src.fg_color), + ) + } else { + None + } + } + + pub fn with_dst(self, dst: &mut Bitmap) -> Self { + Self { + dst_row: unsafe { dst.row_ptr(self.dst_y) }, + dst_stride: dst.stride() as u16, + ..self + } + } + + fn with_rect(self, r: Rect) -> Self { + Self { + width: r.width() as u16, + height: r.height() as u16, + dst_x: r.x0 as u16, + dst_y: r.y0 as u16, + ..self + } + } + + fn with_src(self, bitmap: &Bitmap, x: i16, y: i16) -> Self { + let bitmap_stride = match bitmap.format() { + BitmapFormat::MONO1P => bitmap.width() as u16, // packed bits + _ => bitmap.stride() as u16, + }; + + Self { + src_row: unsafe { bitmap.row_ptr(y as u16) }, + src_stride: bitmap_stride, + src_x: x as u16, + src_y: y as u16, + ..self + } + } + + fn with_fg(self, fg_color: Color) -> Self { + Self { + src_fg: fg_color.into(), + ..self + } + } + + fn with_bg(self, bg_color: Color) -> Self { + Self { + src_bg: bg_color.into(), + ..self + } + } + + fn with_alpha(self, alpha: u8) -> Self { + Self { + src_alpha: alpha, + ..self + } + } + + pub fn wait_for_transfer() { + unsafe { ffi::dma2d_wait_for_transfer() } + } + + pub unsafe fn rgb565_fill(&self) { + unsafe { ffi::rgb565_fill(self) }; + } + + pub unsafe fn rgb565_copy_mono4(&self) { + unsafe { ffi::rgb565_copy_mono4(self) }; + } + + pub unsafe fn rgb565_copy_rgb565(&self) { + unsafe { ffi::rgb565_copy_rgb565(self) }; + } + + pub unsafe fn rgb565_blend_mono4(&self) { + unsafe { ffi::rgb565_blend_mono4(self) }; + } + + pub unsafe fn rgba8888_fill(&self) { + unsafe { ffi::rgba8888_fill(self) }; + } + + pub unsafe fn rgba8888_copy_mono4(&self) { + unsafe { ffi::rgba8888_copy_mono4(self) }; + } + + pub unsafe fn rgba8888_copy_rgb565(&self) { + unsafe { ffi::rgba8888_copy_rgb565(self) }; + } + + pub unsafe fn rgba8888_copy_rgba8888(&self) { + unsafe { ffi::rgba8888_copy_rgba8888(self) }; + } + + pub unsafe fn rgba8888_blend_mono4(&self) { + unsafe { ffi::rgba8888_blend_mono4(self) }; + } + + pub unsafe fn mono8_fill(&self) { + unsafe { ffi::mono8_fill(self) }; + } + + pub unsafe fn mono8_copy_mono1p(&self) { + unsafe { ffi::mono8_copy_mono1p(self) }; + } + + pub unsafe fn mono8_copy_mono4(&self) { + unsafe { ffi::mono8_copy_mono4(self) }; + } + + pub unsafe fn mono8_blend_mono1p(&self) { + unsafe { ffi::mono8_blend_mono1p(self) }; + } + + pub unsafe fn mono8_blend_mono4(&self) { + unsafe { ffi::mono8_blend_mono4(self) }; + } + + pub unsafe fn wnd565_fill(&self) { + unsafe { ffi::wnd565_fill(self) }; + } + + pub unsafe fn wnd565_copy_rgb565(&self) { + unsafe { ffi::wnd565_copy_rgb565(self) }; + } +} diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs index 340f38a9d..5fea28bb5 100644 --- a/core/embed/rust/src/trezorhal/mod.rs +++ b/core/embed/rust/src/trezorhal/mod.rs @@ -2,11 +2,11 @@ pub mod bip39; #[macro_use] #[allow(unused_macros)] pub mod fatal_error; -pub mod bitmap; #[cfg(feature = "ui")] pub mod display; #[cfg(feature = "dma2d")] pub mod dma2d; +pub mod dma2d_new; mod ffi; #[cfg(feature = "haptic")] pub mod haptic; diff --git a/core/embed/rust/src/ui/shape/bitmap/bitmap.rs b/core/embed/rust/src/ui/shape/bitmap/bitmap.rs new file mode 100644 index 000000000..e4123780d --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/bitmap.rs @@ -0,0 +1,317 @@ +use crate::trezorhal::dma2d_new::Dma2d; + +use crate::ui::{display::Color, geometry::Offset}; + +use core::{cell::Cell, marker::PhantomData}; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum BitmapFormat { + /// 1-bit mono + MONO1, + /// 1-bit mono packed (bitmap stride is in bits) + MONO1P, + /// 4-bit mono + MONO4, + /// 8-bit mono + MONO8, + /// 16-bit color, RGB565 format + RGB565, + /// 32-bit color, RGBA format + RGBA8888, +} + +pub struct Bitmap<'a> { + /// Pointer to top-left pixel + ptr: *mut u8, + /// Stride in bytes + stride: usize, + /// Size in pixels + size: Offset, + /// Format of pixels + format: BitmapFormat, + /// Bitmap data is mutable + mutable: bool, + /// DMA operation is pending + dma_pending: Cell, + /// + _phantom: core::marker::PhantomData<&'a ()>, +} + +impl<'a> Bitmap<'a> { + /// Creates a new bitmap referencing a specified buffer. + /// + /// Optionally minimal height can be specified and then the height + /// of the new bitmap is adjusted to the buffer size. + /// + /// Returns None if the buffer is not big enough. + /// + /// The `buff` needs to be properly aligned and big enough + /// to hold a bitmap with the specified format and size + pub fn new( + format: BitmapFormat, + stride: Option, + mut size: Offset, + min_height: Option, + buff: &'a [u8], + ) -> Option { + if size.x < 0 && size.y < 0 { + return None; + } + + let min_stride = match format { + BitmapFormat::MONO1 => (size.x + 7) / 8, + BitmapFormat::MONO1P => 0, + BitmapFormat::MONO4 => (size.x + 1) / 2, + BitmapFormat::MONO8 => size.x, + BitmapFormat::RGB565 => size.x * 2, + BitmapFormat::RGBA8888 => size.x * 4, + } as usize; + + let stride = stride.unwrap_or(min_stride); + + let alignment = match format { + BitmapFormat::MONO1 => 1, + BitmapFormat::MONO1P => 1, + BitmapFormat::MONO4 => 1, + BitmapFormat::MONO8 => 1, + BitmapFormat::RGB565 => 2, + BitmapFormat::RGBA8888 => 4, + }; + + assert!(stride >= min_stride); + assert!(buff.as_ptr() as usize & (alignment - 1) == 0); + assert!(stride & (alignment - 1) == 0); + + let max_height = if stride == 0 { + size.y as usize + } else { + buff.len() / stride + }; + + if size.y as usize > max_height { + if let Some(min_height) = min_height { + if max_height >= min_height as usize { + size.y = max_height as i16; + } else { + return None; + } + } else { + return None; + } + } + + Some(Self { + ptr: buff.as_ptr() as *mut u8, + stride, + size, + format, + mutable: false, + dma_pending: Cell::new(false), + _phantom: PhantomData, + }) + } + + /// Creates a new mutable bitmap referencing a specified buffer. + /// + /// Optionally minimal height can be specified and then the height + /// of the new bitmap is adjusted to the buffer size. + /// + /// Returns None if the buffer is not big enough. + /// + /// The `buff` needs to be properly aligned and big enough + /// to hold a bitmap with the specified format and size + pub fn new_mut( + format: BitmapFormat, + stride: Option, + size: Offset, + min_height: Option, + buff: &'a mut [u8], + ) -> Option { + let mut bitmap = Self::new(format, stride, size, min_height, buff)?; + bitmap.mutable = true; + return Some(bitmap); + } + + /// Returns bitmap width in pixels. + pub fn width(&self) -> i16 { + self.size.x + } + + /// Returns bitmap height in pixels. + pub fn height(&self) -> i16 { + self.size.y + } + + /// Returns bitmap width and height in pixels. + pub fn size(&self) -> Offset { + self.size + } + + /// Returns bitmap stride in bytes. + pub fn stride(&self) -> usize { + self.stride + } + + /// Returns bitmap format. + pub fn format(&self) -> BitmapFormat { + self.format + } + + pub fn view(&self) -> BitmapView { + BitmapView::new(&self) + } + + /// Returns the specified row as an immutable slice. + /// + /// Returns None if row is out of range. + pub fn row(&self, row: i16) -> Option<&[T]> { + if row >= 0 && row < self.size.y { + self.wait_for_dma(); + let offset = row as usize * (self.stride / core::mem::size_of::()); + Some(unsafe { + core::slice::from_raw_parts( + (self.ptr as *const T).add(offset), + self.stride / core::mem::size_of::(), + ) + }) + } else { + None + } + } + + /// Returns the specified row as a mutable slice. + /// + /// Returns None if row is out of range. + pub fn row_mut(&mut self, row: i16) -> Option<&mut [T]> { + if row >= 0 && row < self.size.y { + self.wait_for_dma(); + let offset = row as usize * (self.stride / core::mem::size_of::()); + Some(unsafe { + core::slice::from_raw_parts_mut( + (self.ptr as *mut T).add(offset), + self.stride / core::mem::size_of::(), + ) + }) + } else { + None + } + } + + /// Returns specified consecutive rows as a mutable slice + /// + /// Returns None if any of requested row is out of range. + pub fn rows_mut(&mut self, row: i16, height: i16) -> Option<&mut [T]> { + if row >= 0 && height > 0 && row < self.size.y && row + height <= self.size.y { + self.wait_for_dma(); + let offset = self.stride * row as usize; + let len = self.stride * height as usize; + + let array = unsafe { + core::slice::from_raw_parts_mut( + self.ptr as *mut T, + self.size.y as usize * self.stride / core::mem::size_of::(), + ) + }; + + Some(&mut array[offset..offset + len]) + } else { + None + } + } + + /// Return raw mut pointer to the specified bitmap row. + /// + /// `y` must be in range <0; self.height() - 1>. + pub unsafe fn row_ptr(&self, y: u16) -> *mut cty::c_void { + unsafe { self.ptr.add(self.stride() * y as usize) as *mut cty::c_void } + } + + /// Waits until DMA operation is finished + fn wait_for_dma(&self) { + if self.dma_pending.get() { + Dma2d::wait_for_transfer(); + self.dma_pending.set(false); + } + } + + // Mark bitmap as DMA operation is pending + pub fn mark_dma_pending(&self) { + self.dma_pending.set(true); + } +} + +impl<'a> Drop for Bitmap<'a> { + fn drop(&mut self) { + self.wait_for_dma(); + } +} + +pub struct BitmapView<'a> { + pub bitmap: &'a Bitmap<'a>, + pub offset: Offset, + pub fg_color: Color, + pub bg_color: Color, +} + +impl<'a> BitmapView<'a> { + /// Creates a new reference to the bitmap + pub fn new(bitmap: &'a Bitmap) -> Self { + Self { + bitmap, + offset: Offset::zero(), + fg_color: Color::black(), + bg_color: Color::black(), + } + } + + /// Builds a new structure with offset set to the specified value + pub fn with_offset(self, offset: Offset) -> Self { + Self { + offset: (offset + self.offset.into()).into(), + ..self + } + } + + /// Builds a new structure with foreground color set to the specified value + pub fn with_fg(self, fg_color: Color) -> Self { + Self { + fg_color: fg_color.into(), + ..self + } + } + + /// Builds a new structure with background color set to the specified value + pub fn with_bg(self, bg_color: Color) -> Self { + Self { + bg_color: bg_color.into(), + ..self + } + } + + /// Returns the bitmap width and height in pixels + pub fn size(&self) -> Offset { + self.bitmap.size + } + + /// Returns the bitmap width in pixels + pub fn width(&self) -> i16 { + self.bitmap.width() + } + + /// Returns the bitmap height in pixels + pub fn height(&self) -> i16 { + self.bitmap.height() + } + + /// Returns the bitmap format + pub fn format(&self) -> BitmapFormat { + self.bitmap.format + } + + /// Returns the specified row as an immutable slice. + /// + /// Returns None if row is out of range. + pub fn row(&self, row: i16) -> Option<&[T]> { + self.bitmap.row(row) + } +} diff --git a/core/embed/rust/src/ui/shape/bitmap/mod.rs b/core/embed/rust/src/ui/shape/bitmap/mod.rs new file mode 100644 index 000000000..22103d3ba --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/mod.rs @@ -0,0 +1,6 @@ +pub mod bitmap; +pub mod mono8; +pub mod rgb565; +pub mod rgba8888; + +pub use bitmap::{Bitmap, BitmapFormat, BitmapView}; diff --git a/core/embed/rust/src/ui/shape/bitmap/mono8.rs b/core/embed/rust/src/ui/shape/bitmap/mono8.rs new file mode 100644 index 000000000..c623ed5aa --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/mono8.rs @@ -0,0 +1,48 @@ +use super::{Bitmap, BitmapFormat, BitmapView}; +use crate::{ + trezorhal::dma2d_new::Dma2d, + ui::{display::Color, geometry::Rect}, +}; + +impl<'a> Bitmap<'a> { + /// Fills a rectangle with the specified color. + /// + /// The function is aplicable only on bitmaps with RGB565 format. + pub fn mono8_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { + assert!(self.format() == BitmapFormat::MONO8); + if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) { + let dma2d = dma2d.with_dst(self); + unsafe { dma2d.mono8_fill() }; + self.mark_dma_pending(); + } + } + + // + pub fn mono8_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::MONO8); + if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { + let dma2d = dma2d.with_dst(self); + match src.format() { + BitmapFormat::MONO1P => unsafe { dma2d.mono8_copy_mono1p() }, + BitmapFormat::MONO4 => unsafe { dma2d.mono8_copy_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } + + pub fn mono8_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::MONO8); + if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { + let dma2d = dma2d.with_dst(self); + match src.format() { + BitmapFormat::MONO1P => unsafe { dma2d.mono8_blend_mono1p() }, + BitmapFormat::MONO4 => unsafe { dma2d.mono8_blend_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/bitmap/rgb565.rs b/core/embed/rust/src/ui/shape/bitmap/rgb565.rs new file mode 100644 index 000000000..e1355bc0b --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/rgb565.rs @@ -0,0 +1,47 @@ +use super::{Bitmap, BitmapFormat, BitmapView}; +use crate::{ + trezorhal::dma2d_new::Dma2d, + ui::{display::Color, geometry::Rect}, +}; + +impl<'a> Bitmap<'a> { + /// Fills a rectangle with the specified color. + /// + /// The function is aplicable only on bitmaps with RGB565 format. + pub fn rgb565_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { + assert!(self.format() == BitmapFormat::RGB565); + if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) { + let dma2d = dma2d.with_dst(self); + unsafe { dma2d.rgb565_fill() }; + self.mark_dma_pending(); + } + } + + // + pub fn rgb565_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGB565); + if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { + let dma2d = dma2d.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { dma2d.rgb565_copy_mono4() }, + BitmapFormat::RGB565 => unsafe { dma2d.rgb565_copy_rgb565() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } + + pub fn rgb565_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGB565); + if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { + let dma2d = dma2d.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { dma2d.rgb565_blend_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/bitmap/rgba8888.rs b/core/embed/rust/src/ui/shape/bitmap/rgba8888.rs new file mode 100644 index 000000000..72c24484f --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/rgba8888.rs @@ -0,0 +1,47 @@ +use super::{Bitmap, BitmapFormat, BitmapView}; +use crate::{ + trezorhal::dma2d_new::Dma2d, + ui::{display::Color, geometry::Rect}, +}; + +impl<'a> Bitmap<'a> { + /// Fills a rectangle with the specified color. + /// + /// The function is aplicable only on bitmaps with RGBA888 format. + pub fn rgba8888_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { + assert!(self.format() == BitmapFormat::RGBA8888); + if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) { + let dma2d = dma2d.with_dst(self); + unsafe { dma2d.rgba8888_fill() }; + self.mark_dma_pending(); + } + } + + pub fn rgba8888_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGBA8888); + if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { + let dma2d = dma2d.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { dma2d.rgba8888_copy_mono4() }, + BitmapFormat::RGB565 => unsafe { dma2d.rgba8888_copy_rgb565() }, + BitmapFormat::RGBA8888 => unsafe { dma2d.rgba8888_copy_rgba8888() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } + + pub fn rgba8888_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGBA8888); + if let Some(dma2d) = Dma2d::new_copy(r, clip, src) { + let dma2d = dma2d.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { dma2d.rgba8888_blend_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/canvas/common.rs b/core/embed/rust/src/ui/shape/canvas/common.rs index c6e9f2d57..405eedaea 100644 --- a/core/embed/rust/src/ui/shape/canvas/common.rs +++ b/core/embed/rust/src/ui/shape/canvas/common.rs @@ -3,13 +3,14 @@ use crate::ui::{ geometry::{Offset, Point, Rect}, }; -use super::{BitmapView, Viewport}; +use super::super::{ + algo::{circle_points, line_points, sin_i16, PI4}, + BitmapView, Viewport, +}; #[cfg(feature = "ui_blurring")] use crate::ui::shape::DrawingCache; -use super::super::algo::{circle_points, line_points, sin_i16, PI4}; - pub trait BasicCanvas { /// Returns dimensions of the canvas in pixels. fn size(&self) -> Offset; diff --git a/core/embed/rust/src/ui/shape/canvas/mod.rs b/core/embed/rust/src/ui/shape/canvas/mod.rs index 2d228a063..9e4507d6e 100644 --- a/core/embed/rust/src/ui/shape/canvas/mod.rs +++ b/core/embed/rust/src/ui/shape/canvas/mod.rs @@ -9,6 +9,3 @@ pub use mono8::Mono8Canvas; pub use rgb565::Rgb565Canvas; pub use rgba8888::Rgba8888Canvas; pub use viewport::Viewport; - -use crate::trezorhal::bitmap; -pub use bitmap::{Bitmap, BitmapFormat, BitmapView}; diff --git a/core/embed/rust/src/ui/shape/canvas/mono8.rs b/core/embed/rust/src/ui/shape/canvas/mono8.rs index 5408c0243..33d6d88d8 100644 --- a/core/embed/rust/src/ui/shape/canvas/mono8.rs +++ b/core/embed/rust/src/ui/shape/canvas/mono8.rs @@ -3,10 +3,13 @@ use crate::ui::{ geometry::{Offset, Point, Rect}, }; -#[cfg(feature = "ui_blurring")] -use crate::ui::shape::DrawingCache; +use super::{ + super::{Bitmap, BitmapFormat, BitmapView}, + BasicCanvas, Canvas, Viewport, +}; -use super::{BasicCanvas, Bitmap, BitmapFormat, BitmapView, Canvas, Viewport}; +#[cfg(feature = "ui_blurring")] +use super::super::DrawingCache; /// A struct representing 8-bit monochromatic canvas pub struct Mono8Canvas<'a> { diff --git a/core/embed/rust/src/ui/shape/canvas/rgb565.rs b/core/embed/rust/src/ui/shape/canvas/rgb565.rs index 46aff8e57..54e8e235f 100644 --- a/core/embed/rust/src/ui/shape/canvas/rgb565.rs +++ b/core/embed/rust/src/ui/shape/canvas/rgb565.rs @@ -3,10 +3,13 @@ use crate::ui::{ geometry::{Offset, Point, Rect}, }; -use super::{BasicCanvas, Bitmap, BitmapFormat, BitmapView, Canvas, Viewport}; +use super::{ + super::{Bitmap, BitmapFormat, BitmapView}, + BasicCanvas, Canvas, Viewport, +}; #[cfg(feature = "ui_blurring")] -use crate::ui::shape::DrawingCache; +use super::super::DrawingCache; /// A struct representing 16-bit (RGB565) color canvas pub struct Rgb565Canvas<'a> { diff --git a/core/embed/rust/src/ui/shape/canvas/rgba8888.rs b/core/embed/rust/src/ui/shape/canvas/rgba8888.rs index 3f6b07518..30caa5a5f 100644 --- a/core/embed/rust/src/ui/shape/canvas/rgba8888.rs +++ b/core/embed/rust/src/ui/shape/canvas/rgba8888.rs @@ -3,10 +3,13 @@ use crate::ui::{ geometry::{Offset, Point, Rect}, }; -use super::{BasicCanvas, Bitmap, BitmapFormat, BitmapView, Canvas, Viewport}; +use super::{ + super::{Bitmap, BitmapFormat, BitmapView}, + BasicCanvas, Canvas, Viewport, +}; #[cfg(feature = "ui_blurring")] -use crate::ui::shape::DrawingCache; +use super::super::DrawingCache; /// A struct representing 32-bit (RGBA8888) color canvas pub struct Rgba8888Canvas<'a> { diff --git a/core/embed/rust/src/ui/shape/mod.rs b/core/embed/rust/src/ui/shape/mod.rs index 877ba6966..dbfe6a1ac 100644 --- a/core/embed/rust/src/ui/shape/mod.rs +++ b/core/embed/rust/src/ui/shape/mod.rs @@ -1,6 +1,7 @@ mod algo; mod bar; mod base; +mod bitmap; #[cfg(feature = "ui_blurring")] mod blur; mod cache; @@ -14,11 +15,14 @@ mod render; mod text; mod toif; +pub use algo::PI4; pub use bar::Bar; pub use base::{Shape, ShapeClone}; +pub use bitmap::{Bitmap, BitmapFormat, BitmapView}; #[cfg(feature = "ui_blurring")] pub use blur::Blurring; pub use cache::drawing_cache::DrawingCache; +pub use canvas::{BasicCanvas, Canvas, Mono8Canvas, Rgb565Canvas, Rgba8888Canvas, Viewport}; pub use circle::Circle; #[cfg(feature = "ui_jpeg_decoder")] pub use jpeg::JpegImage; @@ -27,10 +31,3 @@ pub use qrcode::QrImage; pub use render::{DirectRenderer, ProgressiveRenderer, Renderer}; pub use text::Text; pub use toif::ToifImage; - -pub use canvas::{BasicCanvas, Canvas, Mono8Canvas, Rgb565Canvas, Rgba8888Canvas, Viewport}; - -use crate::trezorhal::bitmap; -pub use bitmap::{Bitmap, BitmapFormat, BitmapView}; - -pub use algo::PI4; diff --git a/core/embed/rust/src/ui/shape/model/model_tt.rs b/core/embed/rust/src/ui/shape/model/model_tt.rs index 4319eff91..e92bd35af 100644 --- a/core/embed/rust/src/ui/shape/model/model_tt.rs +++ b/core/embed/rust/src/ui/shape/model/model_tt.rs @@ -1,10 +1,13 @@ +use crate::trezorhal::dma2d_new::Dma2d; + use crate::ui::{ display::Color, geometry::{Offset, Rect}, - shape::{BasicCanvas, DrawingCache, ProgressiveRenderer, Viewport}, }; -use crate::trezorhal::bitmap::{BitmapView, Dma2d}; +use super::super::{ + BasicCanvas, BitmapFormat, BitmapView, DrawingCache, ProgressiveRenderer, Viewport, +}; use static_alloc::Bump; @@ -67,11 +70,19 @@ impl BasicCanvas for DisplayModelT { fn fill_rect(&mut self, r: Rect, color: Color, _alpha: u8) { let r = r.translate(self.viewport.origin); - Dma2d::wnd565_fill(r, self.viewport.clip, color); + if let Some(dma2d) = Dma2d::new_fill(r, self.viewport.clip, color, 255) { + unsafe { dma2d.wnd565_fill() }; + } } fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) { let r = r.translate(self.viewport.origin); - Dma2d::wnd565_copy(r, self.viewport.clip, &bitmap); + if let Some(dma2d) = Dma2d::new_copy(r, self.viewport.clip, &bitmap) { + match bitmap.format() { + BitmapFormat::RGB565 => unsafe { dma2d.wnd565_copy_rgb565() }, + _ => panic!("Unsupported DMA operation"), + } + bitmap.bitmap.mark_dma_pending(); + } } }