mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-25 15:58:08 +00:00
feat(core): introduce new drawing library
[no changelog]
This commit is contained in:
parent
3336e3902f
commit
5d8a7ac5bf
37
core/embed/rust/Cargo.lock
generated
37
core/embed/rust/Cargo.lock
generated
@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloc-traits"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -279,6 +285,15 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static-alloc"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "570b7e840addf99f80c5b26abba410e21537002316fc82f2747fd87c171e9d7e"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.80"
|
version = "1.0.80"
|
||||||
@ -309,8 +324,11 @@ dependencies = [
|
|||||||
"qrcodegen",
|
"qrcodegen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"spin",
|
"spin",
|
||||||
|
"static-alloc",
|
||||||
"trezor-tjpgdec",
|
"trezor-tjpgdec",
|
||||||
"ufmt",
|
"ufmt",
|
||||||
|
"unsize",
|
||||||
|
"without-alloc",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -347,6 +365,15 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsize"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -369,6 +396,16 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "without-alloc"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "375db0478b203b950ef10d1cce23cdbe5f30c2454fd9e7673ff56656df23adbb"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-traits",
|
||||||
|
"unsize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
|
@ -17,8 +17,8 @@ ui = []
|
|||||||
dma2d = []
|
dma2d = []
|
||||||
xframebuffer = []
|
xframebuffer = []
|
||||||
display_mono = []
|
display_mono = []
|
||||||
display_rgb565 = []
|
display_rgb565 = ["ui_antialiasing"]
|
||||||
display_rgba8888 = []
|
display_rgba8888 = ["ui_antialiasing"]
|
||||||
framebuffer = []
|
framebuffer = []
|
||||||
framebuffer32bit = []
|
framebuffer32bit = []
|
||||||
ui_debug = []
|
ui_debug = []
|
||||||
@ -52,6 +52,8 @@ test = [
|
|||||||
"micropython",
|
"micropython",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"ui",
|
"ui",
|
||||||
|
"ui_jpeg_decoder",
|
||||||
|
"ui_blurring",
|
||||||
"dma2d",
|
"dma2d",
|
||||||
"touch",
|
"touch",
|
||||||
"backlight",
|
"backlight",
|
||||||
@ -113,6 +115,15 @@ version = "0.2.6"
|
|||||||
default-features = false
|
default-features = false
|
||||||
features = ["nightly"]
|
features = ["nightly"]
|
||||||
|
|
||||||
|
[dependencies.static-alloc]
|
||||||
|
version = "0.2.4"
|
||||||
|
|
||||||
|
[dependencies.without-alloc]
|
||||||
|
version = "0.2.2"
|
||||||
|
|
||||||
|
[dependencies.unsize]
|
||||||
|
version = "1.1.0"
|
||||||
|
|
||||||
# Build dependencies
|
# Build dependencies
|
||||||
|
|
||||||
[build-dependencies.bindgen]
|
[build-dependencies.bindgen]
|
||||||
|
@ -77,6 +77,16 @@ fn prepare_bindings() -> bindgen::Builder {
|
|||||||
clang_args.push(arg);
|
clang_args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "xframebuffer")]
|
||||||
|
{
|
||||||
|
bindings = bindings.clang_args(&["-DXFRAMEBUFFER"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "new_rendering")]
|
||||||
|
{
|
||||||
|
bindings = bindings.clang_args(["-DNEW_RENDERING"]);
|
||||||
|
}
|
||||||
|
|
||||||
// Pass in correct include paths and defines.
|
// Pass in correct include paths and defines.
|
||||||
if is_firmware() {
|
if is_firmware() {
|
||||||
clang_args.push("-nostdinc");
|
clang_args.push("-nostdinc");
|
||||||
@ -289,6 +299,27 @@ fn generate_trezorhal_bindings() {
|
|||||||
.allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_Y")
|
.allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_Y")
|
||||||
.allowlist_var("DISPLAY_RESX")
|
.allowlist_var("DISPLAY_RESX")
|
||||||
.allowlist_var("DISPLAY_RESY")
|
.allowlist_var("DISPLAY_RESY")
|
||||||
|
.allowlist_type("display_fb_info_t")
|
||||||
|
.allowlist_function("display_get_frame_buffer")
|
||||||
|
.allowlist_function("display_fill")
|
||||||
|
.allowlist_function("display_copy_rgb565")
|
||||||
|
// gfx_bitblt
|
||||||
|
.allowlist_type("gfx_bitblt_t")
|
||||||
|
.allowlist_function("gfx_rgb565_fill")
|
||||||
|
.allowlist_function("gfx_rgb565_copy_mono4")
|
||||||
|
.allowlist_function("gfx_rgb565_copy_rgb565")
|
||||||
|
.allowlist_function("gfx_rgb565_blend_mono4")
|
||||||
|
.allowlist_function("gfx_rgba8888_fill")
|
||||||
|
.allowlist_function("gfx_rgba8888_copy_mono4")
|
||||||
|
.allowlist_function("gfx_rgba8888_copy_rgb565")
|
||||||
|
.allowlist_function("gfx_rgba8888_copy_rgba8888")
|
||||||
|
.allowlist_function("gfx_rgba8888_blend_mono4")
|
||||||
|
.allowlist_function("gfx_mono8_fill")
|
||||||
|
.allowlist_function("gfx_mono8_copy_mono1p")
|
||||||
|
.allowlist_function("gfx_mono8_copy_mono4")
|
||||||
|
.allowlist_function("gfx_mono8_blend_mono1p")
|
||||||
|
.allowlist_function("gfx_mono8_blend_mono4")
|
||||||
|
.allowlist_function("dma2d_wait")
|
||||||
// fonts
|
// fonts
|
||||||
.allowlist_function("font_height")
|
.allowlist_function("font_height")
|
||||||
.allowlist_function("font_max_height")
|
.allowlist_function("font_max_height")
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
use crate::micropython::{buffer::get_buffer, gc::Gc, obj::Obj};
|
||||||
|
|
||||||
pub struct InputStream<'a> {
|
pub struct InputStream<'a> {
|
||||||
buf: &'a [u8],
|
buf: &'a [u8],
|
||||||
pos: usize,
|
pos: usize,
|
||||||
@ -69,3 +72,126 @@ impl<'a> InputStream<'a> {
|
|||||||
Ok(uint)
|
Ok(uint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum BinaryData<'a> {
|
||||||
|
Slice(&'a [u8]),
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
Object(Obj),
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
AllocatedSlice(Gc<[u8]>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BinaryData<'a> {
|
||||||
|
/// Returns `true` if the binary data is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the binary data.
|
||||||
|
///
|
||||||
|
/// This function is used just in the `paint()` functions in
|
||||||
|
/// UI components, that are going to be deleted after adopting new
|
||||||
|
/// drawing library for models T and TS3. Do not use this function in new
|
||||||
|
/// code.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The caller must ensure that the returned slice is not modified by
|
||||||
|
/// MicroPython. This means (a) discarding the slice before returning
|
||||||
|
/// to Python, and (b) being careful about calling into Python while
|
||||||
|
/// the slice is held.
|
||||||
|
pub unsafe fn data(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Self::Slice(data) => data,
|
||||||
|
// SAFETY: We expect no existing mutable reference. See safety
|
||||||
|
// note above.
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
Self::Object(obj) => unsafe { unwrap!(get_buffer(*obj)) },
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
Self::AllocatedSlice(data) => data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the binary data in bytes.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Slice(data) => data.len(),
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
// SAFETY: We expect no existing mutable reference.
|
||||||
|
Self::Object(obj) => unsafe { unwrap!(get_buffer(*obj)).len() },
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
Self::AllocatedSlice(data) => data.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads binary data from the source into the buffer.
|
||||||
|
/// - 'ofs' is the offset in bytes from the start of the binary data.
|
||||||
|
/// - 'buff' is the buffer to read the data into.
|
||||||
|
///
|
||||||
|
/// Returns the number of bytes read.
|
||||||
|
pub fn read(&self, ofs: usize, buff: &mut [u8]) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Slice(data) => {
|
||||||
|
let remaining = data.len().saturating_sub(ofs);
|
||||||
|
let size = buff.len().min(remaining);
|
||||||
|
buff[..size].copy_from_slice(&data[ofs..ofs + size]);
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: We expect no existing mutable reference to `obj`.
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
Self::Object(obj) => {
|
||||||
|
let data = unsafe { unwrap!(get_buffer(*obj)) };
|
||||||
|
let remaining = data.len().saturating_sub(ofs);
|
||||||
|
let size = buff.len().min(remaining);
|
||||||
|
buff[..size].copy_from_slice(&data[ofs..ofs + size]);
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
Self::AllocatedSlice(data) => {
|
||||||
|
let remaining = data.len().saturating_sub(ofs);
|
||||||
|
let size = buff.len().min(remaining);
|
||||||
|
buff[..size].copy_from_slice(&data[ofs..ofs + size]);
|
||||||
|
size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq for BinaryData<'a> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Slice(a), Self::Slice(b)) => a.as_ptr() == b.as_ptr() && a.len() == b.len(),
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
(Self::Object(a), Self::Object(b)) => a == b,
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
impl From<Gc<[u8]>> for BinaryData<'static> {
|
||||||
|
fn from(data: Gc<[u8]>) -> Self {
|
||||||
|
Self::AllocatedSlice(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "micropython")]
|
||||||
|
impl TryFrom<Obj> for BinaryData<'static> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
|
||||||
|
if !obj.is_bytes() {
|
||||||
|
return Err(Error::TypeError);
|
||||||
|
}
|
||||||
|
Ok(Self::Object(obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a [u8]> for BinaryData<'a> {
|
||||||
|
fn from(data: &'a [u8]) -> Self {
|
||||||
|
Self::Slice(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
445
core/embed/rust/src/trezorhal/bitblt.rs
Normal file
445
core/embed/rust/src/trezorhal/bitblt.rs
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
use super::ffi;
|
||||||
|
|
||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::Rect,
|
||||||
|
shape::{Bitmap, BitmapFormat, BitmapView},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Waits for the DMA2D peripheral transfer to complete.
|
||||||
|
pub fn wait_for_transfer() {
|
||||||
|
// SAFETY:
|
||||||
|
// `ffi::dma2d_wait()` is always safe to call.
|
||||||
|
#[cfg(feature = "dma2d")]
|
||||||
|
unsafe {
|
||||||
|
ffi::dma2d_wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ffi::gfx_bitblt_t {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is very private interface (almost completely unsafe) to
|
||||||
|
// preparing a `gfx_bitblt_t` structure for the bitblt operations.
|
||||||
|
impl ffi::gfx_bitblt_t {
|
||||||
|
/// Sets the destination bitmap pointer and stride.
|
||||||
|
///
|
||||||
|
/// # SAFETY
|
||||||
|
/// 1) Ensure that caller holds mutable reference to the destination bitmap
|
||||||
|
/// until the `gfx_bitblt_t` is dropped.
|
||||||
|
unsafe fn with_dst(self, dst: &mut Bitmap) -> Self {
|
||||||
|
// This ensures that the destination rectangle is
|
||||||
|
// completely inside the destination bitmap
|
||||||
|
assert!(dst.width() as u16 >= self.dst_x + self.width);
|
||||||
|
assert!(dst.height() as u16 >= self.dst_y + self.height);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// SAFETY:
|
||||||
|
// Lines between `dst_y` and`dst_y + height` are inside
|
||||||
|
// the destination bitmap. See asserts above.
|
||||||
|
dst_row: unsafe { dst.row_ptr(self.dst_y) },
|
||||||
|
dst_stride: dst.stride() as u16,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the coordinates of the destination rectangle inside the destination
|
||||||
|
/// bitmap.
|
||||||
|
///
|
||||||
|
/// # SAFETY
|
||||||
|
/// 1) Ensure that the rectangle is completely inside the destination
|
||||||
|
/// bitmap.
|
||||||
|
/// 2) If the copy or blend operation is used, ensure that the rectangle
|
||||||
|
/// is completely filled with source bitmap or its part.
|
||||||
|
unsafe 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the source bitmap
|
||||||
|
///
|
||||||
|
/// `x` and `y` specifies offset inside the source bitmap
|
||||||
|
///
|
||||||
|
/// # SAFETY
|
||||||
|
/// 1) Ensure that `x` and `y` are inside the source bitmap.
|
||||||
|
/// 2) Source bitmap complete covers destination rectangle.
|
||||||
|
/// 3) Ensure that caller holds immutable reference to the source bitmap.
|
||||||
|
/// until the `gfx_bitblt_t` is dropped.
|
||||||
|
unsafe 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 {
|
||||||
|
// SAFETY:
|
||||||
|
// It's safe if `y` is inside the bitmap (see note above)
|
||||||
|
src_row: unsafe { bitmap.row_ptr(y as u16) },
|
||||||
|
src_stride: bitmap_stride,
|
||||||
|
src_x: x as u16,
|
||||||
|
src_y: y as u16,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets foreground color used for rectangle filling or
|
||||||
|
/// drawing monochrome bitmaps.
|
||||||
|
fn with_fg(self, fg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
src_fg: fg_color.into(),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets foreground color used for drawing monochrome bitmaps.
|
||||||
|
fn with_bg(self, bg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
src_bg: bg_color.into(),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the foreground alpha value used for rectangle filling or
|
||||||
|
/// bitmap blending.
|
||||||
|
fn with_alpha(self, alpha: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
src_alpha: alpha,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rectangle filling operation.
|
||||||
|
pub struct BitBltFill {
|
||||||
|
bitblt: ffi::gfx_bitblt_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitBltFill {
|
||||||
|
/// Prepares bitblt **fill** operation
|
||||||
|
///
|
||||||
|
/// - `r` is the rectangle to fill.
|
||||||
|
/// - `clip` is the clipping rectangle and must be completely inside the
|
||||||
|
/// destination bitmap.
|
||||||
|
/// - `color` is the color to fill the rectangle with.
|
||||||
|
/// - `alpha` is the alpha value to use for blending.
|
||||||
|
///
|
||||||
|
/// The function ensures proper clipping and returns `None` if the fill
|
||||||
|
/// operation is not needed.
|
||||||
|
pub fn new(r: Rect, clip: Rect, color: Color, alpha: u8) -> Option<Self> {
|
||||||
|
let r = r.clamp(clip);
|
||||||
|
if !r.is_empty() {
|
||||||
|
Some(Self {
|
||||||
|
// SAFETY:
|
||||||
|
// The only unsafe operation is `.with_rect()`, which is safe
|
||||||
|
// as long as the rectangle is completely inside the destination bitmap.
|
||||||
|
// We will set the destination bitmap later, so at the moment the
|
||||||
|
// rectangle cannot be out of bounds.
|
||||||
|
bitblt: unsafe {
|
||||||
|
ffi::gfx_bitblt_t::default()
|
||||||
|
.with_rect(r)
|
||||||
|
.with_fg(color)
|
||||||
|
.with_alpha(alpha)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills a rectangle in the destination bitmap with the specified color.
|
||||||
|
///
|
||||||
|
/// Destination bitmap must be in RGB565 format
|
||||||
|
pub fn rgb565_fill(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::RGB565);
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination bitmap is in the correct format.
|
||||||
|
// - The destination rectangle is completely inside the destination bitmap.
|
||||||
|
unsafe { ffi::gfx_rgb565_fill(&self.bitblt.with_dst(dst)) };
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills a rectangle in the destination bitmap with the specified color.
|
||||||
|
///
|
||||||
|
/// The destination bitmap is in the RGBA8888 format.
|
||||||
|
pub fn rgba8888_fill(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::RGBA8888);
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination bitmap is in the correct format.
|
||||||
|
// - The destination rectangle is completely inside the destination bitmap.
|
||||||
|
unsafe { ffi::gfx_rgba8888_fill(&self.bitblt.with_dst(dst)) };
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills a rectangle in the destination bitmap with the specified color.
|
||||||
|
///
|
||||||
|
/// The destination bitmap is in the MONO8 format.
|
||||||
|
pub fn mono8_fill(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::MONO8);
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination bitmap is in the correct format.
|
||||||
|
// - The destination rectangle is completely inside the destination bitmap.
|
||||||
|
unsafe { ffi::gfx_mono8_fill(&self.bitblt.with_dst(dst)) };
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills a rectangle on the display with the specified color.
|
||||||
|
#[cfg(all(not(feature = "xframebuffer"), feature = "new_rendering"))]
|
||||||
|
pub fn display_fill(&self) {
|
||||||
|
assert!(self.bitblt.dst_x + self.bitblt.width <= ffi::DISPLAY_RESX as u16);
|
||||||
|
assert!(self.bitblt.dst_y + self.bitblt.height <= ffi::DISPLAY_RESY as u16);
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination rectangle is completely inside the display.
|
||||||
|
unsafe { ffi::display_fill(&self.bitblt) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rectangle copying or blending operation.
|
||||||
|
pub struct BitBltCopy<'a> {
|
||||||
|
bitblt: ffi::gfx_bitblt_t,
|
||||||
|
src: &'a BitmapView<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BitBltCopy<'a> {
|
||||||
|
/// Prepares `BitBltCopy` structure for copying or blending a part of the
|
||||||
|
/// source bitmap to the destination bitmap or display.
|
||||||
|
///
|
||||||
|
/// - `r` is the rectangle in the destination bitmap.
|
||||||
|
/// - `clip` is the clipping rectangle and must be completely inside the
|
||||||
|
/// destination bitmap.
|
||||||
|
/// - `src` represents the source bitmap.
|
||||||
|
///
|
||||||
|
/// The function ensures proper clipping and returns `None` if the copy
|
||||||
|
/// operation is not needed.
|
||||||
|
pub fn new(r: Rect, clip: Rect, src: &'a BitmapView) -> Option<Self> {
|
||||||
|
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.clamp(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 = r.x1.min(r.x0 + src.size().x - offset.x);
|
||||||
|
r.y1 = r.y1.min(r.y0 + src.size().y - offset.y);
|
||||||
|
|
||||||
|
if !r.is_empty() {
|
||||||
|
Some(Self {
|
||||||
|
// SAFETY:
|
||||||
|
// The only unsafe operations are `.with_rect()` and `.with_src()`:
|
||||||
|
// - There is currently no set destination so the destination rectangle can't be out
|
||||||
|
// of bounds
|
||||||
|
// - The `x`, `y` offsets are within the source bitmap, as ensured by the preceding
|
||||||
|
// code.
|
||||||
|
// - The source bitmap completely covers the destination rectangle, which is also
|
||||||
|
// ensured by the preceding code.
|
||||||
|
// - We hold a immutable reference to the source bitmap in the `BitBltCopy`
|
||||||
|
// structure.
|
||||||
|
bitblt: unsafe {
|
||||||
|
ffi::gfx_bitblt_t::default()
|
||||||
|
.with_rect(r)
|
||||||
|
.with_src(src.bitmap, offset.x, offset.y)
|
||||||
|
.with_bg(src.bg_color)
|
||||||
|
.with_fg(src.fg_color)
|
||||||
|
},
|
||||||
|
src,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies a part of the source bitmap to the destination bitmap in RGB565
|
||||||
|
/// format.
|
||||||
|
pub fn rgb565_copy(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::RGB565);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination and source bitmaps are in the correct formats.
|
||||||
|
// - Source and destination coordinates are properly clipped
|
||||||
|
// - The DMA pending flag is set for both bitmaps after operations.
|
||||||
|
unsafe {
|
||||||
|
let bitblt = self.bitblt.with_dst(dst);
|
||||||
|
match self.src.format() {
|
||||||
|
BitmapFormat::MONO4 => ffi::gfx_rgb565_copy_mono4(&bitblt),
|
||||||
|
BitmapFormat::RGB565 => ffi::gfx_rgb565_copy_rgb565(&bitblt),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src.bitmap.mark_dma_pending();
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blends a part of the source bitmap with the destination bitmap in RGB565
|
||||||
|
/// format.
|
||||||
|
pub fn rgb565_blend(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::RGB565);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination and source bitmaps are in the correct formats.
|
||||||
|
// - Source and destination coordinates are properly clipped
|
||||||
|
// - The DMA pending flag is set for both bitmaps after operations.
|
||||||
|
unsafe {
|
||||||
|
let bitblt = self.bitblt.with_dst(dst);
|
||||||
|
match self.src.format() {
|
||||||
|
BitmapFormat::MONO4 => ffi::gfx_rgb565_blend_mono4(&bitblt),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src.bitmap.mark_dma_pending();
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies a part of the source bitmap to the destination bitmap in RGBA8888
|
||||||
|
/// format.
|
||||||
|
pub fn rgba8888_copy(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::RGBA8888);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination and source bitmaps are in the correct formats.
|
||||||
|
// - Source and destination coordinates are properly clipped
|
||||||
|
// - The DMA pending flag is set for both bitmaps after operations.
|
||||||
|
unsafe {
|
||||||
|
let bitblt = self.bitblt.with_dst(dst);
|
||||||
|
match self.src.format() {
|
||||||
|
BitmapFormat::MONO4 => ffi::gfx_rgba8888_copy_mono4(&bitblt),
|
||||||
|
BitmapFormat::RGB565 => ffi::gfx_rgba8888_copy_rgb565(&bitblt),
|
||||||
|
BitmapFormat::RGBA8888 => ffi::gfx_rgba8888_copy_rgba8888(&bitblt),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src.bitmap.mark_dma_pending();
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blends a part of the source bitmap with the destination bitmap in
|
||||||
|
/// RGBA8888 format.
|
||||||
|
pub fn rgba8888_blend(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::RGBA8888);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination and source bitmaps are in the correct formats.
|
||||||
|
// - Source and destination coordinates are properly clipped
|
||||||
|
// - The DMA pending flag is set for both bitmaps after operations.
|
||||||
|
unsafe {
|
||||||
|
let bitblt = self.bitblt.with_dst(dst);
|
||||||
|
match self.src.format() {
|
||||||
|
BitmapFormat::MONO4 => ffi::gfx_rgba8888_blend_mono4(&bitblt),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src.bitmap.mark_dma_pending();
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies a part of the source bitmap to the destination bitmap in MONO8
|
||||||
|
/// format.
|
||||||
|
pub fn mono8_copy(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::MONO8);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination and source bitmaps are in the correct formats.
|
||||||
|
// - Source and destination coordinates are properly clipped
|
||||||
|
// - The DMA pending flag is set for both bitmaps after operations.
|
||||||
|
unsafe {
|
||||||
|
let bitblt = self.bitblt.with_dst(dst);
|
||||||
|
match self.src.format() {
|
||||||
|
BitmapFormat::MONO1P => ffi::gfx_mono8_copy_mono1p(&bitblt),
|
||||||
|
BitmapFormat::MONO4 => ffi::gfx_mono8_copy_mono4(&bitblt),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src.bitmap.mark_dma_pending();
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blends a part of the source bitmap with the destination bitmap in MONO8
|
||||||
|
/// format.
|
||||||
|
pub fn mono8_blend(&self, dst: &mut Bitmap) {
|
||||||
|
assert!(dst.format() == BitmapFormat::MONO8);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The destination and source bitmaps are in the correct formats.
|
||||||
|
// - Source and destination coordinates are properly clipped
|
||||||
|
// - The DMA pending flag is set for both bitmaps after operations.
|
||||||
|
unsafe {
|
||||||
|
let bitblt = self.bitblt.with_dst(dst);
|
||||||
|
match self.src.format() {
|
||||||
|
BitmapFormat::MONO1P => ffi::gfx_mono8_blend_mono1p(&bitblt),
|
||||||
|
BitmapFormat::MONO4 => ffi::gfx_mono8_blend_mono4(&bitblt),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src.bitmap.mark_dma_pending();
|
||||||
|
dst.mark_dma_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies a part of the source bitmap to the display.
|
||||||
|
///
|
||||||
|
/// - The source bitmap uses the RGB565 format.
|
||||||
|
#[cfg(all(not(feature = "xframebuffer"), feature = "new_rendering"))]
|
||||||
|
pub fn display_copy(&self) {
|
||||||
|
assert!(self.bitblt.dst_x + self.bitblt.width <= ffi::DISPLAY_RESX as u16);
|
||||||
|
assert!(self.bitblt.dst_y + self.bitblt.height <= ffi::DISPLAY_RESY as u16);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The source bitmap is in the correct formats.
|
||||||
|
// - Source and destination coordinates are properly clipped
|
||||||
|
// - The destination rectangle is completely inside the display.
|
||||||
|
// - The DMA pending flag is set for src bitmap after operations.
|
||||||
|
unsafe {
|
||||||
|
match self.src.format() {
|
||||||
|
BitmapFormat::RGB565 => ffi::display_copy_rgb565(&self.bitblt),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src.bitmap.mark_dma_pending();
|
||||||
|
}
|
||||||
|
}
|
@ -13,11 +13,11 @@ pub use ffi::{
|
|||||||
|
|
||||||
#[cfg(all(feature = "framebuffer", not(feature = "framebuffer32bit")))]
|
#[cfg(all(feature = "framebuffer", not(feature = "framebuffer32bit")))]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct FrameBuffer(*mut u16);
|
pub struct FrameBuffer(pub *mut u16);
|
||||||
|
|
||||||
#[cfg(all(feature = "framebuffer", feature = "framebuffer32bit"))]
|
#[cfg(all(feature = "framebuffer", feature = "framebuffer32bit"))]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct FrameBuffer(*mut u32);
|
pub struct FrameBuffer(pub *mut u32);
|
||||||
|
|
||||||
pub fn backlight(val: i32) -> i32 {
|
pub fn backlight(val: i32) -> i32 {
|
||||||
unsafe { ffi::display_backlight(val) }
|
unsafe { ffi::display_backlight(val) }
|
||||||
@ -98,7 +98,9 @@ pub fn get_fb_addr() -> FrameBuffer {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg(all(not(feature = "framebuffer"), feature = "disp_i8080_8bit_dw"))]
|
#[cfg(all(not(feature = "framebuffer"), feature = "disp_i8080_8bit_dw"))]
|
||||||
|
#[allow(unused_variables)]
|
||||||
pub fn pixeldata(c: u16) {
|
pub fn pixeldata(c: u16) {
|
||||||
|
#[cfg(not(feature = "new_rendering"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8);
|
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8);
|
||||||
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8);
|
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8);
|
||||||
@ -178,3 +180,17 @@ pub fn clear() {
|
|||||||
ffi::display_clear();
|
ffi::display_clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "xframebuffer")]
|
||||||
|
pub fn get_frame_buffer() -> (&'static mut [u8], usize) {
|
||||||
|
let fb_info = unsafe { ffi::display_get_frame_buffer() };
|
||||||
|
|
||||||
|
let fb = unsafe {
|
||||||
|
core::slice::from_raw_parts_mut(
|
||||||
|
fb_info.ptr as *mut u8,
|
||||||
|
DISPLAY_RESY as usize * fb_info.stride,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
(fb, fb_info.stride)
|
||||||
|
}
|
||||||
|
@ -3,12 +3,15 @@ pub mod bip39;
|
|||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
pub mod fatal_error;
|
pub mod fatal_error;
|
||||||
#[cfg(feature = "ui")]
|
#[cfg(feature = "ui")]
|
||||||
|
pub mod bitblt;
|
||||||
|
#[cfg(feature = "ui")]
|
||||||
pub mod display;
|
pub mod display;
|
||||||
#[cfg(feature = "dma2d")]
|
#[cfg(feature = "dma2d")]
|
||||||
pub mod dma2d;
|
pub mod dma2d;
|
||||||
mod ffi;
|
mod ffi;
|
||||||
#[cfg(feature = "haptic")]
|
#[cfg(feature = "haptic")]
|
||||||
pub mod haptic;
|
pub mod haptic;
|
||||||
|
|
||||||
pub mod io;
|
pub mod io;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod random;
|
pub mod random;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::ffi;
|
use super::ffi;
|
||||||
use core::{marker::PhantomData, mem::MaybeUninit, ptr};
|
use crate::io::BinaryData;
|
||||||
|
use core::{cell::RefCell, marker::PhantomData, mem::MaybeUninit, ptr};
|
||||||
|
|
||||||
pub const UZLIB_WINDOW_SIZE: usize = 1 << 10;
|
pub const UZLIB_WINDOW_SIZE: usize = 1 << 10;
|
||||||
pub use ffi::uzlib_uncomp;
|
pub use ffi::uzlib_uncomp;
|
||||||
@ -19,7 +20,7 @@ impl<'a> UzlibContext<'a> {
|
|||||||
pub fn new(src: &'a [u8], window: Option<&'a mut [u8; UZLIB_WINDOW_SIZE]>) -> Self {
|
pub fn new(src: &'a [u8], window: Option<&'a mut [u8; UZLIB_WINDOW_SIZE]>) -> Self {
|
||||||
let mut ctx = Self {
|
let mut ctx = Self {
|
||||||
uncomp: uzlib_uncomp::default(),
|
uncomp: uzlib_uncomp::default(),
|
||||||
src_data: Default::default(),
|
src_data: PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -55,6 +56,158 @@ impl<'a> UzlibContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceReadContext<'a> {
|
||||||
|
/// Compressed data
|
||||||
|
data: BinaryData<'a>,
|
||||||
|
/// Ofsset in the compressed data
|
||||||
|
// (points to the next byte to read)
|
||||||
|
offset: usize,
|
||||||
|
/// Internal buffer for reading compressed data
|
||||||
|
buf: [u8; 128],
|
||||||
|
buf_head: usize,
|
||||||
|
buf_tail: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SourceReadContext<'a> {
|
||||||
|
pub fn new(data: BinaryData<'a>, offset: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
offset,
|
||||||
|
buf: [0; 128],
|
||||||
|
buf_head: 0,
|
||||||
|
buf_tail: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill the uncomp struct with the appropriate pointers to the source data
|
||||||
|
pub fn prepare_uncomp(&self, uncomp: &mut ffi::uzlib_uncomp) {
|
||||||
|
// SAFETY: the offsets are within the buffer bounds.
|
||||||
|
// - buf_head is either 0 or advanced by uzlib to at most buf_tail (via
|
||||||
|
// advance())
|
||||||
|
// - buf_tail is at most the buffer size (via reader_callback())
|
||||||
|
unsafe {
|
||||||
|
uncomp.source = self.buf.as_ptr().add(self.buf_head);
|
||||||
|
uncomp.source_limit = self.buf.as_ptr().add(self.buf_tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance the internal buffer after a read operation
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The operation is only valid on an uncomp struct that has been filled via
|
||||||
|
/// `prepare_uncomp` and the source pointer has been updated by the uzlib
|
||||||
|
/// library after a single uncompress operation.
|
||||||
|
pub unsafe fn advance(&mut self, uncomp: &ffi::uzlib_uncomp) {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: we trust uzlib to move the `source` pointer only up to `source_limit`
|
||||||
|
self.buf_head = uncomp.source.offset_from(self.buf.as_ptr()) as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement uzlib reader callback.
|
||||||
|
///
|
||||||
|
/// If the uncomp buffer is exhausted, a callback is invoked that should (a)
|
||||||
|
/// read one byte of data, and optionally (b) update the uncomp buffer
|
||||||
|
/// with more data.
|
||||||
|
pub fn reader_callback(&mut self, uncomp: &mut ffi::uzlib_uncomp) -> Option<u8> {
|
||||||
|
// fill the internal buffer first
|
||||||
|
let bytes_read = self.data.read(self.offset, self.buf.as_mut());
|
||||||
|
self.buf_head = 0;
|
||||||
|
self.buf_tail = bytes_read;
|
||||||
|
// advance total offset
|
||||||
|
self.offset += bytes_read;
|
||||||
|
|
||||||
|
if bytes_read > 0 {
|
||||||
|
// "read" one byte
|
||||||
|
self.buf_head += 1;
|
||||||
|
// update the uncomp struct
|
||||||
|
self.prepare_uncomp(uncomp);
|
||||||
|
// return the first byte read
|
||||||
|
Some(self.buf[0])
|
||||||
|
} else {
|
||||||
|
// EOF
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ZlibInflate<'a> {
|
||||||
|
/// Compressed data reader
|
||||||
|
data: RefCell<SourceReadContext<'a>>,
|
||||||
|
/// Uzlib context
|
||||||
|
uncomp: ffi::uzlib_uncomp,
|
||||||
|
window: PhantomData<&'a [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ZlibInflate<'a> {
|
||||||
|
/// Creates a new `ZlibInflate` instance.
|
||||||
|
/// - `data` - compressed data
|
||||||
|
/// - `offset` - initial offset in the compressed data
|
||||||
|
/// - `window` - window buffer for decompression
|
||||||
|
pub fn new(
|
||||||
|
data: BinaryData<'a>,
|
||||||
|
offset: usize,
|
||||||
|
window: &'a mut [u8; UZLIB_WINDOW_SIZE],
|
||||||
|
) -> Self {
|
||||||
|
let mut inflate = Self {
|
||||||
|
data: RefCell::new(SourceReadContext::new(data, offset)),
|
||||||
|
uncomp: uzlib_uncomp::default(),
|
||||||
|
window: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// `window` is a valid mutable slice with the size of UZLIB_WINDOW_SIZE
|
||||||
|
// it remains valid until the `ZlibInflate` instance is dropped
|
||||||
|
unsafe {
|
||||||
|
let window_ptr = window.as_mut_ptr() as _;
|
||||||
|
let window_size = UZLIB_WINDOW_SIZE as u32;
|
||||||
|
ffi::uzlib_uncompress_init(&mut inflate.uncomp, window_ptr, window_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
inflate
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads uncompressed data into the destination buffer.
|
||||||
|
/// Returns `Ok(true)` if all data was read
|
||||||
|
pub fn read(&mut self, dest: &mut [u8]) -> Result<bool, ()> {
|
||||||
|
// Fill out source data pointers
|
||||||
|
self.data.borrow().prepare_uncomp(&mut self.uncomp);
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
// Source data callback (used to read more data when source is exhausted)
|
||||||
|
self.uncomp.source_read_cb = Some(zlib_reader_callback);
|
||||||
|
// Context for the source data callback
|
||||||
|
self.uncomp.source_read_cb_context = &self.data as *const _ as *mut cty::c_void;
|
||||||
|
|
||||||
|
// Destination buffer
|
||||||
|
self.uncomp.dest = dest.as_mut_ptr();
|
||||||
|
self.uncomp.dest_limit = self.uncomp.dest.add(dest.len());
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// Even if the `ZlibInflate` instance is moved, the source and context
|
||||||
|
// pointers are still valid as we set them before calling `uzlib_uncompress`
|
||||||
|
let res = ffi::uzlib_uncompress(&mut self.uncomp);
|
||||||
|
|
||||||
|
// `uzlib_uncompress` moved self.uncomp.source to the next byte to read
|
||||||
|
// so we can now update `buf_head` to reflect the new position
|
||||||
|
// SAFETY: we have just called `uzlib_uncompress`
|
||||||
|
self.data.borrow_mut().advance(&self.uncomp);
|
||||||
|
|
||||||
|
res
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear the source read callback (just for safety)
|
||||||
|
self.uncomp.source_read_cb_context = core::ptr::null_mut();
|
||||||
|
|
||||||
|
match res {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Skips a specified number of bytes (`nbytes`) in the uncompressed stream.
|
/// Skips a specified number of bytes (`nbytes`) in the uncompressed stream.
|
||||||
///
|
///
|
||||||
@ -65,8 +218,25 @@ impl<'a> UzlibContext<'a> {
|
|||||||
for i in (0..nbytes).step_by(sink.len()) {
|
for i in (0..nbytes).step_by(sink.len()) {
|
||||||
let chunk_len = core::cmp::min(sink.len(), nbytes - i);
|
let chunk_len = core::cmp::min(sink.len(), nbytes - i);
|
||||||
let chunk = &mut sink[0..chunk_len];
|
let chunk = &mut sink[0..chunk_len];
|
||||||
result = self.uncompress(chunk)?;
|
result = self.read(chunk)?;
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This function is called by the uzlib library to read more data from the
|
||||||
|
/// input stream.
|
||||||
|
unsafe extern "C" fn zlib_reader_callback(uncomp: *mut ffi::uzlib_uncomp) -> i32 {
|
||||||
|
// SAFETY: we assume that passed-in uncomp is not null and that we own it
|
||||||
|
// exclusively (ensured by passing it as &mut into uzlib_uncompress())
|
||||||
|
let uncomp = unwrap!(unsafe { uncomp.as_mut() });
|
||||||
|
let ctx = uncomp.source_read_cb_context as *const RefCell<SourceReadContext>;
|
||||||
|
// SAFETY: we assume that ctx is a valid pointer to the refcell holding the
|
||||||
|
// source data
|
||||||
|
let mut ctx = unwrap!(unsafe { ctx.as_ref() }).borrow_mut();
|
||||||
|
|
||||||
|
match ctx.reader_callback(uncomp) {
|
||||||
|
Some(byte) => byte as i32,
|
||||||
|
None => -1, // EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -98,7 +98,7 @@ impl Color {
|
|||||||
let r = (fg.r() as u16) * fg_mul + (self.r() as u16) * bg_mul;
|
let r = (fg.r() as u16) * fg_mul + (self.r() as u16) * bg_mul;
|
||||||
let g = (fg.g() as u16) * fg_mul + (self.g() as u16) * bg_mul;
|
let g = (fg.g() as u16) * fg_mul + (self.g() as u16) * bg_mul;
|
||||||
let b = (fg.b() as u16) * fg_mul + (self.b() as u16) * bg_mul;
|
let b = (fg.b() as u16) * fg_mul + (self.b() as u16) * bg_mul;
|
||||||
Color::rgb((r >> 8) as u8, (g >> 8) as u8, (b >> 8) as u8)
|
Color::rgb((r / 255) as u8, (g / 255) as u8, (b / 255) as u8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
ui::{
|
ui::{
|
||||||
constant,
|
constant,
|
||||||
geometry::{Offset, Point, Rect},
|
geometry::{Offset, Point, Rect},
|
||||||
|
shape::{Bitmap, BitmapFormat},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use core::slice;
|
use core::slice;
|
||||||
@ -41,12 +42,12 @@ impl Glyph {
|
|||||||
let width = *data.offset(0) as i16;
|
let width = *data.offset(0) as i16;
|
||||||
let height = *data.offset(1) as i16;
|
let height = *data.offset(1) as i16;
|
||||||
|
|
||||||
let data_bits = constant::FONT_BPP * width * height;
|
let data_bytes = match constant::FONT_BPP {
|
||||||
|
1 => (width * height + 7) / 8, // packed bits
|
||||||
let data_bytes = if data_bits % 8 == 0 {
|
2 => (width * height + 3) / 4, // packed bits
|
||||||
data_bits / 8
|
4 => (width + 1) / 2 * height, // row aligned to bytes
|
||||||
} else {
|
8 => width * height,
|
||||||
(data_bits / 8) + 1
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Glyph {
|
Glyph {
|
||||||
@ -119,6 +120,26 @@ impl Glyph {
|
|||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bitmap(&self) -> Bitmap<'static> {
|
||||||
|
match constant::FONT_BPP {
|
||||||
|
1 => unwrap!(Bitmap::new(
|
||||||
|
BitmapFormat::MONO1P,
|
||||||
|
None,
|
||||||
|
Offset::new(self.width, self.height),
|
||||||
|
None,
|
||||||
|
self.data,
|
||||||
|
)),
|
||||||
|
4 => unwrap!(Bitmap::new(
|
||||||
|
BitmapFormat::MONO4,
|
||||||
|
None,
|
||||||
|
Offset::new(self.width, self.height),
|
||||||
|
None,
|
||||||
|
self.data,
|
||||||
|
)),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Font constants. Keep in sync with FONT_ definitions in
|
/// Font constants. Keep in sync with FONT_ definitions in
|
||||||
@ -243,6 +264,25 @@ impl Font {
|
|||||||
constant::LINE_SPACE + self.text_height()
|
constant::LINE_SPACE + self.text_height()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper functions for **horizontal** text centering.
|
||||||
|
///
|
||||||
|
/// The `text` is centered between `start` and `end`.
|
||||||
|
///
|
||||||
|
/// Returns x-coordinate of the centered text start (including left
|
||||||
|
/// bearing).
|
||||||
|
pub fn horz_center(&self, start: i16, end: i16, text: &str) -> i16 {
|
||||||
|
(start + end - self.visible_text_width(text)) / 2 - self.start_x_bearing(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper functions for **vertical** text centering.
|
||||||
|
///
|
||||||
|
/// The `text` is centered between `start` and `end`.
|
||||||
|
///
|
||||||
|
/// Returns y-coordinate of the centered text baseline.
|
||||||
|
pub fn vert_center(&self, start: i16, end: i16, text: &str) -> i16 {
|
||||||
|
(start + end + self.visible_text_height(text)) / 2
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_glyph(self, ch: char) -> Glyph {
|
pub fn get_glyph(self, ch: char) -> Glyph {
|
||||||
let gl_data = display::get_char_glyph(ch as u16, self.into());
|
let gl_data = display::get_char_glyph(ch as u16, self.into());
|
||||||
|
|
||||||
|
225
core/embed/rust/src/ui/display/image.rs
Normal file
225
core/embed/rust/src/ui/display/image.rs
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
use crate::{io::BinaryData, ui::geometry::Offset};
|
||||||
|
|
||||||
|
impl<'a> BinaryData<'a> {
|
||||||
|
fn read_u8(&self, ofs: usize) -> Option<u8> {
|
||||||
|
let mut buff: [u8; 1] = [0; 1];
|
||||||
|
if self.read(ofs, buff.as_mut()) == buff.len() {
|
||||||
|
Some(buff[0])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u16_le(&self, ofs: usize) -> Option<u16> {
|
||||||
|
let mut buff: [u8; 2] = [0; 2];
|
||||||
|
if self.read(ofs, buff.as_mut()) == buff.len() {
|
||||||
|
Some(u16::from_le_bytes(buff))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u16_be(&self, ofs: usize) -> Option<u16> {
|
||||||
|
let mut buff: [u8; 2] = [0; 2];
|
||||||
|
if self.read(ofs, buff.as_mut()) == buff.len() {
|
||||||
|
Some(u16::from_be_bytes(buff))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u32_le(&self, ofs: usize) -> Option<u32> {
|
||||||
|
let mut buff: [u8; 4] = [0; 4];
|
||||||
|
if self.read(ofs, buff.as_mut()) == buff.len() {
|
||||||
|
Some(u32::from_le_bytes(buff))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)]
|
||||||
|
pub enum ToifFormat {
|
||||||
|
FullColorBE = 0, // big endian
|
||||||
|
GrayScaleOH = 1, // odd hi
|
||||||
|
FullColorLE = 2, // little endian
|
||||||
|
GrayScaleEH = 3, // even hi
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ToifInfo {
|
||||||
|
format: ToifFormat,
|
||||||
|
size: Offset,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToifInfo {
|
||||||
|
pub const HEADER_LENGTH: usize = 12;
|
||||||
|
|
||||||
|
pub fn parse(image: BinaryData) -> Option<Self> {
|
||||||
|
if image.read_u8(0)? != b'T' && image.read_u8(1)? != b'O' && image.read_u8(2)? != b'I' {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let format = match image.read_u8(3)? {
|
||||||
|
b'f' => ToifFormat::FullColorBE,
|
||||||
|
b'g' => ToifFormat::GrayScaleOH,
|
||||||
|
b'F' => ToifFormat::FullColorLE,
|
||||||
|
b'G' => ToifFormat::GrayScaleEH,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = image.read_u16_le(4)?;
|
||||||
|
let height = image.read_u16_le(6)?;
|
||||||
|
let len = image.read_u32_le(8)? as usize;
|
||||||
|
|
||||||
|
if width > 1024 || height > 1024 || len > 65536 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if len + Self::HEADER_LENGTH != image.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
format,
|
||||||
|
size: Offset::new(width as i16, height as i16),
|
||||||
|
len,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(&self) -> ToifFormat {
|
||||||
|
self.format
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Offset {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> i16 {
|
||||||
|
self.size.x
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> i16 {
|
||||||
|
self.size.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_grayscale(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.format,
|
||||||
|
ToifFormat::GrayScaleOH | ToifFormat::GrayScaleEH
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stride(&self) -> usize {
|
||||||
|
if self.is_grayscale() {
|
||||||
|
(self.width() + 1) as usize / 2
|
||||||
|
} else {
|
||||||
|
self.width() as usize * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JpegInfo {
|
||||||
|
size: Offset,
|
||||||
|
mcu_height: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JpegInfo {
|
||||||
|
pub fn parse(image: BinaryData) -> Option<Self> {
|
||||||
|
const M_SOI: u16 = 0xFFD8;
|
||||||
|
const M_SOF0: u16 = 0xFFC0;
|
||||||
|
const M_DRI: u16 = 0xFFDD;
|
||||||
|
const M_RST0: u16 = 0xFFD0;
|
||||||
|
const M_RST7: u16 = 0xFFD7;
|
||||||
|
const M_SOS: u16 = 0xFFDA;
|
||||||
|
const M_EOI: u16 = 0xFFD9;
|
||||||
|
|
||||||
|
let mut result = None;
|
||||||
|
let mut ofs = 0;
|
||||||
|
|
||||||
|
while image.read_u16_be(ofs)? != M_SOI {
|
||||||
|
ofs += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let marker = image.read_u16_be(ofs)?;
|
||||||
|
|
||||||
|
if (marker & 0xFF00) != 0xFF00 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
ofs += 2;
|
||||||
|
|
||||||
|
ofs += match marker {
|
||||||
|
M_SOI => 0,
|
||||||
|
M_SOF0 => {
|
||||||
|
let w = image.read_u16_be(ofs + 3)? as i16;
|
||||||
|
let h = image.read_u16_be(ofs + 5)? as i16;
|
||||||
|
// Number of components
|
||||||
|
let nc = image.read_u8(ofs + 7)?;
|
||||||
|
if (nc != 1) && (nc != 3) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// Sampling factor of the first component
|
||||||
|
let c1 = image.read_u8(ofs + 9)?;
|
||||||
|
if (c1 != 0x11) && (c1 != 0x21) & (c1 != 0x22) {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let mcu_height = (8 * (c1 & 15)) as i16;
|
||||||
|
|
||||||
|
// We now have all the information we need, but
|
||||||
|
// we will not exit the loop yet until we find the
|
||||||
|
// M_SOS marker. While this does not ensure absolute
|
||||||
|
// correctness, it improves the verification slightly.
|
||||||
|
result = Some(JpegInfo {
|
||||||
|
size: Offset::new(w, h),
|
||||||
|
mcu_height,
|
||||||
|
});
|
||||||
|
|
||||||
|
image.read_u16_be(ofs)?
|
||||||
|
}
|
||||||
|
M_DRI => 4,
|
||||||
|
M_EOI => return None,
|
||||||
|
M_RST0..=M_RST7 => 0,
|
||||||
|
M_SOS => break,
|
||||||
|
_ => image.read_u16_be(ofs)?,
|
||||||
|
} as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Offset {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> i16 {
|
||||||
|
self.size.x
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> i16 {
|
||||||
|
self.size.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mcu_height(&self) -> i16 {
|
||||||
|
self.mcu_height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ImageInfo {
|
||||||
|
Invalid,
|
||||||
|
Toif(ToifInfo),
|
||||||
|
Jpeg(JpegInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageInfo {
|
||||||
|
pub fn parse(image: BinaryData) -> Self {
|
||||||
|
if let Some(info) = ToifInfo::parse(image) {
|
||||||
|
Self::Toif(info)
|
||||||
|
} else if let Some(info) = JpegInfo::parse(image) {
|
||||||
|
Self::Jpeg(info)
|
||||||
|
} else {
|
||||||
|
Self::Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
|
pub mod image;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
#[cfg(feature = "jpeg")]
|
#[cfg(feature = "jpeg")]
|
||||||
pub mod tjpgd;
|
pub mod tjpgd;
|
||||||
|
@ -271,6 +271,10 @@ impl<'i> Toif<'i> {
|
|||||||
&self.data[TOIF_HEADER_LENGTH..]
|
&self.data[TOIF_HEADER_LENGTH..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn original_data(&self) -> &'i [u8] {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uncompress(&self, dest: &mut [u8]) {
|
pub fn uncompress(&self, dest: &mut [u8]) {
|
||||||
let mut ctx = self.decompression_context(None);
|
let mut ctx = self.decompression_context(None);
|
||||||
unwrap!(ctx.uncompress(dest));
|
unwrap!(ctx.uncompress(dest));
|
||||||
|
@ -545,6 +545,7 @@ pub enum Alignment {
|
|||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub struct Alignment2D(pub Alignment, pub Alignment);
|
pub struct Alignment2D(pub Alignment, pub Alignment);
|
||||||
|
|
||||||
impl Alignment2D {
|
impl Alignment2D {
|
||||||
@ -552,6 +553,8 @@ impl Alignment2D {
|
|||||||
pub const TOP_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Start);
|
pub const TOP_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Start);
|
||||||
pub const TOP_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Start);
|
pub const TOP_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Start);
|
||||||
pub const CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Center);
|
pub const CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Center);
|
||||||
|
pub const CENTER_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::Center);
|
||||||
|
pub const CENTER_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Center);
|
||||||
pub const BOTTOM_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::End);
|
pub const BOTTOM_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::End);
|
||||||
pub const BOTTOM_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::End);
|
pub const BOTTOM_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::End);
|
||||||
pub const BOTTOM_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::End);
|
pub const BOTTOM_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::End);
|
||||||
|
@ -8,6 +8,7 @@ pub mod display;
|
|||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
pub mod lerp;
|
pub mod lerp;
|
||||||
|
pub mod shape;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
128
core/embed/rust/src/ui/shape/bar.rs
Normal file
128
core/embed/rust/src/ui/shape/bar.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
use crate::ui::{display::Color, geometry::Rect};
|
||||||
|
|
||||||
|
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
/// A shape for the rendering variuous type of rectangles.
|
||||||
|
pub struct Bar {
|
||||||
|
/// Rectangle position and dimenstion
|
||||||
|
area: Rect,
|
||||||
|
/// Foreground color (default None)
|
||||||
|
fg_color: Option<Color>,
|
||||||
|
/// Background color (default None)
|
||||||
|
bg_color: Option<Color>,
|
||||||
|
/// Thickness (default 0)
|
||||||
|
thickness: i16,
|
||||||
|
/// Corner radius (default 0)
|
||||||
|
radius: i16,
|
||||||
|
/// Alpha (default 255)
|
||||||
|
alpha: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bar {
|
||||||
|
pub fn new(area: Rect) -> Self {
|
||||||
|
Self {
|
||||||
|
area,
|
||||||
|
fg_color: None,
|
||||||
|
bg_color: None,
|
||||||
|
thickness: 1,
|
||||||
|
radius: 0,
|
||||||
|
alpha: 255,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fg(self, fg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
fg_color: Some(fg_color),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_bg(self, bg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
bg_color: Some(bg_color),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_radius(self, radius: i16) -> Self {
|
||||||
|
Self { radius, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_thickness(self, thickness: i16) -> Self {
|
||||||
|
Self { thickness, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_alpha(self, alpha: u8) -> Self {
|
||||||
|
Self { alpha, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||||
|
renderer.render_shape(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape<'_> for Bar {
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
self.area
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||||
|
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) {
|
||||||
|
// NOTE: drawing of rounded bars without a background
|
||||||
|
// is not supported. If we needed it, we would have to
|
||||||
|
// introduce a new function in RgbCanvas.
|
||||||
|
|
||||||
|
// TODO: panic! in unsupported scenarious
|
||||||
|
|
||||||
|
let th = match self.fg_color {
|
||||||
|
Some(_) => self.thickness,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.radius == 0 {
|
||||||
|
if let Some(fg_color) = self.fg_color {
|
||||||
|
// outline
|
||||||
|
if th > 0 {
|
||||||
|
let r = self.area;
|
||||||
|
canvas.fill_rect(Rect { y1: r.y0 + th, ..r }, fg_color, self.alpha);
|
||||||
|
canvas.fill_rect(Rect { x1: r.x0 + th, ..r }, fg_color, self.alpha);
|
||||||
|
canvas.fill_rect(Rect { x0: r.x1 - th, ..r }, fg_color, self.alpha);
|
||||||
|
canvas.fill_rect(Rect { y0: r.y1 - th, ..r }, fg_color, self.alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(bg_color) = self.bg_color {
|
||||||
|
// background
|
||||||
|
let bg_r = self.area.shrink(th);
|
||||||
|
canvas.fill_rect(bg_r, bg_color, self.alpha);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(fg_color) = self.fg_color {
|
||||||
|
if th > 0 {
|
||||||
|
if self.bg_color.is_some() {
|
||||||
|
canvas.fill_round_rect(self.area, self.radius, fg_color, self.alpha);
|
||||||
|
} else {
|
||||||
|
#[cfg(not(feature = "ui_antialiasing"))]
|
||||||
|
canvas.draw_round_rect(self.area, self.radius, fg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(bg_color) = self.bg_color {
|
||||||
|
let bg_r = self.area.shrink(th);
|
||||||
|
canvas.fill_round_rect(bg_r, self.radius, bg_color, self.alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ShapeClone<'s> for Bar {
|
||||||
|
fn clone_at_bump<T>(self, bump: &'s T) -> Option<&'s mut dyn Shape<'s>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'s>,
|
||||||
|
{
|
||||||
|
let clone = bump.alloc_t()?;
|
||||||
|
Some(clone.uninit.init(Bar { ..self }))
|
||||||
|
}
|
||||||
|
}
|
52
core/embed/rust/src/ui/shape/base.rs
Normal file
52
core/embed/rust/src/ui/shape/base.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use crate::ui::geometry::Rect;
|
||||||
|
|
||||||
|
use super::{Canvas, DrawingCache};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// trait Shape
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
/// This trait is used internally by so-called Renderers -
|
||||||
|
/// `DirectRenderer` & `ProgressiveRederer`.
|
||||||
|
///
|
||||||
|
/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered
|
||||||
|
/// must implement `Shape` trait.
|
||||||
|
///
|
||||||
|
/// `Shape` objects may use `DrawingCache` as a scratch-pad memory or for
|
||||||
|
/// caching expensive calculations results.
|
||||||
|
pub trait Shape<'s> {
|
||||||
|
/// Returns the smallest bounding rectangle containing whole parts of the
|
||||||
|
/// shape.
|
||||||
|
///
|
||||||
|
/// The function is used by renderer for optimization if the shape
|
||||||
|
/// must be renderer or not.
|
||||||
|
fn bounds(&self) -> Rect;
|
||||||
|
|
||||||
|
/// Draws shape on the canvas.
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'s>);
|
||||||
|
|
||||||
|
/// The function should release all allocated resources needed
|
||||||
|
/// for shape drawing.
|
||||||
|
///
|
||||||
|
/// It's called by renderer if the shape's draw() function won't be called
|
||||||
|
/// anymore.
|
||||||
|
fn cleanup(&mut self, cache: &DrawingCache<'s>);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// trait ShapeClone
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered
|
||||||
|
/// by `ProgressiveRender` must implement `ShapeClone`.
|
||||||
|
pub trait ShapeClone<'s> {
|
||||||
|
/// Clones a shape object at the specified memory bump.
|
||||||
|
///
|
||||||
|
/// The method is used by `ProgressiveRenderer` to store shape objects for
|
||||||
|
/// deferred drawing.
|
||||||
|
fn clone_at_bump<T>(self, bump: &'s T) -> Option<&'s mut dyn Shape<'s>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'s>;
|
||||||
|
}
|
360
core/embed/rust/src/ui/shape/bitmap.rs
Normal file
360
core/embed/rust/src/ui/shape/bitmap.rs
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
use crate::trezorhal::bitblt;
|
||||||
|
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// If this flag is set, a DMA operation on the pointed-to buffer
|
||||||
|
/// may be pending and so nobody is allowed to look at it --
|
||||||
|
/// either through a pre-existing reference (so the Bitmap object must not
|
||||||
|
/// be dropped) or through one of Bitmap's methods.
|
||||||
|
///
|
||||||
|
/// The `Bitmap` ensures that by `wait_for_dma()`ing the buffer before
|
||||||
|
/// dropping it or doing anything else with it.
|
||||||
|
dma_pending: Cell<bool>,
|
||||||
|
///
|
||||||
|
_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<usize>,
|
||||||
|
mut size: Offset,
|
||||||
|
min_height: Option<i16>,
|
||||||
|
buff: &'a [u8],
|
||||||
|
) -> Option<Self> {
|
||||||
|
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().align_offset(alignment) == 0);
|
||||||
|
assert!(stride % alignment == 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<usize>,
|
||||||
|
size: Offset,
|
||||||
|
min_height: Option<i16>,
|
||||||
|
buff: &'a mut [u8],
|
||||||
|
) -> Option<Self> {
|
||||||
|
let mut bitmap = Self::new(format, stride, size, min_height, buff)?;
|
||||||
|
bitmap.mutable = true;
|
||||||
|
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<T>(&self, row: i16) -> Option<&[T]> {
|
||||||
|
if row < 0 || row >= self.size.y {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = row as usize * (self.stride / core::mem::size_of::<T>());
|
||||||
|
|
||||||
|
if offset % core::mem::align_of::<T>() != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wait_for_dma();
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// The resulting slice is inside the bitmap and properly aligned.
|
||||||
|
// Potential DMA operation is finished.
|
||||||
|
Some(unsafe {
|
||||||
|
core::slice::from_raw_parts(
|
||||||
|
(self.ptr as *const T).add(offset),
|
||||||
|
self.stride / core::mem::size_of::<T>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the specified row as a mutable slice.
|
||||||
|
///
|
||||||
|
/// Returns None if row is out of range or
|
||||||
|
/// the bitmap is not set as mutable.
|
||||||
|
pub fn row_mut<T>(&mut self, row: i16) -> Option<&mut [T]> {
|
||||||
|
if !self.mutable {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if row < 0 || row >= self.size.y {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = row as usize * (self.stride / core::mem::size_of::<T>());
|
||||||
|
|
||||||
|
if offset % core::mem::align_of::<T>() != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wait_for_dma();
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// The bitmap is mutable.
|
||||||
|
// The resulting slice is inside the bitmap and properly aligned.
|
||||||
|
// Potential DMA operation is finished.
|
||||||
|
Some(unsafe {
|
||||||
|
core::slice::from_raw_parts_mut(
|
||||||
|
(self.ptr as *mut T).add(offset),
|
||||||
|
self.stride / core::mem::size_of::<T>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns specified consecutive rows as a mutable slice
|
||||||
|
///
|
||||||
|
/// Returns None if any of requested row is out of range or
|
||||||
|
/// the bitmap is not set as mutable.
|
||||||
|
pub fn rows_mut<T>(&mut self, row: i16, height: i16) -> Option<&mut [T]> {
|
||||||
|
if !self.mutable {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if row < 0 || height <= 0 || row + height > self.size.y {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = self.stride * row as usize;
|
||||||
|
|
||||||
|
if offset % core::mem::align_of::<T>() != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wait_for_dma();
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// The bitmap is mutable.
|
||||||
|
// The resulting slice is inside the bitmap and properly aligned.
|
||||||
|
// Potential DMA operation is finished.
|
||||||
|
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::<T>(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = self.stride * height as usize;
|
||||||
|
|
||||||
|
Some(&mut array[offset..offset + len])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return raw mut pointer to the specified bitmap row.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// `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() {
|
||||||
|
bitblt::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,
|
||||||
|
..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, ..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, ..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<T>(&self, row: i16) -> Option<&[T]> {
|
||||||
|
self.bitmap.row(row)
|
||||||
|
}
|
||||||
|
}
|
45
core/embed/rust/src/ui/shape/blur.rs
Normal file
45
core/embed/rust/src/ui/shape/blur.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use crate::ui::geometry::Rect;
|
||||||
|
|
||||||
|
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
pub struct Blurring {
|
||||||
|
// Blurred area
|
||||||
|
area: Rect,
|
||||||
|
/// Blurring kernel radius
|
||||||
|
radius: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shape for the blurring of a specified rectangle area.
|
||||||
|
impl Blurring {
|
||||||
|
pub fn new(area: Rect, radius: usize) -> Self {
|
||||||
|
Self { area, radius }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||||
|
renderer.render_shape(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape<'_> for Blurring {
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
self.area
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||||
|
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
|
||||||
|
canvas.blur_rect(self.area, self.radius, cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ShapeClone<'s> for Blurring {
|
||||||
|
fn clone_at_bump<T>(self, bump: &'s T) -> Option<&'s mut dyn Shape<'s>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'s>,
|
||||||
|
{
|
||||||
|
let clone = bump.alloc_t()?;
|
||||||
|
Some(clone.uninit.init(Blurring { ..self }))
|
||||||
|
}
|
||||||
|
}
|
61
core/embed/rust/src/ui/shape/cache/blur_cache.rs
vendored
Normal file
61
core/embed/rust/src/ui/shape/cache/blur_cache.rs
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
geometry::Offset,
|
||||||
|
shape::utils::{BlurAlgorithm, BlurBuff},
|
||||||
|
};
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
pub struct BlurCache<'a> {
|
||||||
|
algo: Option<BlurAlgorithm<'a>>,
|
||||||
|
buff: &'a UnsafeCell<BlurBuff>,
|
||||||
|
tag: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlurCache<'a> {
|
||||||
|
pub fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option<Self>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'alloc>,
|
||||||
|
{
|
||||||
|
let buff = bump
|
||||||
|
.alloc_t()?
|
||||||
|
.uninit
|
||||||
|
.init(UnsafeCell::new([0; core::mem::size_of::<BlurBuff>()]));
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
algo: None,
|
||||||
|
buff,
|
||||||
|
tag: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(
|
||||||
|
&mut self,
|
||||||
|
size: Offset,
|
||||||
|
radius: usize,
|
||||||
|
tag: Option<u32>,
|
||||||
|
) -> Result<(&mut BlurAlgorithm<'a>, u32), ()> {
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
if self.tag == tag {
|
||||||
|
return Ok((unwrap!(self.algo.as_mut()), self.tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the existing blurring inbstance holding
|
||||||
|
// a mutable reference to its scratchpad buffer
|
||||||
|
self.algo = None;
|
||||||
|
self.tag += 1;
|
||||||
|
|
||||||
|
// Now there's nobody else holding any reference to our buffer
|
||||||
|
// so we can get mutable reference and pass it to a new
|
||||||
|
// instance of the blurring algorithm
|
||||||
|
let buff = unsafe { &mut *self.buff.get() };
|
||||||
|
|
||||||
|
self.algo = Some(BlurAlgorithm::new(size, radius, buff)?);
|
||||||
|
|
||||||
|
Ok((unwrap!(self.algo.as_mut()), self.tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_bump_size() -> usize {
|
||||||
|
core::mem::size_of::<UnsafeCell<BlurBuff>>()
|
||||||
|
}
|
||||||
|
}
|
130
core/embed/rust/src/ui/shape/cache/drawing_cache.rs
vendored
Normal file
130
core/embed/rust/src/ui/shape/cache/drawing_cache.rs
vendored
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
use super::zlib_cache::ZlibCache;
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
use super::blur_cache::BlurCache;
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
use super::jpeg_cache::JpegCache;
|
||||||
|
|
||||||
|
use core::cell::{RefCell, RefMut};
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
const ALIGN_PAD: usize = 8;
|
||||||
|
|
||||||
|
#[cfg(feature = "xframebuff")]
|
||||||
|
const ZLIB_CACHE_SLOTS: usize = 1;
|
||||||
|
#[cfg(not(feature = "xframebuff"))]
|
||||||
|
const ZLIB_CACHE_SLOTS: usize = 3;
|
||||||
|
|
||||||
|
const RENDER_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD;
|
||||||
|
const IMAGE_BUFF_SIZE: usize = 2048 + ALIGN_PAD;
|
||||||
|
|
||||||
|
pub type ImageBuff = [u8; IMAGE_BUFF_SIZE];
|
||||||
|
pub type RenderBuff = [u8; RENDER_BUFF_SIZE];
|
||||||
|
|
||||||
|
pub type ImageBuffRef<'a> = RefMut<'a, ImageBuff>;
|
||||||
|
pub type RenderBuffRef<'a> = RefMut<'a, RenderBuff>;
|
||||||
|
|
||||||
|
pub struct DrawingCache<'a> {
|
||||||
|
image_buff: &'a RefCell<ImageBuff>,
|
||||||
|
zlib_cache: RefCell<ZlibCache<'a>>,
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
jpeg_cache: RefCell<JpegCache<'a>>,
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
blur_cache: RefCell<BlurCache<'a>>,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "xframebuff"))]
|
||||||
|
render_buff: &'a RefCell<RenderBuff>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_buf<'a, const S: usize, B>(bump: &'a B) -> Option<&'a RefCell<[u8; S]>>
|
||||||
|
where
|
||||||
|
B: LocalAllocLeakExt<'a>,
|
||||||
|
{
|
||||||
|
Some(bump.alloc_t()?.uninit.init(RefCell::new([0; S])))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DrawingCache<'a> {
|
||||||
|
pub fn new<TA, TB>(bump_a: &'a TA, bump_b: &'a TB) -> Self
|
||||||
|
where
|
||||||
|
TA: LocalAllocLeakExt<'a>,
|
||||||
|
TB: LocalAllocLeakExt<'a>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
image_buff: unwrap!(alloc_buf(bump_b), "Toif buff alloc"),
|
||||||
|
zlib_cache: RefCell::new(unwrap!(
|
||||||
|
ZlibCache::new(bump_a, ZLIB_CACHE_SLOTS),
|
||||||
|
"ZLIB cache alloc"
|
||||||
|
)),
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
jpeg_cache: RefCell::new(unwrap!(JpegCache::new(bump_a), "JPEG cache alloc")),
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
blur_cache: RefCell::new(unwrap!(BlurCache::new(bump_a), "Blur cache alloc")),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "xframebuff"))]
|
||||||
|
render_buff: unwrap!(alloc_buf(bump_b), "Render buff alloc"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an object for decompression of TOIF images
|
||||||
|
pub fn zlib(&self) -> RefMut<ZlibCache<'a>> {
|
||||||
|
self.zlib_cache.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an object for decompression of JPEG images
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
pub fn jpeg(&self) -> RefMut<JpegCache<'a>> {
|
||||||
|
self.jpeg_cache.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an object providing blurring algorithm
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
pub fn blur(&self) -> RefMut<BlurCache<'a>> {
|
||||||
|
self.blur_cache.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a buffer used for ProgressiveRenderer slice
|
||||||
|
#[cfg(not(feature = "xframebuff"))]
|
||||||
|
pub fn render_buff(&self) -> Option<RenderBuffRef<'a>> {
|
||||||
|
self.render_buff.try_borrow_mut().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a buffer for intended for drawing of
|
||||||
|
/// QrCode or ToifImage
|
||||||
|
pub fn image_buff(&self) -> Option<ImageBuffRef<'a>> {
|
||||||
|
self.image_buff.try_borrow_mut().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_bump_a_size() -> usize {
|
||||||
|
let mut size = 0;
|
||||||
|
|
||||||
|
size += ZlibCache::get_bump_size(ZLIB_CACHE_SLOTS);
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
{
|
||||||
|
size += JpegCache::get_bump_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
{
|
||||||
|
size += BlurCache::get_bump_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_bump_b_size() -> usize {
|
||||||
|
let mut size = 0;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "xframebuff"))]
|
||||||
|
{
|
||||||
|
size += core::mem::size_of::<RefCell<RenderBuff>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
size += core::mem::size_of::<RefCell<ImageBuff>>();
|
||||||
|
|
||||||
|
size
|
||||||
|
}
|
||||||
|
}
|
333
core/embed/rust/src/ui/shape/cache/jpeg_cache.rs
vendored
Normal file
333
core/embed/rust/src/ui/shape/cache/jpeg_cache.rs
vendored
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
use crate::{
|
||||||
|
io::BinaryData,
|
||||||
|
ui::{
|
||||||
|
display::tjpgd,
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
shape::{BasicCanvas, Bitmap, BitmapFormat, BitmapView, Canvas, Rgb565Canvas},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
// JDEC work buffer size
|
||||||
|
//
|
||||||
|
// number of quantization tables (n_qtbl) = 2..4 (typical 2)
|
||||||
|
// number of huffman tables (n_htbl) = 2..4 (typical 2)
|
||||||
|
// mcu size = 1 * 1 .. 2 * 2 = 1..4 (typical 4)
|
||||||
|
//
|
||||||
|
// hufflut_ac & hufflut_dc are required only if JD_FASTDECODE == 2 (default)
|
||||||
|
//
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// table | size calculation | MIN..MAX | TYP
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// qttbl | n_qtbl * size_of(i32) * 64 | 512..1024 | 512
|
||||||
|
// huffbits | n_htbl * size_of(u8) * 16 | 32..64 | 32
|
||||||
|
// huffcode | n_htbl * size_of(u16) * 256 | 1024..2048 | 1024
|
||||||
|
// huffdata | n_htbl * size_of(u8) * 256 | 512..1024 | 512
|
||||||
|
// hufflut_ac | n_htbl * size_of(u16) * 1024 | 4096..8192 | 4096
|
||||||
|
// hufflut_dc | n_htbl * size_of(u8) * 1024 | 2048..4096 | 2048
|
||||||
|
// workbuf | mcu_size * 192 + 64 | 256..832 | 832
|
||||||
|
// mcubuf | (mcu_size + 2) * size_of(u16) * 64 | 384..768 | 768
|
||||||
|
// inbuff | JD_SZBUF constant | 512..512 | 512
|
||||||
|
// ---------------------------------------------------------------|------
|
||||||
|
// SUM | | 9376..18560 | 10336
|
||||||
|
// ---------------------------------------------------------------|------
|
||||||
|
|
||||||
|
const JPEG_SCRATCHPAD_SIZE: usize = 10500; // the same const > 10336 as in original code
|
||||||
|
|
||||||
|
// Buffer for a cached row of JPEG MCUs (up to 240x16 RGB565 pixels)
|
||||||
|
const ALIGN_PAD: usize = 8;
|
||||||
|
const JPEG_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD;
|
||||||
|
|
||||||
|
pub struct JpegCache<'a> {
|
||||||
|
/// Reference to compressed data
|
||||||
|
image: Option<BinaryData<'a>>,
|
||||||
|
/// Value in range 0..3 leads into scale factor 1 << scale
|
||||||
|
scale: u8,
|
||||||
|
/// Reader for compressed data
|
||||||
|
reader: Option<BinaryDataReader<'a>>,
|
||||||
|
/// JPEG decoder instance
|
||||||
|
decoder: Option<tjpgd::JDEC<'a>>,
|
||||||
|
/// Scratchpad memory used by the JPEG decoder
|
||||||
|
/// (it's used just by our decoder and nobody else)
|
||||||
|
scratchpad: &'a UnsafeCell<[u8; JPEG_SCRATCHPAD_SIZE]>,
|
||||||
|
/// horizontal coordinate of cached row or None
|
||||||
|
/// (valid if row_canvas is Some)
|
||||||
|
row_y: i16,
|
||||||
|
/// Canvas for recently decoded row of MCU's
|
||||||
|
row_canvas: Option<Rgb565Canvas<'a>>,
|
||||||
|
/// Buffer for slice canvas
|
||||||
|
row_buff: &'a UnsafeCell<[u8; JPEG_BUFF_SIZE]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> JpegCache<'a> {
|
||||||
|
pub fn new<T>(bump: &'a T) -> Option<Self>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'a>,
|
||||||
|
{
|
||||||
|
let scratchpad = bump
|
||||||
|
.alloc_t()?
|
||||||
|
.uninit
|
||||||
|
.init(UnsafeCell::new([0; JPEG_SCRATCHPAD_SIZE]));
|
||||||
|
|
||||||
|
let row_buff = bump
|
||||||
|
.alloc_t()?
|
||||||
|
.uninit
|
||||||
|
.init(UnsafeCell::new([0; JPEG_BUFF_SIZE]));
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
image: None,
|
||||||
|
scale: 0,
|
||||||
|
reader: None,
|
||||||
|
decoder: None,
|
||||||
|
scratchpad,
|
||||||
|
row_y: 0,
|
||||||
|
row_canvas: None,
|
||||||
|
row_buff,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset<'i: 'a>(
|
||||||
|
&mut self,
|
||||||
|
image: Option<BinaryData<'a>>,
|
||||||
|
scale: u8,
|
||||||
|
) -> Result<(), tjpgd::Error> {
|
||||||
|
// Drop the existing decoder holding
|
||||||
|
// a mutable reference to the scratchpad and canvas buffer & c
|
||||||
|
self.decoder = None;
|
||||||
|
self.row_canvas = None;
|
||||||
|
|
||||||
|
if let Some(image) = image {
|
||||||
|
// Now there's nobody else holding any reference to our scratchpad buffer
|
||||||
|
// so we can get a mutable reference and pass it to a new
|
||||||
|
// instance of the JPEG decoder
|
||||||
|
let scratchpad = unsafe { &mut *self.scratchpad.get() };
|
||||||
|
// Prepare a input buffer
|
||||||
|
let mut input = BinaryDataReader::new(image);
|
||||||
|
// Initialize the decoder by reading headers from input
|
||||||
|
let mut decoder = tjpgd::JDEC::new(&mut input, scratchpad)?;
|
||||||
|
// Set decoder scale factor
|
||||||
|
decoder.set_scale(scale)?;
|
||||||
|
self.decoder = Some(decoder);
|
||||||
|
// Save modified input buffer
|
||||||
|
self.reader = Some(input);
|
||||||
|
} else {
|
||||||
|
self.reader = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.image = image;
|
||||||
|
self.scale = scale;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_for<'i: 'a>(&self, image: BinaryData<'i>, scale: u8) -> bool {
|
||||||
|
match self.image {
|
||||||
|
Some(current) => self.decoder.is_some() && current == image && scale == self.scale,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// left-top origin of output rectangle must be aligned to JPEG MCU size
|
||||||
|
pub fn decompress_mcu<'i: 'a>(
|
||||||
|
&mut self,
|
||||||
|
image: BinaryData<'i>,
|
||||||
|
scale: u8,
|
||||||
|
offset: Point,
|
||||||
|
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
|
||||||
|
) -> Result<(), tjpgd::Error> {
|
||||||
|
// Reset the slot if the JPEG image is different
|
||||||
|
if !self.is_for(image, scale) {
|
||||||
|
self.reset(Some(image), scale)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get coordinates of the next coming MCU
|
||||||
|
let decoder = unwrap!(self.decoder.as_ref()); // should never fail
|
||||||
|
let divisor = 1 << self.scale;
|
||||||
|
let next_mcu = Offset::new(
|
||||||
|
decoder.next_mcu().0 as i16 / divisor,
|
||||||
|
decoder.next_mcu().1 as i16 / divisor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get height of the MCUs (8 or 16pixels)
|
||||||
|
let mcu_height = decoder.mcu_height() / (1 << self.scale);
|
||||||
|
|
||||||
|
// Reset the decoder if pixel at the offset was already decoded
|
||||||
|
if offset.y < next_mcu.y || (offset.x < next_mcu.x && offset.y < next_mcu.y + mcu_height) {
|
||||||
|
self.reset(self.image, scale)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoder = unwrap!(self.decoder.as_mut()); // should never fail
|
||||||
|
let input = unwrap!(self.reader.as_mut()); // should never fail
|
||||||
|
let mut output = JpegFnOutput::new(output);
|
||||||
|
|
||||||
|
match decoder.decomp(input, &mut output) {
|
||||||
|
Ok(_) | Err(tjpgd::Error::Interrupted) => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decompress_row<'i: 'a>(
|
||||||
|
&mut self,
|
||||||
|
image: BinaryData<'i>,
|
||||||
|
scale: u8,
|
||||||
|
mut offset_y: i16,
|
||||||
|
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
|
||||||
|
) -> Result<(), tjpgd::Error> {
|
||||||
|
// Reset the slot if the JPEG image is different
|
||||||
|
if !self.is_for(image, scale) {
|
||||||
|
self.reset(Some(image), scale)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut row_canvas = self.row_canvas.take();
|
||||||
|
let mut row_y = self.row_y;
|
||||||
|
|
||||||
|
// Use cached data if possible
|
||||||
|
if let Some(row_canvas) = row_canvas.as_mut() {
|
||||||
|
if offset_y >= self.row_y && offset_y < self.row_y + row_canvas.height() {
|
||||||
|
if !output(
|
||||||
|
Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)),
|
||||||
|
row_canvas.view(),
|
||||||
|
) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// Align to the next MCU row
|
||||||
|
offset_y += row_canvas.height() - offset_y % row_canvas.height();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create a new row for cahing decoded JPEG data
|
||||||
|
// Now there's nobody else holding any reference to canvas_buff so
|
||||||
|
// we can get a mutable reference and pass it to a new instance
|
||||||
|
// of Rgb565Canvas
|
||||||
|
let canvas_buff = unsafe { &mut *self.row_buff.get() };
|
||||||
|
// Prepare canvas as a cache for a row of decoded JPEG MCUs
|
||||||
|
let decoder = unwrap!(self.decoder.as_ref()); // shoud never fail
|
||||||
|
let divisor = 1 << self.scale;
|
||||||
|
row_canvas = Some(unwrap!(
|
||||||
|
Rgb565Canvas::new(
|
||||||
|
Offset::new(decoder.width() / divisor, decoder.mcu_height() / divisor),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
canvas_buff
|
||||||
|
),
|
||||||
|
"Buffer too small"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.decompress_mcu(
|
||||||
|
image,
|
||||||
|
scale,
|
||||||
|
Point::new(0, offset_y),
|
||||||
|
&mut |mcu_r, mcu_bitmap| {
|
||||||
|
// Get canvas for MCU caching
|
||||||
|
let row_canvas = unwrap!(row_canvas.as_mut()); // should never fail
|
||||||
|
|
||||||
|
// Calculate coordinates in the row canvas
|
||||||
|
let dst_r = Rect {
|
||||||
|
y0: 0,
|
||||||
|
y1: mcu_r.height(),
|
||||||
|
..mcu_r
|
||||||
|
};
|
||||||
|
// Draw a MCU
|
||||||
|
row_canvas.draw_bitmap(dst_r, mcu_bitmap);
|
||||||
|
|
||||||
|
if mcu_r.x1 < row_canvas.size().x {
|
||||||
|
// We are not done with the row yet
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// We have a complete row, let's pass it to the callee
|
||||||
|
row_y = mcu_r.y0;
|
||||||
|
output(
|
||||||
|
Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)),
|
||||||
|
row_canvas.view(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Store the recently decoded row for future use
|
||||||
|
self.row_y = row_y;
|
||||||
|
self.row_canvas = row_canvas;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_bump_size() -> usize {
|
||||||
|
core::mem::size_of::<UnsafeCell<[u8; JPEG_SCRATCHPAD_SIZE]>>()
|
||||||
|
+ core::mem::size_of::<UnsafeCell<[u8; JPEG_BUFF_SIZE]>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BinaryDataReader<'a> {
|
||||||
|
data: BinaryData<'a>,
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BinaryDataReader<'a> {
|
||||||
|
fn new(data: BinaryData<'a>) -> Self {
|
||||||
|
Self { data, offset: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> trezor_tjpgdec::JpegInput for BinaryDataReader<'a> {
|
||||||
|
fn read(&mut self, buf: Option<&mut [u8]>, n_data: usize) -> usize {
|
||||||
|
let bytes_read = if let Some(buf) = buf {
|
||||||
|
self.data.read(self.offset, &mut buf[..n_data])
|
||||||
|
} else {
|
||||||
|
n_data.min(self.data.len() - self.offset)
|
||||||
|
};
|
||||||
|
self.offset += bytes_read;
|
||||||
|
|
||||||
|
bytes_read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JpegFnOutput<F>
|
||||||
|
where
|
||||||
|
F: FnMut(Rect, BitmapView) -> bool,
|
||||||
|
{
|
||||||
|
output: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> JpegFnOutput<F>
|
||||||
|
where
|
||||||
|
F: FnMut(Rect, BitmapView) -> bool,
|
||||||
|
{
|
||||||
|
pub fn new(output: F) -> Self {
|
||||||
|
Self { output }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> trezor_tjpgdec::JpegOutput for JpegFnOutput<F>
|
||||||
|
where
|
||||||
|
F: FnMut(Rect, BitmapView) -> bool,
|
||||||
|
{
|
||||||
|
fn write(
|
||||||
|
&mut self,
|
||||||
|
_jd: &tjpgd::JDEC,
|
||||||
|
rect_origin: (u32, u32),
|
||||||
|
rect_size: (u32, u32),
|
||||||
|
pixels: &[u16],
|
||||||
|
) -> bool {
|
||||||
|
// MCU coordinates in source image
|
||||||
|
let mcu_r = Rect::from_top_left_and_size(
|
||||||
|
Point::new(rect_origin.0 as i16, rect_origin.1 as i16),
|
||||||
|
Offset::new(rect_size.0 as i16, rect_size.1 as i16),
|
||||||
|
);
|
||||||
|
|
||||||
|
// SAFETY: aligning from [u16] -> [u8]
|
||||||
|
let (_, pixels, _) = unsafe { pixels.align_to() };
|
||||||
|
|
||||||
|
// Create readonly bitmap
|
||||||
|
let mcu_bitmap = unwrap!(Bitmap::new(
|
||||||
|
BitmapFormat::RGB565,
|
||||||
|
None,
|
||||||
|
mcu_r.size(),
|
||||||
|
None,
|
||||||
|
pixels,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Return true to continue decompression
|
||||||
|
(self.output)(mcu_r, BitmapView::new(&mcu_bitmap))
|
||||||
|
}
|
||||||
|
}
|
7
core/embed/rust/src/ui/shape/cache/mod.rs
vendored
Normal file
7
core/embed/rust/src/ui/shape/cache/mod.rs
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pub mod blur_cache;
|
||||||
|
pub mod drawing_cache;
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
pub mod jpeg_cache;
|
||||||
|
|
||||||
|
pub mod zlib_cache;
|
188
core/embed/rust/src/ui/shape/cache/zlib_cache.rs
vendored
Normal file
188
core/embed/rust/src/ui/shape/cache/zlib_cache.rs
vendored
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use crate::{
|
||||||
|
io::BinaryData,
|
||||||
|
trezorhal::uzlib::{ZlibInflate, UZLIB_WINDOW_SIZE},
|
||||||
|
ui::display::image::ToifInfo,
|
||||||
|
};
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
|
||||||
|
|
||||||
|
struct ZlibCacheSlot<'a> {
|
||||||
|
/// Decompression context for the current zdata.
|
||||||
|
/// If `None`, the slot is free to be used.
|
||||||
|
dc: Option<ZlibInflate<'a>>,
|
||||||
|
/// Reference to compressed data
|
||||||
|
image: Option<BinaryData<'a>>,
|
||||||
|
/// Current offset in decompressed data
|
||||||
|
output_offset: usize,
|
||||||
|
/// Window used by current decompression context.
|
||||||
|
/// (It's used just by own dc and nobody else.)
|
||||||
|
window: &'a UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ZlibCacheSlot<'a> {
|
||||||
|
fn new<T>(bump: &'a T) -> Option<Self>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'a>,
|
||||||
|
{
|
||||||
|
let window = bump
|
||||||
|
.alloc_t()?
|
||||||
|
.uninit
|
||||||
|
.init(UnsafeCell::new([0; UZLIB_WINDOW_SIZE]));
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
dc: None,
|
||||||
|
image: None,
|
||||||
|
output_offset: 0,
|
||||||
|
window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calling with None makes the slot free
|
||||||
|
fn reset(&mut self, image: Option<BinaryData<'a>>) {
|
||||||
|
// Drop the existing decompression context holding
|
||||||
|
// a mutable reference to window buffer
|
||||||
|
self.dc = None;
|
||||||
|
|
||||||
|
if let Some(image) = image {
|
||||||
|
// Now there's nobody else holding any reference to our window
|
||||||
|
// so we can get mutable reference and pass it to a new
|
||||||
|
// instance of the decompression context
|
||||||
|
let window = unsafe { &mut *self.window.get() };
|
||||||
|
|
||||||
|
self.dc = Some(ZlibInflate::new(image, ToifInfo::HEADER_LENGTH, window));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.output_offset = 0;
|
||||||
|
self.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uncompress(&mut self, dest_buf: &mut [u8]) -> Result<bool, ()> {
|
||||||
|
if let Some(dc) = self.dc.as_mut() {
|
||||||
|
match dc.read(dest_buf) {
|
||||||
|
Ok(done) => {
|
||||||
|
if done {
|
||||||
|
self.reset(None);
|
||||||
|
} else {
|
||||||
|
self.output_offset += dest_buf.len();
|
||||||
|
}
|
||||||
|
Ok(done)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip(&mut self, nbytes: usize) -> Result<bool, ()> {
|
||||||
|
if let Some(dc) = self.dc.as_mut() {
|
||||||
|
match dc.skip(nbytes) {
|
||||||
|
Ok(done) => {
|
||||||
|
if done {
|
||||||
|
self.reset(None);
|
||||||
|
} else {
|
||||||
|
self.output_offset += nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(done)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_for(&self, image: BinaryData<'a>, offset: usize) -> bool {
|
||||||
|
match self.image {
|
||||||
|
Some(current) => current == image && self.output_offset == offset,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ZlibCache<'a> {
|
||||||
|
slots: FixedVec<'a, ZlibCacheSlot<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ZlibCache<'a> {
|
||||||
|
pub fn new<T>(bump: &'a T, slot_count: usize) -> Option<Self>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'a>,
|
||||||
|
{
|
||||||
|
let mut cache = Self {
|
||||||
|
slots: bump.fixed_vec(slot_count)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
for _ in 0..cache.slots.capacity() {
|
||||||
|
cache.slots.push(ZlibCacheSlot::new(bump)?).ok()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_slot_for_reuse(&self) -> Result<usize, ()> {
|
||||||
|
if self.slots.capacity() > 0 {
|
||||||
|
// Try to find a free slot. If there's no free slot,
|
||||||
|
// select the one that performed the least amount of work
|
||||||
|
// based on the offset in the uncompressed data.
|
||||||
|
let mut selected = 0;
|
||||||
|
for (i, slot) in self.slots.iter().enumerate() {
|
||||||
|
if slot.dc.is_none() {
|
||||||
|
selected = i;
|
||||||
|
break;
|
||||||
|
} else if slot.output_offset < self.slots[selected].output_offset {
|
||||||
|
selected = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(selected)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uncompress(
|
||||||
|
&mut self,
|
||||||
|
image: BinaryData<'a>,
|
||||||
|
offset: usize,
|
||||||
|
dest_buf: &mut [u8],
|
||||||
|
) -> Result<bool, ()> {
|
||||||
|
let slot = self
|
||||||
|
.slots
|
||||||
|
.iter_mut()
|
||||||
|
.find(|slot| slot.is_for(image, offset));
|
||||||
|
|
||||||
|
let slot = match slot {
|
||||||
|
Some(slot) => slot,
|
||||||
|
None => {
|
||||||
|
let selected = self.select_slot_for_reuse()?;
|
||||||
|
let slot = &mut self.slots[selected];
|
||||||
|
slot.reset(Some(image));
|
||||||
|
slot.skip(offset)?;
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
slot.uncompress(dest_buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uncompress_toif(
|
||||||
|
&mut self,
|
||||||
|
image: BinaryData<'a>,
|
||||||
|
from_row: i16,
|
||||||
|
dest_buf: &mut [u8],
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
// TODO: optimize this
|
||||||
|
let info = ToifInfo::parse(image).ok_or(())?;
|
||||||
|
let from_offset = info.stride() * from_row as usize;
|
||||||
|
self.uncompress(image, from_offset, dest_buf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_bump_size(slot_count: usize) -> usize {
|
||||||
|
(core::mem::size_of::<ZlibCacheSlot>()
|
||||||
|
+ core::mem::size_of::<UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>>()
|
||||||
|
+ 16)
|
||||||
|
* slot_count
|
||||||
|
}
|
||||||
|
}
|
828
core/embed/rust/src/ui/shape/canvas/common.rs
Normal file
828
core/embed/rust/src/ui/shape/canvas/common.rs
Normal file
@ -0,0 +1,828 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::super::{
|
||||||
|
utils::{circle_points, line_points, sin_i16, PI4},
|
||||||
|
BitmapView, Viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
use crate::ui::shape::DrawingCache;
|
||||||
|
|
||||||
|
pub trait BasicCanvas {
|
||||||
|
/// Returns dimensions of the canvas in pixels.
|
||||||
|
fn size(&self) -> Offset;
|
||||||
|
|
||||||
|
/// Returns the dimensions of the canvas as a rectangle with
|
||||||
|
/// the top-left at (0,0).
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
Rect::from_size(self.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the width of the canvas in pixels.
|
||||||
|
fn width(&self) -> i16 {
|
||||||
|
self.size().x
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the height of the canvas in pixels.
|
||||||
|
fn height(&self) -> i16 {
|
||||||
|
self.size().y
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current drawing viewport previously set by `set_viewport()`
|
||||||
|
/// function.
|
||||||
|
fn viewport(&self) -> Viewport;
|
||||||
|
|
||||||
|
/// Sets the active viewport valid for all subsequent drawing operations.
|
||||||
|
fn set_viewport(&mut self, vp: Viewport);
|
||||||
|
|
||||||
|
/// Sets the new viewport that's intersection of the
|
||||||
|
/// current viewport and the `window` rectangle relative
|
||||||
|
/// to the current viewport. The viewport's origin is
|
||||||
|
/// set to the top-left corener of the `window`.
|
||||||
|
fn set_window(&mut self, window: Rect) -> Viewport {
|
||||||
|
let viewport = self.viewport();
|
||||||
|
self.set_viewport(viewport.relative_window(window));
|
||||||
|
viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the new viewport that's intersection of the
|
||||||
|
/// current viewport and the `clip` rectangle relative
|
||||||
|
/// to the current viewport. The viewport's origin is
|
||||||
|
/// not changed.
|
||||||
|
fn set_clip(&mut self, clip: Rect) -> Viewport {
|
||||||
|
let viewport = self.viewport();
|
||||||
|
self.set_viewport(viewport.relative_clip(clip));
|
||||||
|
viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws a filled rectangle with the specified color.
|
||||||
|
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8);
|
||||||
|
|
||||||
|
/// Fills the canvas background with the specified color.
|
||||||
|
fn fill_background(&mut self, color: Color) {
|
||||||
|
let vp = self.viewport();
|
||||||
|
self.fill_rect(vp.clip.translate(-vp.origin), color, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws a bitmap into to the rectangle.
|
||||||
|
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Canvas: BasicCanvas {
|
||||||
|
/// Returns a non-mutable view of the underlying bitmap.
|
||||||
|
fn view(&self) -> BitmapView;
|
||||||
|
|
||||||
|
/// Draw a pixel at specified coordinates.
|
||||||
|
fn draw_pixel(&mut self, pt: Point, color: Color);
|
||||||
|
|
||||||
|
/// Draws a single pixel and blends its color with the background.
|
||||||
|
///
|
||||||
|
/// - If alpha == 255, the (foreground) pixel color is used.
|
||||||
|
/// - If 0 < alpha << 255, pixel and backround colors are blended.
|
||||||
|
/// - If alpha == 0, the background color is used.
|
||||||
|
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8);
|
||||||
|
|
||||||
|
/// Blends a bitmap with the canvas background
|
||||||
|
fn blend_bitmap(&mut self, r: Rect, src: BitmapView);
|
||||||
|
|
||||||
|
/// Applies a blur effect to the specified rectangle.
|
||||||
|
///
|
||||||
|
/// The blur effect works properly only when the rectangle is not clipped,
|
||||||
|
/// which is a strong constraint that's hard to be met. The function uses a
|
||||||
|
/// simple box filter, where the 'radius' argument represents the length
|
||||||
|
/// of the sides of this filter.
|
||||||
|
///
|
||||||
|
/// It's important to be aware that strong artifacts may appear on images
|
||||||
|
/// with horizontal/vertical lines.
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache);
|
||||||
|
|
||||||
|
/// Draws an outline of a rectangle with rounded corners.
|
||||||
|
fn draw_round_rect(&mut self, r: Rect, radius: i16, color: Color) {
|
||||||
|
let split = unwrap!(circle_points(radius).last()).v;
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y1: r.y0 + radius - split + 1,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
||||||
|
if p.v == radius && p.last {
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
|
||||||
|
} else {
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y0 + radius - split + 1,
|
||||||
|
y1: r.y0 + radius + 1,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fill_rect(
|
||||||
|
Rect {
|
||||||
|
x0: r.x0,
|
||||||
|
y0: r.y0 + radius + 1,
|
||||||
|
x1: r.x0 + 1,
|
||||||
|
y1: r.y1 - radius - 1,
|
||||||
|
},
|
||||||
|
color,
|
||||||
|
255,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.fill_rect(
|
||||||
|
Rect {
|
||||||
|
x0: r.x1 - 1,
|
||||||
|
y0: r.y0 + radius + 1,
|
||||||
|
x1: r.x1,
|
||||||
|
y1: r.y1 - radius - 1,
|
||||||
|
},
|
||||||
|
color,
|
||||||
|
255,
|
||||||
|
);
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y1 - radius - 1,
|
||||||
|
y1: r.y1 - radius - 1 + split,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y1 - radius - 1 + split,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
||||||
|
|
||||||
|
if p.v == radius && p.last {
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
|
||||||
|
} else {
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws filled rectangle with rounded corners.
|
||||||
|
#[cfg(not(feature = "ui_antialiasing"))]
|
||||||
|
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
|
||||||
|
let split = unwrap!(circle_points(radius).last()).v;
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y1: r.y0 + radius - split + 1,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
if p.last {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y0 + radius - split + 1,
|
||||||
|
y1: r.y0 + radius + 1,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fill_rect(
|
||||||
|
Rect {
|
||||||
|
x0: r.x0,
|
||||||
|
y0: r.y0 + radius + 1,
|
||||||
|
x1: r.x1,
|
||||||
|
y1: r.y1 - radius - 1,
|
||||||
|
},
|
||||||
|
color,
|
||||||
|
alpha,
|
||||||
|
);
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y1 - radius - 1,
|
||||||
|
y1: r.y1 - radius - 1 + split,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y1 - radius - 1 + split,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
if p.last {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws filled rectangle with antialiased rounded corners.
|
||||||
|
#[cfg(feature = "ui_antialiasing")]
|
||||||
|
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
|
||||||
|
let split = unwrap!(circle_points(radius).last()).v;
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y1: r.y0 + radius - split + 1,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
|
||||||
|
if p.first {
|
||||||
|
let inner = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(inner, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y0 + radius - split + 1,
|
||||||
|
y1: r.y0 + radius + 1,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
|
||||||
|
let inner = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(inner, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fill_rect(
|
||||||
|
Rect {
|
||||||
|
x0: r.x0,
|
||||||
|
y0: r.y0 + radius + 1,
|
||||||
|
x1: r.x1,
|
||||||
|
y1: r.y1 - radius - 1,
|
||||||
|
},
|
||||||
|
color,
|
||||||
|
alpha,
|
||||||
|
);
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y1 - radius - 1,
|
||||||
|
y1: r.y1 - radius - 1 + split,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
|
||||||
|
let b = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(b, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = Rect {
|
||||||
|
y0: r.y1 - radius - 1 + split,
|
||||||
|
..r
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.viewport().contains(b) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
|
||||||
|
if p.first {
|
||||||
|
let b = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(b, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws circle with the specified center and the radius.
|
||||||
|
#[cfg(not(feature = "ui_antialiasing"))]
|
||||||
|
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
|
||||||
|
let split = unwrap!(circle_points(radius).last()).v;
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y - radius),
|
||||||
|
Point::new(center.x + radius + 1, center.y - split + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
||||||
|
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y - split),
|
||||||
|
Point::new(center.x + radius + 1, center.y + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
||||||
|
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y + 1),
|
||||||
|
Point::new(center.x + radius + 1, center.y + split + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
||||||
|
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y + split),
|
||||||
|
Point::new(center.x + radius + 1, center.y + radius + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
||||||
|
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
||||||
|
self.draw_pixel(pt_l, color);
|
||||||
|
self.draw_pixel(pt_r, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws filled circle with the specified center and the radius.
|
||||||
|
#[cfg(not(feature = "ui_antialiasing"))]
|
||||||
|
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
|
||||||
|
let split = unwrap!(circle_points(radius).last()).v;
|
||||||
|
let alpha = 255;
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y - radius),
|
||||||
|
Point::new(center.x + radius + 1, center.y - split + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
if p.last {
|
||||||
|
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
||||||
|
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y - split),
|
||||||
|
Point::new(center.x + radius + 1, center.y + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
||||||
|
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y + 1),
|
||||||
|
Point::new(center.x + radius + 1, center.y + split + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
||||||
|
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y + split),
|
||||||
|
Point::new(center.x + radius + 1, center.y + radius + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
if p.last {
|
||||||
|
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
||||||
|
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
||||||
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws antialiased filled circle with the specified center and the
|
||||||
|
/// radius.
|
||||||
|
#[cfg(feature = "ui_antialiasing")]
|
||||||
|
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
|
||||||
|
let split = unwrap!(circle_points(radius).last()).v;
|
||||||
|
|
||||||
|
let alpha = 255;
|
||||||
|
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y - radius),
|
||||||
|
Point::new(center.x + radius + 1, center.y - split + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
||||||
|
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
if pt_l != pt_r {
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.first {
|
||||||
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(r, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y - split),
|
||||||
|
Point::new(center.x + radius + 1, center.y + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
||||||
|
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
|
||||||
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(r, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y + 1),
|
||||||
|
Point::new(center.x + radius + 1, center.y + split + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
||||||
|
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
||||||
|
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
|
||||||
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(r, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect::new(
|
||||||
|
Point::new(center.x - radius, center.y + split),
|
||||||
|
Point::new(center.x + radius + 1, center.y + radius + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.viewport().contains(r) {
|
||||||
|
for p in circle_points(radius) {
|
||||||
|
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
||||||
|
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
||||||
|
if pt_l != pt_r {
|
||||||
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||||
|
}
|
||||||
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||||
|
|
||||||
|
if p.first {
|
||||||
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||||
|
self.fill_rect(r, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills circle sector with a specified color.
|
||||||
|
fn fill_sector(
|
||||||
|
&mut self,
|
||||||
|
center: Point,
|
||||||
|
radius: i16,
|
||||||
|
mut start: i16,
|
||||||
|
mut end: i16,
|
||||||
|
color: Color,
|
||||||
|
) {
|
||||||
|
start = (PI4 * 8 + start % (PI4 * 8)) % (PI4 * 8);
|
||||||
|
end = (PI4 * 8 + end % (PI4 * 8)) % (PI4 * 8);
|
||||||
|
|
||||||
|
let alpha = 255;
|
||||||
|
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
||||||
|
|
||||||
|
if start != end {
|
||||||
|
// The algorithm fills everything except the middle point ;-)
|
||||||
|
self.draw_pixel(center, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
for octant in 0..8 {
|
||||||
|
let angle = octant * PI4;
|
||||||
|
|
||||||
|
// Function for calculation of 'u' coordinate inside the circle octant
|
||||||
|
// radius * sin(angle)
|
||||||
|
let sin = |angle: i16| -> i16 { sin_i16(angle, radius) };
|
||||||
|
|
||||||
|
// Calculate the octant's bounding rectangle
|
||||||
|
let p = Point::new(sin(PI4) + 1, -radius - 1).rot(octant);
|
||||||
|
let r = Rect::new(center, p + center.into());
|
||||||
|
|
||||||
|
// Ensure that `x0`, `y0` represents the top-left corner and
|
||||||
|
// `x1`, `y1` represents the bottom-right corner.
|
||||||
|
let r = Rect {
|
||||||
|
x0: r.x0.min(r.x1),
|
||||||
|
y0: r.y0.min(r.y1),
|
||||||
|
x1: r.x0.max(r.x1),
|
||||||
|
y1: r.y0.max(r.y1),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip octant if not visible
|
||||||
|
if !self.viewport().contains(r) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function for filling a line between two endpoints with antialiasing.
|
||||||
|
// The function is special for each octant using 4 different axes of symmetry
|
||||||
|
let filler = &mut |p1: Option<Point>, p1_frac, p2: Point, p2_frac| {
|
||||||
|
let p2: Point = center + p2.rot(octant).into();
|
||||||
|
self.blend_pixel(p2, color, alpha_mul(p2_frac));
|
||||||
|
if let Some(p1) = p1 {
|
||||||
|
let p1: Point = center + p1.rot(octant).into();
|
||||||
|
let ofs = Point::new(-1, 0).rot(octant);
|
||||||
|
self.blend_pixel(p1 + ofs.into(), color, alpha_mul(p1_frac));
|
||||||
|
if ofs.x + ofs.y < 0 {
|
||||||
|
if ofs.x != 0 {
|
||||||
|
self.fill_rect(Rect::new(p1, p2.under()), color, alpha);
|
||||||
|
} else {
|
||||||
|
self.fill_rect(Rect::new(p1, p2.onright()), color, alpha);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let p1 = p1 + ofs.into();
|
||||||
|
let p2 = p2 + ofs.into();
|
||||||
|
if ofs.x != 0 {
|
||||||
|
self.fill_rect(Rect::new(p2, p1.under()), color, alpha);
|
||||||
|
} else {
|
||||||
|
self.fill_rect(Rect::new(p2, p1.onright()), color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let corr = if octant & 1 == 0 {
|
||||||
|
// The clockwise octant
|
||||||
|
|angle| angle
|
||||||
|
} else {
|
||||||
|
// The anticlockwise octant
|
||||||
|
|angle| PI4 - angle
|
||||||
|
};
|
||||||
|
|
||||||
|
if start <= end {
|
||||||
|
// Octant may contain 0 or 1 sector
|
||||||
|
if start < angle + PI4 && end > angle {
|
||||||
|
if start <= angle && end >= angle + PI4 {
|
||||||
|
// Fill all pixels in the octant
|
||||||
|
fill_octant(radius, 0, sin(PI4), filler);
|
||||||
|
} else {
|
||||||
|
// Partial fill
|
||||||
|
let u1 = if start <= angle {
|
||||||
|
sin(corr(0))
|
||||||
|
} else {
|
||||||
|
sin(corr(start - angle))
|
||||||
|
};
|
||||||
|
let u2 = if end <= angle + PI4 {
|
||||||
|
sin(corr(end - angle))
|
||||||
|
} else {
|
||||||
|
sin(corr(PI4))
|
||||||
|
};
|
||||||
|
|
||||||
|
fill_octant(radius, u1, u2, filler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Octant may contain 0, 1 or 2 sectors
|
||||||
|
if end >= angle + PI4 || start <= angle {
|
||||||
|
// Fill all pixels in the octant
|
||||||
|
fill_octant(radius, 0, sin(PI4), filler);
|
||||||
|
} else {
|
||||||
|
// Partial fill
|
||||||
|
if (end > angle) && (end < angle + PI4) {
|
||||||
|
// Fill up to `end`
|
||||||
|
fill_octant(radius, sin(corr(0)), sin(corr(end - angle)), filler);
|
||||||
|
}
|
||||||
|
if start < angle + PI4 {
|
||||||
|
// Fill all from `start`
|
||||||
|
fill_octant(radius, sin(corr(start - angle)), sin(corr(PI4)), filler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates endpoints of a single octant of a circle
|
||||||
|
///
|
||||||
|
/// Used internally by `Canvas::fill_sector()`.
|
||||||
|
fn fill_octant(
|
||||||
|
radius: i16,
|
||||||
|
mut u1: i16,
|
||||||
|
mut u2: i16,
|
||||||
|
fill: &mut impl FnMut(Option<Point>, u8, Point, u8),
|
||||||
|
) {
|
||||||
|
// Starting end ending points on
|
||||||
|
if u1 > u2 {
|
||||||
|
(u1, u2) = (u2, u1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut iter = circle_points(radius).skip(u1 as usize);
|
||||||
|
|
||||||
|
// Intersection of the p1 line and the circle
|
||||||
|
let p1_start = unwrap!(iter.next());
|
||||||
|
|
||||||
|
// Intersection of the p1 line and the circle
|
||||||
|
let mut p2_start = p1_start;
|
||||||
|
|
||||||
|
for p in iter.by_ref() {
|
||||||
|
if p.u > u2 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p2_start = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag if we draw section up to 45degs
|
||||||
|
let join_flag = iter.next().is_none();
|
||||||
|
|
||||||
|
// Process area between a p1 line and the circle
|
||||||
|
let mut p1_iter = line_points(p1_start.v, p1_start.u, 0);
|
||||||
|
let mut first = true;
|
||||||
|
let mut skip = 0;
|
||||||
|
|
||||||
|
for c in circle_points(radius)
|
||||||
|
.skip(p1_start.u as usize)
|
||||||
|
.take((p2_start.u - p1_start.u) as usize)
|
||||||
|
{
|
||||||
|
let p2_coord = Point::new(c.u, -c.v);
|
||||||
|
|
||||||
|
if c.first || first {
|
||||||
|
let p1 = unwrap!(p1_iter.next());
|
||||||
|
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
fill(Some(p1_coord), p1.frac, p2_coord, c.frac);
|
||||||
|
} else {
|
||||||
|
fill(None, 0, p2_coord, c.frac);
|
||||||
|
}
|
||||||
|
|
||||||
|
skip = if c.last { 0 } else { 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process area between a p1 and p2 lines
|
||||||
|
let p2_iter = line_points(p2_start.v, p2_start.u, 0).skip(skip);
|
||||||
|
let mut first = true;
|
||||||
|
for (p1, p2) in p1_iter.zip(p2_iter) {
|
||||||
|
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
|
||||||
|
let p2_coord = Point::new(p2_start.u - p2.v, -p2_start.v + p2.u);
|
||||||
|
let p2_frac = if first {
|
||||||
|
p2_start.frac
|
||||||
|
} else if join_flag {
|
||||||
|
255
|
||||||
|
} else {
|
||||||
|
255 - p2.frac
|
||||||
|
};
|
||||||
|
|
||||||
|
fill(Some(p1_coord), p1.frac, p2_coord, p2_frac);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private extension functions for the `Point` type not useful
|
||||||
|
// enough to be exposed in the public API.
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
/// Returns point on the left side of the current point.
|
||||||
|
fn onleft(self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x - 1,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns point on the right side of the current point.
|
||||||
|
fn onright(self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x + 1,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns point above the current point.
|
||||||
|
fn above(self) -> Self {
|
||||||
|
Self {
|
||||||
|
y: self.y - 1,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns point below the current point.
|
||||||
|
fn under(self) -> Self {
|
||||||
|
Self {
|
||||||
|
y: self.y + 1,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rotates the point around (0,0) by `octant` * 45 degrees.
|
||||||
|
fn rot(self, octant: i16) -> Self {
|
||||||
|
let mut result = self;
|
||||||
|
|
||||||
|
if (octant + 1) & 2 != 0 {
|
||||||
|
result = Point::new(-result.y, -result.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if octant & 4 != 0 {
|
||||||
|
result = Point::new(-result.x, result.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (octant + 2) & 4 != 0 {
|
||||||
|
result = Point::new(result.x, -result.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
11
core/embed/rust/src/ui/shape/canvas/mod.rs
Normal file
11
core/embed/rust/src/ui/shape/canvas/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
mod common;
|
||||||
|
mod mono8;
|
||||||
|
mod rgb565;
|
||||||
|
mod rgba8888;
|
||||||
|
mod viewport;
|
||||||
|
|
||||||
|
pub use common::{BasicCanvas, Canvas};
|
||||||
|
pub use mono8::Mono8Canvas;
|
||||||
|
pub use rgb565::Rgb565Canvas;
|
||||||
|
pub use rgba8888::Rgba8888Canvas;
|
||||||
|
pub use viewport::Viewport;
|
114
core/embed/rust/src/ui/shape/canvas/mono8.rs
Normal file
114
core/embed/rust/src/ui/shape/canvas/mono8.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
use crate::{
|
||||||
|
trezorhal::bitblt::{BitBltCopy, BitBltFill},
|
||||||
|
ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::{Bitmap, BitmapFormat, BitmapView},
|
||||||
|
BasicCanvas, Canvas, Viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
use super::super::DrawingCache;
|
||||||
|
|
||||||
|
/// A struct representing 8-bit monochromatic canvas
|
||||||
|
pub struct Mono8Canvas<'a> {
|
||||||
|
bitmap: Bitmap<'a>,
|
||||||
|
viewport: Viewport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Mono8Canvas<'a> {
|
||||||
|
/// Creates a new canvas with the specified size and 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.
|
||||||
|
pub fn new(
|
||||||
|
size: Offset,
|
||||||
|
stride: Option<usize>,
|
||||||
|
min_height: Option<i16>,
|
||||||
|
buff: &'a mut [u8],
|
||||||
|
) -> Option<Self> {
|
||||||
|
let bitmap = Bitmap::new_mut(BitmapFormat::MONO8, stride, size, min_height, buff)?;
|
||||||
|
let viewport = Viewport::from_size(bitmap.size());
|
||||||
|
Some(Self { bitmap, viewport })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 [u8]> {
|
||||||
|
self.bitmap.row_mut(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BasicCanvas for Mono8Canvas<'a> {
|
||||||
|
fn viewport(&self) -> Viewport {
|
||||||
|
self.viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_viewport(&mut self, viewport: Viewport) {
|
||||||
|
self.viewport = viewport.absolute_clip(self.bounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Offset {
|
||||||
|
self.bitmap.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltFill::new(r, self.viewport.clip, color, alpha) {
|
||||||
|
bitblt.mono8_fill(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltCopy::new(r, self.viewport.clip, &bitmap) {
|
||||||
|
bitblt.mono8_copy(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Canvas for Mono8Canvas<'a> {
|
||||||
|
fn view(&self) -> BitmapView {
|
||||||
|
BitmapView::new(&self.bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel(&mut self, pt: Point, color: Color) {
|
||||||
|
let pt = pt + self.viewport.origin;
|
||||||
|
if self.viewport.clip.contains(pt) {
|
||||||
|
if let Some(row) = self.row_mut(pt.y) {
|
||||||
|
row[pt.x as usize] = color.luminance() as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
|
||||||
|
let pt = pt + self.viewport.origin;
|
||||||
|
if self.viewport.clip.contains(pt) {
|
||||||
|
if let Some(row) = self.row_mut(pt.y) {
|
||||||
|
let pixel = &mut row[pt.x as usize];
|
||||||
|
let fg_color = color.luminance() as u16;
|
||||||
|
let bg_color = *pixel as u16;
|
||||||
|
*pixel = ((fg_color * alpha as u16 + bg_color * (255 - alpha) as u16) / 255) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltCopy::new(r, self.viewport.clip, &src) {
|
||||||
|
bitblt.mono8_blend(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
137
core/embed/rust/src/ui/shape/canvas/rgb565.rs
Normal file
137
core/embed/rust/src/ui/shape/canvas/rgb565.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
use crate::{
|
||||||
|
trezorhal::bitblt::{BitBltCopy, BitBltFill},
|
||||||
|
ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::{Bitmap, BitmapFormat, BitmapView},
|
||||||
|
BasicCanvas, Canvas, Viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
use super::super::DrawingCache;
|
||||||
|
|
||||||
|
/// A struct representing 16-bit (RGB565) color canvas
|
||||||
|
pub struct Rgb565Canvas<'a> {
|
||||||
|
bitmap: Bitmap<'a>,
|
||||||
|
viewport: Viewport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Rgb565Canvas<'a> {
|
||||||
|
/// Creates a new canvas with the specified size and 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.
|
||||||
|
pub fn new(
|
||||||
|
size: Offset,
|
||||||
|
stride: Option<usize>,
|
||||||
|
min_height: Option<i16>,
|
||||||
|
buff: &'a mut [u8],
|
||||||
|
) -> Option<Self> {
|
||||||
|
let bitmap = Bitmap::new_mut(BitmapFormat::RGB565, stride, size, min_height, buff)?;
|
||||||
|
let viewport = Viewport::from_size(bitmap.size());
|
||||||
|
Some(Self { bitmap, viewport })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 [u16]> {
|
||||||
|
self.bitmap.row_mut(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BasicCanvas for Rgb565Canvas<'a> {
|
||||||
|
fn size(&self) -> Offset {
|
||||||
|
self.bitmap.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn viewport(&self) -> Viewport {
|
||||||
|
self.viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_viewport(&mut self, viewport: Viewport) {
|
||||||
|
self.viewport = viewport.absolute_clip(self.bounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltFill::new(r, self.viewport.clip, color, alpha) {
|
||||||
|
bitblt.rgb565_fill(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltCopy::new(r, self.viewport.clip, &bitmap) {
|
||||||
|
bitblt.rgb565_copy(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Canvas for Rgb565Canvas<'a> {
|
||||||
|
fn view(&self) -> BitmapView {
|
||||||
|
BitmapView::new(&self.bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel(&mut self, pt: Point, color: Color) {
|
||||||
|
let pt = pt + self.viewport.origin;
|
||||||
|
if self.viewport.clip.contains(pt) {
|
||||||
|
if let Some(row) = self.row_mut(pt.y) {
|
||||||
|
row[pt.x as usize] = color.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
|
||||||
|
let pt = pt + self.viewport.origin;
|
||||||
|
if self.viewport.clip.contains(pt) {
|
||||||
|
if let Some(row) = self.row_mut(pt.y) {
|
||||||
|
let pixel = &mut row[pt.x as usize];
|
||||||
|
let bg_color: Color = (*pixel).into();
|
||||||
|
*pixel = bg_color.blend(color, alpha).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltCopy::new(r, self.viewport.clip, &src) {
|
||||||
|
bitblt.rgb565_blend(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache) {
|
||||||
|
let clip = r.translate(self.viewport.origin).clamp(self.viewport.clip);
|
||||||
|
|
||||||
|
let ofs = radius as i16;
|
||||||
|
|
||||||
|
if clip.width() > 2 * ofs - 1 && clip.height() > 2 * ofs - 1 {
|
||||||
|
let mut blur_cache = cache.blur();
|
||||||
|
let (blur, _) = unwrap!(
|
||||||
|
blur_cache.get(clip.size(), radius, None),
|
||||||
|
"Too small blur buffer"
|
||||||
|
);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(y) = blur.push_ready() {
|
||||||
|
let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic
|
||||||
|
blur.push(&row[clip.x0 as usize..clip.x1 as usize]);
|
||||||
|
}
|
||||||
|
if let Some(y) = blur.pop_ready() {
|
||||||
|
let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic
|
||||||
|
blur.pop(&mut row[clip.x0 as usize..clip.x1 as usize], None);
|
||||||
|
if y + 1 >= clip.height() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
core/embed/rust/src/ui/shape/canvas/rgba8888.rs
Normal file
128
core/embed/rust/src/ui/shape/canvas/rgba8888.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
use crate::{
|
||||||
|
trezorhal::bitblt::{BitBltCopy, BitBltFill},
|
||||||
|
ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::{Bitmap, BitmapFormat, BitmapView},
|
||||||
|
BasicCanvas, Canvas, Viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
use super::super::DrawingCache;
|
||||||
|
|
||||||
|
/// A struct representing 32-bit (RGBA8888) color canvas
|
||||||
|
pub struct Rgba8888Canvas<'a> {
|
||||||
|
bitmap: Bitmap<'a>,
|
||||||
|
viewport: Viewport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Rgba8888Canvas<'a> {
|
||||||
|
/// Creates a new canvas with the specified size and 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.
|
||||||
|
pub fn new(
|
||||||
|
size: Offset,
|
||||||
|
stride: Option<usize>,
|
||||||
|
min_height: Option<i16>,
|
||||||
|
buff: &'a mut [u8],
|
||||||
|
) -> Option<Self> {
|
||||||
|
let bitmap = Bitmap::new_mut(BitmapFormat::RGBA8888, stride, size, min_height, buff)?;
|
||||||
|
let viewport = Viewport::from_size(bitmap.size());
|
||||||
|
Some(Self { bitmap, viewport })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 [u32]> {
|
||||||
|
self.bitmap.row_mut(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BasicCanvas for Rgba8888Canvas<'a> {
|
||||||
|
fn size(&self) -> Offset {
|
||||||
|
self.bitmap.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn viewport(&self) -> Viewport {
|
||||||
|
self.viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_viewport(&mut self, viewport: Viewport) {
|
||||||
|
self.viewport = viewport.absolute_clip(self.bounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltFill::new(r, self.viewport.clip, color, alpha) {
|
||||||
|
bitblt.rgba8888_fill(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltCopy::new(r, self.viewport.clip, &bitmap) {
|
||||||
|
bitblt.rgba8888_copy(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Canvas for Rgba8888Canvas<'a> {
|
||||||
|
fn view(&self) -> BitmapView {
|
||||||
|
BitmapView::new(&self.bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel(&mut self, pt: Point, color: Color) {
|
||||||
|
let pt = pt + self.viewport.origin;
|
||||||
|
if self.viewport.clip.contains(pt) {
|
||||||
|
if let Some(row) = self.row_mut(pt.y) {
|
||||||
|
row[pt.x as usize] = color.to_u32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
|
||||||
|
let pt = pt + self.viewport.origin;
|
||||||
|
if self.viewport.clip.contains(pt) {
|
||||||
|
if let Some(row) = self.row_mut(pt.y) {
|
||||||
|
let bg = row[pt.x as usize];
|
||||||
|
|
||||||
|
let bg_r = ((bg & 0x00FF0000) >> 16) as u16;
|
||||||
|
let bg_g = ((bg & 0x0000FF00) >> 8) as u16;
|
||||||
|
let bg_b = (bg & 0x000000FF) as u16;
|
||||||
|
|
||||||
|
let fg_r = color.r() as u16;
|
||||||
|
let fg_g = color.g() as u16;
|
||||||
|
let fg_b = color.b() as u16;
|
||||||
|
|
||||||
|
let fg_mul = alpha as u16;
|
||||||
|
let bg_mul = (255 - alpha) as u16;
|
||||||
|
|
||||||
|
let r = ((fg_r * fg_mul + bg_r * bg_mul) / 255) as u32;
|
||||||
|
let g = ((fg_g * fg_mul + bg_g * bg_mul) / 255) as u32;
|
||||||
|
let b = ((fg_b * fg_mul + bg_b * bg_mul) / 255) as u32;
|
||||||
|
|
||||||
|
row[pt.x as usize] = (0xFF << 24) | (r << 16) | (g << 8) | b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltCopy::new(r, self.viewport.clip, &src) {
|
||||||
|
bitblt.rgba8888_blend(&mut self.bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
103
core/embed/rust/src/ui/shape/canvas/viewport.rs
Normal file
103
core/embed/rust/src/ui/shape/canvas/viewport.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use crate::ui::geometry::{Offset, Rect};
|
||||||
|
|
||||||
|
/// The Viewport concept is foundation for clipping and translating
|
||||||
|
/// during drawing on the general canvas.
|
||||||
|
///
|
||||||
|
/// The Viewport structure comprises a rectangle representing the
|
||||||
|
/// clipping area and a drawing origin (or offset), which is applied
|
||||||
|
/// to all coordinates passed to the drawing functions.
|
||||||
|
///
|
||||||
|
/// Two coordination systems exist - "absolute" and "relative."
|
||||||
|
///
|
||||||
|
/// In the "absolute" coordinate system, (0, 0) is at the left-top of
|
||||||
|
/// a referenced canvas (device or bitmap).
|
||||||
|
///
|
||||||
|
/// Relative coordinates are with respect to the viewport origin.
|
||||||
|
/// The relative coordinate (0, 0) is located at (viewport.origin.x,
|
||||||
|
/// viewport.origin.y).
|
||||||
|
///
|
||||||
|
/// Conversion between "absolute" and "relative" coordinates is straightforward:
|
||||||
|
///
|
||||||
|
/// pt_absolute = pt_relative.translate(viewport.origin)
|
||||||
|
///
|
||||||
|
/// pt_relative = pt_absolute.translate(-viewport.origin)
|
||||||
|
///
|
||||||
|
/// The Viewport's clipping area and origin are always in "absolute"
|
||||||
|
/// coordinates. Canvas objects utilize the viewport to translate "relative"
|
||||||
|
/// coordinates passed to drawing functions into "absolute" coordinates that
|
||||||
|
/// correspond to the target device or bitmap.
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Viewport {
|
||||||
|
/// Clipping rectangle relative to the canvas top-left corner
|
||||||
|
pub clip: Rect,
|
||||||
|
/// Offset applied to all coordinates before clipping
|
||||||
|
pub origin: Offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Viewport {
|
||||||
|
/// Creates a new viewport with specified clip rectangle and origin at
|
||||||
|
/// (0,0).
|
||||||
|
pub fn new(clip: Rect) -> Self {
|
||||||
|
Self {
|
||||||
|
clip,
|
||||||
|
origin: Offset::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new viewport with specified size and origin at (0,0).
|
||||||
|
pub fn from_size(size: Offset) -> Self {
|
||||||
|
Self {
|
||||||
|
clip: Rect::from_size(size),
|
||||||
|
origin: Offset::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the viewport intersects with the specified rectangle
|
||||||
|
/// given in relative coordinates.
|
||||||
|
pub fn contains(&self, r: Rect) -> bool {
|
||||||
|
!r.translate(self.origin).clamp(self.clip).is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate(self, offset: Offset) -> Self {
|
||||||
|
Self {
|
||||||
|
clip: self.clip.translate(offset),
|
||||||
|
origin: self.origin + offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new viewport with the new origin given in
|
||||||
|
/// absolute coordinates.
|
||||||
|
pub fn with_origin(self, origin: Offset) -> Self {
|
||||||
|
Self { origin, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a clip of the viewport containing only the specified rectangle
|
||||||
|
/// given in absolute coordinates. The origin of the new viewport
|
||||||
|
/// remains unchanged.
|
||||||
|
pub fn absolute_clip(self, r: Rect) -> Self {
|
||||||
|
Self {
|
||||||
|
clip: r.clamp(self.clip),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a clip of the viewport containing only the specified rectangle
|
||||||
|
/// given in relative coordinates. The origin of the new viewport
|
||||||
|
/// remains unchanged.
|
||||||
|
pub fn relative_clip(self, r: Rect) -> Self {
|
||||||
|
Self {
|
||||||
|
clip: r.translate(self.origin).clamp(self.clip),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a clip of the viewport containing only the specified rectangle
|
||||||
|
/// given in relative coordinates. The origin of the new viewport
|
||||||
|
/// is set to the top-left corner of the rectangle.
|
||||||
|
pub fn relative_window(&self, r: Rect) -> Self {
|
||||||
|
let clip = r.translate(self.origin).clamp(self.clip);
|
||||||
|
let origin = self.origin + (clip.top_left() - self.clip.top_left());
|
||||||
|
Self { clip, origin }
|
||||||
|
}
|
||||||
|
}
|
139
core/embed/rust/src/ui/shape/circle.rs
Normal file
139
core/embed/rust/src/ui/shape/circle.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Point, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
/// A shape for rendering various types of circles or circle sectors.
|
||||||
|
pub struct Circle {
|
||||||
|
center: Point,
|
||||||
|
radius: i16,
|
||||||
|
fg_color: Option<Color>,
|
||||||
|
bg_color: Option<Color>,
|
||||||
|
thickness: i16,
|
||||||
|
start_angle: Option<i16>,
|
||||||
|
end_angle: Option<i16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Circle {
|
||||||
|
pub fn new(center: Point, radius: i16) -> Self {
|
||||||
|
Self {
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
fg_color: None,
|
||||||
|
bg_color: None,
|
||||||
|
thickness: 1,
|
||||||
|
start_angle: None,
|
||||||
|
end_angle: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fg(self, fg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
fg_color: Some(fg_color),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_bg(self, bg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
bg_color: Some(bg_color),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_thickness(self, thickness: i16) -> Self {
|
||||||
|
Self { thickness, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_start_angle(self, from_angle: i16) -> Self {
|
||||||
|
Self {
|
||||||
|
start_angle: Some(from_angle),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_end_angle(self, to_angle: i16) -> Self {
|
||||||
|
Self {
|
||||||
|
end_angle: Some(to_angle),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||||
|
renderer.render_shape(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape<'_> for Circle {
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
let c = self.center;
|
||||||
|
let r = self.radius;
|
||||||
|
Rect::new(
|
||||||
|
Point::new(c.x - r, c.y - r),
|
||||||
|
Point::new(c.x + r + 1, c.y + r + 1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||||
|
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) {
|
||||||
|
// NOTE: drawing of circles without a background and with a thickness > 1
|
||||||
|
// is not supported. If we needed it, we would have to
|
||||||
|
// introduce RgbCanvas::draw_ring() function.
|
||||||
|
|
||||||
|
// TODO: panic! in unsupported scenarious
|
||||||
|
let th = match self.fg_color {
|
||||||
|
Some(_) => self.thickness,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.start_angle.is_none() && self.end_angle.is_none() {
|
||||||
|
if th == 1 {
|
||||||
|
if let Some(color) = self.bg_color {
|
||||||
|
canvas.fill_circle(self.center, self.radius, color);
|
||||||
|
}
|
||||||
|
if let Some(color) = self.fg_color {
|
||||||
|
#[cfg(not(feature = "ui_antialiasing"))]
|
||||||
|
canvas.draw_circle(self.center, self.radius, color);
|
||||||
|
#[cfg(feature = "ui_antialiasing")]
|
||||||
|
canvas.fill_circle(self.center, self.radius, color);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(color) = self.fg_color {
|
||||||
|
if th > 0 {
|
||||||
|
canvas.fill_circle(self.center, self.radius, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(color) = self.bg_color {
|
||||||
|
canvas.fill_circle(self.center, self.radius - th, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let start = self.start_angle.unwrap_or(0);
|
||||||
|
let end = self.end_angle.unwrap_or(360);
|
||||||
|
|
||||||
|
if let Some(color) = self.fg_color {
|
||||||
|
if th > 0 {
|
||||||
|
canvas.fill_sector(self.center, self.radius, start, end, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(color) = self.bg_color {
|
||||||
|
canvas.fill_sector(self.center, self.radius - th, start, end, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ShapeClone<'s> for Circle {
|
||||||
|
fn clone_at_bump<T>(self, bump: &'s T) -> Option<&'s mut dyn Shape<'s>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'s>,
|
||||||
|
{
|
||||||
|
let clone = bump.alloc_t()?;
|
||||||
|
Some(clone.uninit.init(Circle { ..self }))
|
||||||
|
}
|
||||||
|
}
|
11
core/embed/rust/src/ui/shape/display/fake_display.rs
Normal file
11
core/embed/rust/src/ui/shape/display/fake_display.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
shape::{DirectRenderer, Mono8Canvas, Viewport},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn render_on_display<'a, F>(_viewport: Option<Viewport>, _bg_color: Option<Color>, _func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>),
|
||||||
|
{
|
||||||
|
unimplemented!();
|
||||||
|
}
|
55
core/embed/rust/src/ui/shape/display/fb_mono8.rs
Normal file
55
core/embed/rust/src/ui/shape/display/fb_mono8.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::Offset,
|
||||||
|
shape::{BasicCanvas, DirectRenderer, DrawingCache, Mono8Canvas, Viewport},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::trezorhal::display;
|
||||||
|
|
||||||
|
use static_alloc::Bump;
|
||||||
|
|
||||||
|
/// Creates the `Renderer` object for drawing on a display and invokes a
|
||||||
|
/// user-defined function that takes a single argument `target`. The user's
|
||||||
|
/// function can utilize the `target` for drawing on the display.
|
||||||
|
///
|
||||||
|
/// `clip` specifies a rectangle area that the user will draw to.
|
||||||
|
/// If no clip is specified, the entire display area is used.
|
||||||
|
///
|
||||||
|
/// `bg_color` specifies a background color with which the clip is filled before
|
||||||
|
/// the drawing starts. If the background color is None, the background
|
||||||
|
/// is undefined, and the user has to fill it themselves.
|
||||||
|
pub fn render_on_display<'a, F>(viewport: Option<Viewport>, bg_color: Option<Color>, func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>),
|
||||||
|
{
|
||||||
|
const BUMP_SIZE: usize = DrawingCache::get_bump_a_size() + DrawingCache::get_bump_b_size();
|
||||||
|
|
||||||
|
static mut BUMP: Bump<[u8; BUMP_SIZE]> = Bump::uninit();
|
||||||
|
|
||||||
|
let bump = unsafe { &mut *core::ptr::addr_of_mut!(BUMP) };
|
||||||
|
{
|
||||||
|
let width = display::DISPLAY_RESX as i16;
|
||||||
|
let height = display::DISPLAY_RESY as i16;
|
||||||
|
|
||||||
|
bump.reset();
|
||||||
|
|
||||||
|
let cache = DrawingCache::new(bump, bump);
|
||||||
|
|
||||||
|
let (fb, fb_stride) = display::get_frame_buffer();
|
||||||
|
|
||||||
|
let mut canvas = unwrap!(Mono8Canvas::new(
|
||||||
|
Offset::new(width, height),
|
||||||
|
Some(fb_stride),
|
||||||
|
None,
|
||||||
|
fb
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Some(viewport) = viewport {
|
||||||
|
canvas.set_viewport(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
|
||||||
|
|
||||||
|
func(&mut target);
|
||||||
|
}
|
||||||
|
}
|
62
core/embed/rust/src/ui/shape/display/fb_rgb565.rs
Normal file
62
core/embed/rust/src/ui/shape/display/fb_rgb565.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::Offset,
|
||||||
|
shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::trezorhal::display;
|
||||||
|
|
||||||
|
use static_alloc::Bump;
|
||||||
|
|
||||||
|
/// Creates the `Renderer` object for drawing on a display and invokes a
|
||||||
|
/// user-defined function that takes a single argument `target`. The user's
|
||||||
|
/// function can utilize the `target` for drawing on the display.
|
||||||
|
///
|
||||||
|
/// `clip` specifies a rectangle area that the user will draw to.
|
||||||
|
/// If no clip is specified, the entire display area is used.
|
||||||
|
///
|
||||||
|
/// `bg_color` specifies a background color with which the clip is filled before
|
||||||
|
/// the drawing starts. If the background color is None, the background
|
||||||
|
/// is undefined, and the user has to fill it themselves.
|
||||||
|
pub fn render_on_display<'a, F>(viewport: Option<Viewport>, bg_color: Option<Color>, func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut DirectRenderer<'_, 'a, Rgb565Canvas<'a>>),
|
||||||
|
{
|
||||||
|
const BUMP_A_SIZE: usize = DrawingCache::get_bump_a_size();
|
||||||
|
const BUMP_B_SIZE: usize = DrawingCache::get_bump_b_size();
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "macos"), link_section = ".no_dma_buffers")]
|
||||||
|
static mut BUMP_A: Bump<[u8; BUMP_A_SIZE]> = Bump::uninit();
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "macos"), link_section = ".buf")]
|
||||||
|
static mut BUMP_B: Bump<[u8; BUMP_B_SIZE]> = Bump::uninit();
|
||||||
|
|
||||||
|
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
|
||||||
|
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
|
||||||
|
{
|
||||||
|
let width = display::DISPLAY_RESX as i16;
|
||||||
|
let height = display::DISPLAY_RESY as i16;
|
||||||
|
|
||||||
|
bump_a.reset();
|
||||||
|
bump_b.reset();
|
||||||
|
|
||||||
|
let cache = DrawingCache::new(bump_a, bump_b);
|
||||||
|
|
||||||
|
let (fb, fb_stride) = display::get_frame_buffer();
|
||||||
|
|
||||||
|
let mut canvas = unwrap!(Rgb565Canvas::new(
|
||||||
|
Offset::new(width, height),
|
||||||
|
Some(fb_stride),
|
||||||
|
None,
|
||||||
|
fb
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Some(viewport) = viewport {
|
||||||
|
canvas.set_viewport(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
|
||||||
|
|
||||||
|
func(&mut target);
|
||||||
|
}
|
||||||
|
}
|
62
core/embed/rust/src/ui/shape/display/fb_rgba8888.rs
Normal file
62
core/embed/rust/src/ui/shape/display/fb_rgba8888.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::Offset,
|
||||||
|
shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport},
|
||||||
|
};
|
||||||
|
|
||||||
|
use static_alloc::Bump;
|
||||||
|
|
||||||
|
use crate::trezorhal::display;
|
||||||
|
|
||||||
|
/// Creates the `Renderer` object for drawing on a display and invokes a
|
||||||
|
/// user-defined function that takes a single argument `target`. The user's
|
||||||
|
/// function can utilize the `target` for drawing on the display.
|
||||||
|
///
|
||||||
|
/// `clip` specifies a rectangle area that the user will draw to.
|
||||||
|
/// If no clip is specified, the entire display area is used.
|
||||||
|
///
|
||||||
|
/// `bg_color` specifies a background color with which the clip is filled before
|
||||||
|
/// the drawing starts. If the background color is None, the background
|
||||||
|
/// is undefined, and the user has to fill it themselves.
|
||||||
|
pub fn render_on_display<'a, F>(viewport: Option<Viewport>, bg_color: Option<Color>, func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut DirectRenderer<'_, 'a, Rgba8888Canvas<'a>>),
|
||||||
|
{
|
||||||
|
const BUMP_A_SIZE: usize = DrawingCache::get_bump_a_size();
|
||||||
|
const BUMP_B_SIZE: usize = DrawingCache::get_bump_b_size();
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "macos"), link_section = ".no_dma_buffers")]
|
||||||
|
static mut BUMP_A: Bump<[u8; BUMP_A_SIZE]> = Bump::uninit();
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "macos"), link_section = ".buf")]
|
||||||
|
static mut BUMP_B: Bump<[u8; BUMP_B_SIZE]> = Bump::uninit();
|
||||||
|
|
||||||
|
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
|
||||||
|
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
|
||||||
|
{
|
||||||
|
let width = display::DISPLAY_RESX as i16;
|
||||||
|
let height = display::DISPLAY_RESY as i16;
|
||||||
|
|
||||||
|
bump_a.reset();
|
||||||
|
bump_b.reset();
|
||||||
|
|
||||||
|
let cache = DrawingCache::new(bump_a, bump_b);
|
||||||
|
|
||||||
|
let (fb, fb_stride) = display::get_frame_buffer();
|
||||||
|
|
||||||
|
let mut canvas = unwrap!(Rgba8888Canvas::new(
|
||||||
|
Offset::new(width, height),
|
||||||
|
Some(fb_stride),
|
||||||
|
None,
|
||||||
|
fb
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Some(viewport) = viewport {
|
||||||
|
canvas.set_viewport(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
|
||||||
|
|
||||||
|
func(&mut target);
|
||||||
|
}
|
||||||
|
}
|
58
core/embed/rust/src/ui/shape/display/memory.md
Normal file
58
core/embed/rust/src/ui/shape/display/memory.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
## Memory usage comparison
|
||||||
|
|
||||||
|
## Legacy solution
|
||||||
|
|
||||||
|
**Memory with DMA access**
|
||||||
|
|
||||||
|
```
|
||||||
|
buffer_line_16bpp @.buf 1440
|
||||||
|
buffer_line_4bpp @.buf 360
|
||||||
|
buffer_text @.buf 4320
|
||||||
|
-------------------------------------------------
|
||||||
|
6120
|
||||||
|
```
|
||||||
|
|
||||||
|
**Memory without DMA access**
|
||||||
|
|
||||||
|
```
|
||||||
|
buffer_jpeg @.no_dma 7680
|
||||||
|
buffer_jpeg_work @.no_dma 10500
|
||||||
|
buffer_blurring @.no_dma 14400
|
||||||
|
buffer_blurring_totals @.no_dma 1440
|
||||||
|
zlib context+window @.stack 2308
|
||||||
|
-------------------------------------------------
|
||||||
|
36328
|
||||||
|
```
|
||||||
|
|
||||||
|
## New drawing library
|
||||||
|
|
||||||
|
The memory usage is configurable, so the two options are considered.\
|
||||||
|
|
||||||
|
MIN variant is slower, but consumes less memory. OPT variant should
|
||||||
|
be sufficient for all purposes.
|
||||||
|
|
||||||
|
|
||||||
|
**Memory with DMA access**
|
||||||
|
|
||||||
|
```
|
||||||
|
MIN OPT
|
||||||
|
ProgressiveRenderer.slice @.buf 480 7680
|
||||||
|
ProgressiveRenderer.scratch @.buf 480 2048
|
||||||
|
---------------------------------------------------------
|
||||||
|
960 9728
|
||||||
|
```
|
||||||
|
|
||||||
|
**Memory without DMA access**
|
||||||
|
|
||||||
|
```
|
||||||
|
ProgressiveRenderer.list @.stack 512 2048
|
||||||
|
zlib decompression context @.no_dma 2308 6924
|
||||||
|
jpeg decompressor @.no_dma 10500 10500
|
||||||
|
partial jpeg image @.no_dma 7680 7680
|
||||||
|
blurring window/totals @.no_dma 7920 7920
|
||||||
|
------------------------------------------------------------------
|
||||||
|
28920 35072
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
40
core/embed/rust/src/ui/shape/display/mod.rs
Normal file
40
core/embed/rust/src/ui/shape/display/mod.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#[cfg(all(feature = "xframebuffer", feature = "display_mono"))]
|
||||||
|
pub mod fb_mono8;
|
||||||
|
#[cfg(all(feature = "xframebuffer", feature = "display_mono"))]
|
||||||
|
pub use fb_mono8::render_on_display;
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))]
|
||||||
|
pub mod nofb_rgb565;
|
||||||
|
#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))]
|
||||||
|
pub use nofb_rgb565::render_on_display;
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "xframebuffer",
|
||||||
|
feature = "display_rgb565",
|
||||||
|
not(feature = "display_rgba8888")
|
||||||
|
))]
|
||||||
|
pub mod fb_rgb565;
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "xframebuffer",
|
||||||
|
feature = "display_rgb565",
|
||||||
|
not(feature = "display_rgba8888")
|
||||||
|
))]
|
||||||
|
pub use fb_rgb565::render_on_display;
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "xframebuffer",
|
||||||
|
feature = "display_rgba8888",
|
||||||
|
not(feature = "display_rgb565")
|
||||||
|
))]
|
||||||
|
pub mod fb_rgba8888;
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "xframebuffer",
|
||||||
|
feature = "display_rgba8888",
|
||||||
|
not(feature = "display_rgb565")
|
||||||
|
))]
|
||||||
|
pub use fb_rgba8888::render_on_display;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "new_rendering"))]
|
||||||
|
pub mod fake_display;
|
||||||
|
#[cfg(not(feature = "new_rendering"))]
|
||||||
|
pub use fake_display::render_on_display;
|
107
core/embed/rust/src/ui/shape/display/nofb_rgb565.rs
Normal file
107
core/embed/rust/src/ui/shape/display/nofb_rgb565.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
use crate::trezorhal::{
|
||||||
|
bitblt::{BitBltCopy, BitBltFill},
|
||||||
|
display,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::super::{BasicCanvas, BitmapView, DrawingCache, ProgressiveRenderer, Viewport};
|
||||||
|
|
||||||
|
use static_alloc::Bump;
|
||||||
|
|
||||||
|
// Maximum number of shapes on a single screen
|
||||||
|
const SHAPE_MAX_COUNT: usize = 45;
|
||||||
|
// Memory reserved for ProgressiveRenderes shape storage
|
||||||
|
const SHAPE_MEM_SIZE: usize = 5 * 1024;
|
||||||
|
// Memory not accessible by DMA
|
||||||
|
const BUMP_A_SIZE: usize = DrawingCache::get_bump_a_size() + SHAPE_MEM_SIZE;
|
||||||
|
// Memory accessible by DMA
|
||||||
|
const BUMP_B_SIZE: usize = DrawingCache::get_bump_b_size();
|
||||||
|
|
||||||
|
/// Creates the `Renderer` object for drawing on a display and invokes a
|
||||||
|
/// user-defined function that takes a single argument `target`. The user's
|
||||||
|
/// function can utilize the `target` for drawing on the display.
|
||||||
|
///
|
||||||
|
/// `clip` specifies a rectangle area that the user will draw to.
|
||||||
|
/// If no clip is specified, the entire display area is used.
|
||||||
|
///
|
||||||
|
/// `bg_color` specifies a background color with which the clip is filled before
|
||||||
|
/// the drawing starts. If the background color is None, the background
|
||||||
|
/// is undefined, and the user has to fill it themselves.
|
||||||
|
pub fn render_on_display<'a, F>(viewport: Option<Viewport>, bg_color: Option<Color>, func: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut ProgressiveRenderer<'_, 'a, Bump<[u8; BUMP_A_SIZE]>, DisplayCanvas>),
|
||||||
|
{
|
||||||
|
#[cfg_attr(not(target_os = "macos"), link_section = ".no_dma_buffers")]
|
||||||
|
static mut BUMP_A: Bump<[u8; BUMP_A_SIZE]> = Bump::uninit();
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "macos"), link_section = ".buf")]
|
||||||
|
static mut BUMP_B: Bump<[u8; BUMP_B_SIZE]> = Bump::uninit();
|
||||||
|
|
||||||
|
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
|
||||||
|
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
|
||||||
|
{
|
||||||
|
bump_a.reset();
|
||||||
|
bump_b.reset();
|
||||||
|
|
||||||
|
let cache = DrawingCache::new(bump_a, bump_b);
|
||||||
|
let mut canvas = DisplayCanvas::new();
|
||||||
|
|
||||||
|
if let Some(viewport) = viewport {
|
||||||
|
canvas.set_viewport(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target =
|
||||||
|
ProgressiveRenderer::new(&mut canvas, bg_color, &cache, bump_a, SHAPE_MAX_COUNT);
|
||||||
|
|
||||||
|
func(&mut target);
|
||||||
|
|
||||||
|
target.render(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple display canvas allowing just two bitblt operations:
|
||||||
|
/// 'fill_rect' and 'draw_bitmap` needed by `ProgressiveRenderer`.
|
||||||
|
pub struct DisplayCanvas {
|
||||||
|
size: Offset,
|
||||||
|
viewport: Viewport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayCanvas {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let size = Offset::new(display::DISPLAY_RESX as i16, display::DISPLAY_RESY as i16);
|
||||||
|
let viewport = Viewport::from_size(size);
|
||||||
|
Self { size, viewport }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BasicCanvas for DisplayCanvas {
|
||||||
|
fn viewport(&self) -> Viewport {
|
||||||
|
self.viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_viewport(&mut self, viewport: Viewport) {
|
||||||
|
self.viewport = viewport.absolute_clip(self.bounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Offset {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rect(&mut self, r: Rect, color: Color, _alpha: u8) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltFill::new(r, self.viewport.clip, color, 255) {
|
||||||
|
bitblt.display_fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||||
|
let r = r.translate(self.viewport.origin);
|
||||||
|
if let Some(bitblt) = BitBltCopy::new(r, self.viewport.clip, &bitmap) {
|
||||||
|
bitblt.display_copy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 197 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 46 KiB |
216
core/embed/rust/src/ui/shape/jpeg.rs
Normal file
216
core/embed/rust/src/ui/shape/jpeg.rs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
use crate::{
|
||||||
|
io::BinaryData,
|
||||||
|
ui::{
|
||||||
|
display::image::JpegInfo,
|
||||||
|
geometry::{Alignment2D, Offset, Point, Rect},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Bitmap, BitmapFormat, BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
/// A shape for rendering compressed JPEG images.
|
||||||
|
pub struct JpegImage<'a> {
|
||||||
|
/// Image position
|
||||||
|
pos: Point,
|
||||||
|
// Image position alignment
|
||||||
|
align: Alignment2D,
|
||||||
|
/// JPEG data
|
||||||
|
jpeg: BinaryData<'a>,
|
||||||
|
/// Scale factor (default 0)
|
||||||
|
scale: u8,
|
||||||
|
/// Blurring radius or 0 if no blurring required (default 0)
|
||||||
|
blur_radius: usize,
|
||||||
|
/// Dimming of blurred image in range of 0..255 (default 255)
|
||||||
|
dim: u8,
|
||||||
|
/// Set if blurring is pending
|
||||||
|
/// (used only during image drawing).
|
||||||
|
blur_tag: Option<u32>,
|
||||||
|
/// Final size calculated from TOIF data
|
||||||
|
size: Offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> JpegImage<'a> {
|
||||||
|
pub fn new_image(pos: Point, jpeg: BinaryData<'a>) -> Self {
|
||||||
|
JpegImage {
|
||||||
|
pos,
|
||||||
|
align: Alignment2D::TOP_LEFT,
|
||||||
|
scale: 0,
|
||||||
|
dim: 255,
|
||||||
|
blur_radius: 0,
|
||||||
|
jpeg,
|
||||||
|
blur_tag: None,
|
||||||
|
size: Offset::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(pos: Point, jpeg: &'a [u8]) -> Self {
|
||||||
|
Self::new_image(pos, jpeg.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_align(self, align: Alignment2D) -> Self {
|
||||||
|
Self { align, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_scale(self, scale: u8) -> Self {
|
||||||
|
assert!(scale <= 3);
|
||||||
|
Self { scale, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_blur(self, blur_radius: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
blur_radius,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_dim(self, dim: u8) -> Self {
|
||||||
|
Self { dim, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(mut self, renderer: &mut impl Renderer<'a>) {
|
||||||
|
self.size = self.calc_size();
|
||||||
|
renderer.render_shape(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_size(&self) -> Offset {
|
||||||
|
let info = unwrap!(JpegInfo::parse(self.jpeg), "Invalid image");
|
||||||
|
let scale_factor = 1 << self.scale;
|
||||||
|
let size = info.size();
|
||||||
|
Offset::new(size.x / scale_factor, size.y / scale_factor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Shape<'a> for JpegImage<'a> {
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
Rect::from_top_left_and_size(self.size.snap(self.pos, self.align), self.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self, _cache: &DrawingCache<'a>) {
|
||||||
|
self.blur_tag = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Faster implementation suitable for DirectRenderer without blurring support
|
||||||
|
// (but is terribly slow on ProgressiveRenderer if slices are not aligned
|
||||||
|
// to JPEG MCUs )
|
||||||
|
fn draw(&mut self, canvas: &mut dyn RgbCanvasEx, cache: &DrawingCache<'a>) {
|
||||||
|
let bounds = self.bounds(cache);
|
||||||
|
let clip = canvas.viewport().relative_clip(bounds).clip;
|
||||||
|
|
||||||
|
// translate clip to JPEG relative coordinates
|
||||||
|
let clip = clip.translate(-canvas.viewport().origin);
|
||||||
|
let clip = clip.translate((-bounds.top_left()).into());
|
||||||
|
|
||||||
|
unwrap!(
|
||||||
|
cache.jpeg().decompress_mcu(
|
||||||
|
self.jpeg,
|
||||||
|
self.scale,
|
||||||
|
clip.top_left(),
|
||||||
|
&mut |mcu_r, mcu_bitmap| {
|
||||||
|
// Draw single MCU
|
||||||
|
canvas.draw_bitmap(mcu_r.translate(bounds.top_left().into()), mcu_bitmap);
|
||||||
|
// Return true if we are not done yet
|
||||||
|
mcu_r.x1 < clip.x1 || mcu_r.y1 < clip.y1
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"Invalid JPEG"
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// This is a little bit slower implementation suitable for ProgressiveRenderer
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||||
|
let bounds = self.bounds();
|
||||||
|
let clip = canvas.viewport().relative_clip(bounds).clip;
|
||||||
|
|
||||||
|
// Translate clip to JPEG relative coordinates
|
||||||
|
let clip = clip.translate(-canvas.viewport().origin);
|
||||||
|
let clip = clip.translate((-bounds.top_left()).into());
|
||||||
|
|
||||||
|
if self.blur_radius == 0 {
|
||||||
|
// Draw JPEG without blurring
|
||||||
|
|
||||||
|
// Routine for drawing single JPEG MCU
|
||||||
|
let draw_mcu = &mut |row_r: Rect, row_bitmap: BitmapView| {
|
||||||
|
// Draw a row of decoded MCUs
|
||||||
|
canvas.draw_bitmap(row_r.translate(bounds.top_left().into()), row_bitmap);
|
||||||
|
// Return true if we are not done yet
|
||||||
|
row_r.y1 < clip.y1
|
||||||
|
};
|
||||||
|
|
||||||
|
unwrap!(
|
||||||
|
cache
|
||||||
|
.jpeg()
|
||||||
|
.decompress_row(self.jpeg, self.scale, clip.y0, draw_mcu),
|
||||||
|
"Invalid JPEG"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Draw JPEG with blurring effect
|
||||||
|
let jpeg_size = self.bounds().size();
|
||||||
|
|
||||||
|
// Get a single line working bitmap
|
||||||
|
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
|
||||||
|
let mut slice = unwrap!(
|
||||||
|
Bitmap::new_mut(
|
||||||
|
BitmapFormat::RGB565,
|
||||||
|
None,
|
||||||
|
Offset::new(jpeg_size.x, 1),
|
||||||
|
None,
|
||||||
|
&mut buff[..]
|
||||||
|
),
|
||||||
|
"Too small buffer"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the blurring algorithm instance
|
||||||
|
let mut blur_cache = cache.blur();
|
||||||
|
let (blur, blur_tag) =
|
||||||
|
unwrap!(blur_cache.get(jpeg_size, self.blur_radius, self.blur_tag));
|
||||||
|
self.blur_tag = Some(blur_tag);
|
||||||
|
|
||||||
|
if let Some(y) = blur.push_ready() {
|
||||||
|
// A function for drawing a row of JPEG MCUs
|
||||||
|
let draw_row = &mut |row_r: Rect, jpeg_slice: BitmapView| {
|
||||||
|
loop {
|
||||||
|
if let Some(y) = blur.push_ready() {
|
||||||
|
if y < row_r.y1 {
|
||||||
|
// should never fail
|
||||||
|
blur.push(unwrap!(jpeg_slice.row(y - row_r.y0)));
|
||||||
|
} else {
|
||||||
|
return true; // need more data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(y) = blur.pop_ready() {
|
||||||
|
blur.pop(unwrap!(slice.row_mut(0)), Some(self.dim)); // should never fail
|
||||||
|
let dst_r = Rect::from_top_left_and_size(bounds.top_left(), jpeg_size)
|
||||||
|
.translate(Offset::new(0, y));
|
||||||
|
canvas.draw_bitmap(dst_r, slice.view());
|
||||||
|
|
||||||
|
if y + 1 >= clip.y1 {
|
||||||
|
return false; // we are done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
unwrap!(
|
||||||
|
cache
|
||||||
|
.jpeg()
|
||||||
|
.decompress_row(self.jpeg, self.scale, y, draw_row),
|
||||||
|
"Invalid JPEG"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ShapeClone<'a> for JpegImage<'a> {
|
||||||
|
fn clone_at_bump<T>(self, bump: &'a T) -> Option<&'a mut dyn Shape<'a>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'a>,
|
||||||
|
{
|
||||||
|
let clone = bump.alloc_t()?;
|
||||||
|
Some(clone.uninit.init(JpegImage { ..self }))
|
||||||
|
}
|
||||||
|
}
|
33
core/embed/rust/src/ui/shape/mod.rs
Normal file
33
core/embed/rust/src/ui/shape/mod.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
mod bar;
|
||||||
|
mod base;
|
||||||
|
mod bitmap;
|
||||||
|
#[cfg(feature = "ui_blurring")]
|
||||||
|
mod blur;
|
||||||
|
mod cache;
|
||||||
|
mod canvas;
|
||||||
|
mod circle;
|
||||||
|
mod display;
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
mod jpeg;
|
||||||
|
mod qrcode;
|
||||||
|
mod render;
|
||||||
|
mod text;
|
||||||
|
mod toif;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
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;
|
||||||
|
pub use display::render_on_display;
|
||||||
|
#[cfg(feature = "ui_jpeg_decoder")]
|
||||||
|
pub use jpeg::JpegImage;
|
||||||
|
pub use qrcode::QrImage;
|
||||||
|
pub use render::{DirectRenderer, ProgressiveRenderer, Renderer};
|
||||||
|
pub use text::Text;
|
||||||
|
pub use toif::ToifImage;
|
||||||
|
pub use utils::PI4;
|
169
core/embed/rust/src/ui/shape/qrcode.rs
Normal file
169
core/embed/rust/src/ui/shape/qrcode.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use qrcodegen::QrCode;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
utils::line_points, Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone,
|
||||||
|
};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
const MAX_QRCODE_BYTES: usize = 400;
|
||||||
|
|
||||||
|
/// A shape for `QrCode` rendering.
|
||||||
|
pub struct QrImage {
|
||||||
|
/// Destination rectangle
|
||||||
|
area: Rect,
|
||||||
|
/// QR code bitmap
|
||||||
|
qr_modules: [u8; MAX_QRCODE_BYTES],
|
||||||
|
/// Size of QR code bitmap in bytes
|
||||||
|
qr_size: i16,
|
||||||
|
/// Foreground color
|
||||||
|
fg_color: Color,
|
||||||
|
/// Optional background color
|
||||||
|
bg_color: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QrImage {
|
||||||
|
pub fn new(area: Rect, qrcode: &QrCode) -> Self {
|
||||||
|
if area.width() < qrcode.size() as i16 || area.height() < qrcode.size() as i16 {
|
||||||
|
panic!("Too small area");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = QrImage {
|
||||||
|
area,
|
||||||
|
qr_size: qrcode.size() as i16,
|
||||||
|
qr_modules: [0u8; MAX_QRCODE_BYTES],
|
||||||
|
fg_color: Color::white(),
|
||||||
|
bg_color: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy content of QR code to the qrmodules buffer
|
||||||
|
for y in 0..result.qr_size {
|
||||||
|
for x in 0..result.qr_size {
|
||||||
|
result.set_module(x, y, qrcode.get_module(x as i32, y as i32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_module(&mut self, x: i16, y: i16, value: bool) {
|
||||||
|
// Every row starts at byte aligned address
|
||||||
|
let row_offset = (y * (self.qr_size + 7) / 8) as usize;
|
||||||
|
let row = &mut self.qr_modules[row_offset..];
|
||||||
|
let col_offset = (x / 8) as usize;
|
||||||
|
let col_bit = 1 << (x & 0x7);
|
||||||
|
if value {
|
||||||
|
row[col_offset] |= col_bit;
|
||||||
|
} else {
|
||||||
|
row[col_offset] &= col_bit ^ 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_module(&self, x: i16, y: i16) -> bool {
|
||||||
|
// Every row starts at byte aligned address
|
||||||
|
let row_offset = (y * (self.qr_size + 7) / 8) as usize;
|
||||||
|
let row = &self.qr_modules[row_offset..];
|
||||||
|
let col_offset = (x / 8) as usize;
|
||||||
|
let col_bit = 1 << (x & 0x7);
|
||||||
|
(row[col_offset] & col_bit) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fg(self, fg_color: Color) -> Self {
|
||||||
|
Self { fg_color, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_bg(self, bg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
bg_color: Some(bg_color),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||||
|
renderer.render_shape(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_row(&self, slice_row: &mut [u8], qr_y: i16) {
|
||||||
|
slice_row.iter_mut().for_each(|b| *b = 0);
|
||||||
|
|
||||||
|
let mut qr_module = false;
|
||||||
|
|
||||||
|
for p in line_points(self.area.x1 - self.area.x0, self.qr_size, 0) {
|
||||||
|
if p.first {
|
||||||
|
qr_module = self.get_module(p.v, qr_y);
|
||||||
|
}
|
||||||
|
if !qr_module {
|
||||||
|
if p.u & 0x01 == 0 {
|
||||||
|
slice_row[(p.u / 2) as usize] |= 0x0F;
|
||||||
|
} else {
|
||||||
|
slice_row[(p.u / 2) as usize] |= 0xF0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape<'_> for QrImage {
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
self.area
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||||
|
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
|
||||||
|
let buff = &mut unwrap!(cache.image_buff(), "No TOIF buffer");
|
||||||
|
|
||||||
|
let mut slice = unwrap!(
|
||||||
|
Bitmap::new_mut(
|
||||||
|
BitmapFormat::MONO4,
|
||||||
|
None,
|
||||||
|
Offset::new(self.area.width(), 1),
|
||||||
|
None,
|
||||||
|
&mut buff[..]
|
||||||
|
),
|
||||||
|
"Too small buffer"
|
||||||
|
);
|
||||||
|
|
||||||
|
let clip = canvas.viewport().relative_clip(self.bounds()).clip;
|
||||||
|
|
||||||
|
// translate clip to the relative coordinates
|
||||||
|
let clip = clip.translate(-canvas.viewport().origin);
|
||||||
|
let clip = clip.translate((-self.area.top_left()).into());
|
||||||
|
|
||||||
|
for p in line_points(self.area.y1 - self.area.y0, self.qr_size, clip.y0)
|
||||||
|
.take(clip.height() as usize)
|
||||||
|
{
|
||||||
|
if p.first {
|
||||||
|
self.draw_row(slice.row_mut(0).unwrap(), p.v);
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Rect {
|
||||||
|
y0: self.area.y0 + p.u,
|
||||||
|
y1: self.area.y0 + p.u + 1,
|
||||||
|
..self.area
|
||||||
|
};
|
||||||
|
|
||||||
|
let slice_view = slice.view().with_fg(self.fg_color);
|
||||||
|
|
||||||
|
match self.bg_color {
|
||||||
|
Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)),
|
||||||
|
None => canvas.blend_bitmap(r, slice_view),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ShapeClone<'s> for QrImage {
|
||||||
|
fn clone_at_bump<T>(self, bump: &'s T) -> Option<&'s mut dyn Shape<'s>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'s>,
|
||||||
|
{
|
||||||
|
let clone = bump.alloc_t()?;
|
||||||
|
Some(clone.uninit.init(QrImage { ..self }))
|
||||||
|
}
|
||||||
|
}
|
254
core/embed/rust/src/ui/shape/render.rs
Normal file
254
core/embed/rust/src/ui/shape/render.rs
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::Color,
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{BasicCanvas, Canvas, DrawingCache, Rgb565Canvas, Shape, ShapeClone, Viewport};
|
||||||
|
|
||||||
|
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// trait Renderer
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
/// All renders must implement Renderer trait
|
||||||
|
/// Renderers can immediately use the draw() method of the passed shape or
|
||||||
|
/// may store it (using the boxed() method) and draw it later
|
||||||
|
pub trait Renderer<'a> {
|
||||||
|
fn viewport(&self) -> Viewport;
|
||||||
|
|
||||||
|
fn set_viewport(&mut self, viewport: Viewport);
|
||||||
|
|
||||||
|
fn set_window(&mut self, window: Rect) -> Viewport {
|
||||||
|
let viewport = self.viewport();
|
||||||
|
self.set_viewport(viewport.relative_window(window));
|
||||||
|
viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_clip(&mut self, clip: Rect) -> Viewport {
|
||||||
|
let viewport = self.viewport();
|
||||||
|
self.set_viewport(viewport.relative_clip(clip));
|
||||||
|
viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_shape<S>(&mut self, shape: S)
|
||||||
|
where
|
||||||
|
S: Shape<'a> + ShapeClone<'a>;
|
||||||
|
|
||||||
|
fn in_window(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) {
|
||||||
|
let original = self.set_window(r);
|
||||||
|
inner(self);
|
||||||
|
self.set_viewport(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_clip(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) {
|
||||||
|
let original = self.set_clip(r);
|
||||||
|
inner(self);
|
||||||
|
self.set_viewport(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_origin(&mut self, origin: Offset, inner: &dyn Fn(&mut Self)) {
|
||||||
|
let original = self.viewport();
|
||||||
|
self.set_viewport(self.viewport().with_origin(origin));
|
||||||
|
inner(self);
|
||||||
|
self.set_viewport(original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// struct DirectRenderer
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
/// A simple implementation of a Renderer that draws directly onto the CanvasEx
|
||||||
|
pub struct DirectRenderer<'a, 'alloc, C>
|
||||||
|
where
|
||||||
|
C: Canvas,
|
||||||
|
{
|
||||||
|
/// Target canvas
|
||||||
|
canvas: &'a mut C,
|
||||||
|
/// Drawing cache (decompression context, scratch-pad memory)
|
||||||
|
cache: &'a DrawingCache<'alloc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'alloc, C> DirectRenderer<'a, 'alloc, C>
|
||||||
|
where
|
||||||
|
C: Canvas,
|
||||||
|
{
|
||||||
|
/// Creates a new DirectRenderer instance with the given canvas
|
||||||
|
pub fn new(
|
||||||
|
canvas: &'a mut C,
|
||||||
|
bg_color: Option<Color>,
|
||||||
|
cache: &'a DrawingCache<'alloc>,
|
||||||
|
) -> Self {
|
||||||
|
if let Some(color) = bg_color {
|
||||||
|
canvas.fill_background(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider storing original canvas.viewport
|
||||||
|
// and restoring it by drop() function
|
||||||
|
|
||||||
|
Self { canvas, cache }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'alloc, C> Renderer<'alloc> for DirectRenderer<'a, 'alloc, C>
|
||||||
|
where
|
||||||
|
C: Canvas,
|
||||||
|
{
|
||||||
|
fn viewport(&self) -> Viewport {
|
||||||
|
self.canvas.viewport()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_viewport(&mut self, viewport: Viewport) {
|
||||||
|
self.canvas.set_viewport(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_shape<S>(&mut self, mut shape: S)
|
||||||
|
where
|
||||||
|
S: Shape<'alloc> + ShapeClone<'alloc>,
|
||||||
|
{
|
||||||
|
if self.canvas.viewport().contains(shape.bounds()) {
|
||||||
|
shape.draw(self.canvas, self.cache);
|
||||||
|
shape.cleanup(self.cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// struct ProgressiveRenderer
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
struct ShapeHolder<'a> {
|
||||||
|
shape: &'a mut dyn Shape<'a>,
|
||||||
|
viewport: Viewport,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A more advanced Renderer implementation that supports deferred rendering.
|
||||||
|
pub struct ProgressiveRenderer<'a, 'alloc, T, C>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'alloc>,
|
||||||
|
C: BasicCanvas,
|
||||||
|
{
|
||||||
|
/// Target canvas
|
||||||
|
canvas: &'a mut C,
|
||||||
|
/// Bump for cloning shapes
|
||||||
|
bump: &'alloc T,
|
||||||
|
/// List of rendered shapes
|
||||||
|
shapes: FixedVec<'alloc, ShapeHolder<'alloc>>,
|
||||||
|
/// Current viewport
|
||||||
|
viewport: Viewport,
|
||||||
|
// Default background color
|
||||||
|
bg_color: Option<Color>,
|
||||||
|
/// Drawing cache (decompression context, scratch-pad memory)
|
||||||
|
cache: &'a DrawingCache<'alloc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'alloc, T, C> ProgressiveRenderer<'a, 'alloc, T, C>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'alloc>,
|
||||||
|
C: BasicCanvas,
|
||||||
|
{
|
||||||
|
/// Creates a new ProgressiveRenderer instance
|
||||||
|
pub fn new(
|
||||||
|
canvas: &'a mut C,
|
||||||
|
bg_color: Option<Color>,
|
||||||
|
cache: &'a DrawingCache<'alloc>,
|
||||||
|
bump: &'alloc T,
|
||||||
|
max_shapes: usize,
|
||||||
|
) -> Self {
|
||||||
|
let viewport = canvas.viewport();
|
||||||
|
Self {
|
||||||
|
canvas,
|
||||||
|
bump,
|
||||||
|
shapes: unwrap!(bump.fixed_vec(max_shapes), "No shape memory"),
|
||||||
|
viewport,
|
||||||
|
bg_color,
|
||||||
|
cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders stored shapes onto the specified canvas
|
||||||
|
pub fn render(&mut self, lines: usize) {
|
||||||
|
let canvas_clip = self.canvas.viewport().clip;
|
||||||
|
|
||||||
|
if canvas_clip.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buff = &mut unwrap!(self.cache.render_buff(), "No render buffer");
|
||||||
|
|
||||||
|
let mut slice = unwrap!(
|
||||||
|
Rgb565Canvas::new(
|
||||||
|
Offset::new(canvas_clip.width(), lines as i16),
|
||||||
|
None,
|
||||||
|
Some(1),
|
||||||
|
&mut buff[..],
|
||||||
|
),
|
||||||
|
"No render memory"
|
||||||
|
);
|
||||||
|
|
||||||
|
let lines = slice.height() as usize;
|
||||||
|
|
||||||
|
for y in (canvas_clip.y0..canvas_clip.y1).step_by(lines) {
|
||||||
|
// Calculate the coordinates of the slice we will draw into
|
||||||
|
let slice_r = Rect::new(
|
||||||
|
// slice_r is in absolute coordinates
|
||||||
|
Point::new(canvas_clip.x0, y),
|
||||||
|
Point::new(canvas_clip.x1, y + lines as i16),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear the slice background
|
||||||
|
if let Some(color) = self.bg_color {
|
||||||
|
slice.set_viewport(Viewport::from_size(slice_r.size()));
|
||||||
|
slice.fill_background(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw all shapes that overlaps the slice
|
||||||
|
for holder in self.shapes.iter_mut() {
|
||||||
|
let shape_viewport = holder.viewport.absolute_clip(slice_r);
|
||||||
|
let shape_bounds = holder.shape.bounds();
|
||||||
|
|
||||||
|
// Is the shape overlapping the current slice?
|
||||||
|
if shape_viewport.contains(shape_bounds) {
|
||||||
|
slice.set_viewport(shape_viewport.translate((-slice_r.top_left()).into()));
|
||||||
|
holder.shape.draw(&mut slice, self.cache);
|
||||||
|
|
||||||
|
if shape_bounds.y1 + shape_viewport.origin.y <= shape_viewport.clip.y1 {
|
||||||
|
// The shape will never be drawn again
|
||||||
|
holder.shape.cleanup(self.cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.canvas.draw_bitmap(slice_r, slice.view());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'alloc, T, C> Renderer<'alloc> for ProgressiveRenderer<'a, 'alloc, T, C>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'alloc>,
|
||||||
|
C: BasicCanvas,
|
||||||
|
{
|
||||||
|
fn viewport(&self) -> Viewport {
|
||||||
|
self.viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_viewport(&mut self, viewport: Viewport) {
|
||||||
|
self.viewport = viewport.absolute_clip(self.canvas.bounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_shape<S>(&mut self, shape: S)
|
||||||
|
where
|
||||||
|
S: Shape<'alloc> + ShapeClone<'alloc>,
|
||||||
|
{
|
||||||
|
// Is the shape visible?
|
||||||
|
if self.viewport.contains(shape.bounds()) {
|
||||||
|
// Clone the shape & push it to the list
|
||||||
|
let holder = ShapeHolder {
|
||||||
|
shape: unwrap!(shape.clone_at_bump(self.bump), "No shape memory"),
|
||||||
|
viewport: self.viewport,
|
||||||
|
};
|
||||||
|
unwrap!(self.shapes.push(holder), "Shape list full");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
core/embed/rust/src/ui/shape/text.rs
Normal file
134
core/embed/rust/src/ui/shape/text.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
display::{Color, Font},
|
||||||
|
geometry::{Alignment, Offset, Point, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
/// A shape for text strings rendering.
|
||||||
|
pub struct Text<'a> {
|
||||||
|
// Text position
|
||||||
|
pos: Point,
|
||||||
|
// Text string
|
||||||
|
text: &'a str,
|
||||||
|
// Text color
|
||||||
|
color: Color,
|
||||||
|
// Text font
|
||||||
|
font: Font,
|
||||||
|
// Horizontal alignment
|
||||||
|
align: Alignment,
|
||||||
|
// Final bounds calculated when rendered
|
||||||
|
bounds: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Text<'a> {
|
||||||
|
/// Creates a `shape::Text` structure with a specified
|
||||||
|
/// text (`str`) and the top-left corner (`pos`).
|
||||||
|
pub fn new(pos: Point, text: &'a str) -> Self {
|
||||||
|
Self {
|
||||||
|
pos,
|
||||||
|
text,
|
||||||
|
color: Color::white(),
|
||||||
|
font: Font::NORMAL,
|
||||||
|
align: Alignment::Start,
|
||||||
|
bounds: Rect::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fg(self, color: Color) -> Self {
|
||||||
|
Self { color, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_font(self, font: Font) -> Self {
|
||||||
|
Self { font, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_align(self, align: Alignment) -> Self {
|
||||||
|
Self { align, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<'r>(mut self, renderer: &mut impl Renderer<'r>) {
|
||||||
|
self.bounds = self.calc_bounds();
|
||||||
|
renderer.render_shape(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aligned_pos(&self) -> Point {
|
||||||
|
match self.align {
|
||||||
|
Alignment::Start => self.pos,
|
||||||
|
Alignment::Center => Point::new(
|
||||||
|
self.font.horz_center(self.pos.x, self.pos.x, self.text),
|
||||||
|
self.pos.y,
|
||||||
|
),
|
||||||
|
Alignment::End => Point::new(self.pos.x - self.font.text_width(self.text), self.pos.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_bounds(&self) -> Rect {
|
||||||
|
let pos = self.aligned_pos();
|
||||||
|
let (ascent, descent) = self.font.visible_text_height_ex(self.text);
|
||||||
|
Rect {
|
||||||
|
x0: pos.x,
|
||||||
|
y0: pos.y - ascent,
|
||||||
|
x1: pos.x + self.font.text_width(self.text),
|
||||||
|
y1: pos.y + descent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Shape<'_> for Text<'a> {
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
self.bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||||
|
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) {
|
||||||
|
let mut r = self.bounds();
|
||||||
|
let max_ascent = self.pos.y - r.y0;
|
||||||
|
|
||||||
|
// TODO: optimize text clipping, use canvas.viewport()
|
||||||
|
|
||||||
|
for ch in self.text.chars() {
|
||||||
|
if r.x0 >= r.x1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let glyph = self.font.get_glyph(ch);
|
||||||
|
let glyph_bitmap = glyph.bitmap();
|
||||||
|
let glyph_view = BitmapView::new(&glyph_bitmap)
|
||||||
|
.with_fg(self.color)
|
||||||
|
.with_offset(Offset::new(
|
||||||
|
-glyph.bearing_x,
|
||||||
|
-(max_ascent - glyph.bearing_y),
|
||||||
|
));
|
||||||
|
|
||||||
|
canvas.blend_bitmap(r, glyph_view);
|
||||||
|
r.x0 += glyph.adv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 's> ShapeClone<'s> for Text<'a> {
|
||||||
|
fn clone_at_bump<T>(self, bump: &'s T) -> Option<&'s mut dyn Shape<'s>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'s>,
|
||||||
|
{
|
||||||
|
let clone = bump.alloc_t()?;
|
||||||
|
let text = bump.copy_str(self.text)?;
|
||||||
|
Some(clone.uninit.init(Text { text, ..self }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Font {
|
||||||
|
fn visible_text_height_ex(&self, text: &str) -> (i16, i16) {
|
||||||
|
let (mut ascent, mut descent) = (0, 0);
|
||||||
|
for c in text.chars() {
|
||||||
|
let glyph = self.get_glyph(c);
|
||||||
|
ascent = ascent.max(glyph.bearing_y);
|
||||||
|
descent = descent.max(glyph.height - glyph.bearing_y);
|
||||||
|
}
|
||||||
|
(ascent, descent)
|
||||||
|
}
|
||||||
|
}
|
198
core/embed/rust/src/ui/shape/toif.rs
Normal file
198
core/embed/rust/src/ui/shape/toif.rs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
use crate::{
|
||||||
|
io::BinaryData,
|
||||||
|
ui::{
|
||||||
|
display::{image::ToifInfo, toif::Toif, Color},
|
||||||
|
geometry::{Alignment2D, Offset, Point, Rect},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||||
|
|
||||||
|
use without_alloc::alloc::LocalAllocLeakExt;
|
||||||
|
|
||||||
|
/// A shape for rendering compressed TOIF images.
|
||||||
|
pub struct ToifImage<'a> {
|
||||||
|
/// Image position
|
||||||
|
pos: Point,
|
||||||
|
// Image position alignment
|
||||||
|
align: Alignment2D,
|
||||||
|
// Image data
|
||||||
|
toif: BinaryData<'a>,
|
||||||
|
// Foreground color
|
||||||
|
fg_color: Color,
|
||||||
|
// Optional background color
|
||||||
|
bg_color: Option<Color>,
|
||||||
|
/// Final size calculated from TOIF data
|
||||||
|
size: Offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToifImage<'a> {
|
||||||
|
pub fn new_image(pos: Point, toif: BinaryData<'a>) -> Self {
|
||||||
|
Self {
|
||||||
|
pos,
|
||||||
|
align: Alignment2D::TOP_LEFT,
|
||||||
|
toif,
|
||||||
|
fg_color: Color::white(),
|
||||||
|
bg_color: None,
|
||||||
|
size: Offset::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(pos: Point, toif: Toif<'a>) -> Self {
|
||||||
|
Self {
|
||||||
|
pos,
|
||||||
|
align: Alignment2D::TOP_LEFT,
|
||||||
|
toif: toif.original_data().into(),
|
||||||
|
fg_color: Color::white(),
|
||||||
|
bg_color: None,
|
||||||
|
size: Offset::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_align(self, align: Alignment2D) -> Self {
|
||||||
|
Self { align, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fg(self, fg_color: Color) -> Self {
|
||||||
|
Self { fg_color, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_bg(self, bg_color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
bg_color: Some(bg_color),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(mut self, renderer: &mut impl Renderer<'a>) {
|
||||||
|
self.size = self.calc_size();
|
||||||
|
renderer.render_shape(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_grayscale(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||||
|
// TODO: introduce new viewport/shape function for this calculation
|
||||||
|
let bounds = self.bounds();
|
||||||
|
let viewport = canvas.viewport();
|
||||||
|
let mut clip = self
|
||||||
|
.bounds()
|
||||||
|
.clamp(viewport.clip.translate(-viewport.origin))
|
||||||
|
.translate((-bounds.top_left()).into());
|
||||||
|
|
||||||
|
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
|
||||||
|
let mut slice = unwrap!(
|
||||||
|
Bitmap::new_mut(
|
||||||
|
BitmapFormat::MONO4,
|
||||||
|
None,
|
||||||
|
bounds.size(),
|
||||||
|
Some(1),
|
||||||
|
&mut buff[..]
|
||||||
|
),
|
||||||
|
"Too small buffer"
|
||||||
|
);
|
||||||
|
|
||||||
|
while !clip.is_empty() {
|
||||||
|
let height = core::cmp::min(slice.height(), clip.height());
|
||||||
|
unwrap!(
|
||||||
|
cache.zlib().uncompress_toif(
|
||||||
|
self.toif,
|
||||||
|
clip.y0,
|
||||||
|
unwrap!(slice.rows_mut(0, height)), // should never fail
|
||||||
|
),
|
||||||
|
"Invalid TOIF"
|
||||||
|
);
|
||||||
|
|
||||||
|
let r = clip.translate(bounds.top_left().into());
|
||||||
|
|
||||||
|
let slice_view = slice
|
||||||
|
.view()
|
||||||
|
.with_fg(self.fg_color)
|
||||||
|
.with_offset(Offset::new(r.x0 - bounds.top_left().x, 0));
|
||||||
|
|
||||||
|
match self.bg_color {
|
||||||
|
Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)),
|
||||||
|
None => canvas.blend_bitmap(r, slice_view),
|
||||||
|
}
|
||||||
|
|
||||||
|
clip.y0 += height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_rgb(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||||
|
// TODO: introduce new viewport/shape function for this calculation
|
||||||
|
let bounds = self.bounds();
|
||||||
|
let viewport = canvas.viewport();
|
||||||
|
let mut clip = self
|
||||||
|
.bounds()
|
||||||
|
.clamp(viewport.clip.translate(-viewport.origin))
|
||||||
|
.translate((-bounds.top_left()).into());
|
||||||
|
|
||||||
|
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
|
||||||
|
let mut slice = unwrap!(
|
||||||
|
Bitmap::new_mut(
|
||||||
|
BitmapFormat::RGB565,
|
||||||
|
None,
|
||||||
|
bounds.size(),
|
||||||
|
Some(1),
|
||||||
|
&mut buff[..]
|
||||||
|
),
|
||||||
|
"Too small buffer"
|
||||||
|
);
|
||||||
|
|
||||||
|
while !clip.is_empty() {
|
||||||
|
let height = core::cmp::min(slice.height(), clip.height());
|
||||||
|
|
||||||
|
if let Some(row_bytes) = slice.rows_mut(0, height) {
|
||||||
|
// always true
|
||||||
|
unwrap!(
|
||||||
|
cache.zlib().uncompress_toif(self.toif, clip.y0, row_bytes,),
|
||||||
|
"Invalid TOIF"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = clip.translate(bounds.top_left().into());
|
||||||
|
|
||||||
|
let slice_view = slice
|
||||||
|
.view()
|
||||||
|
.with_offset(Offset::new(r.x0 - bounds.top_left().x, 0));
|
||||||
|
|
||||||
|
canvas.draw_bitmap(r, slice_view);
|
||||||
|
|
||||||
|
clip.y0 += height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_size(&self) -> Offset {
|
||||||
|
let info = unwrap!(ToifInfo::parse(self.toif), "Invalid image");
|
||||||
|
|
||||||
|
info.size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Shape<'a> for ToifImage<'a> {
|
||||||
|
fn bounds(&self) -> Rect {
|
||||||
|
Rect::from_top_left_and_size(self.size.snap(self.pos, self.align), self.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self, _cache: &DrawingCache<'a>) {
|
||||||
|
// TODO: inform the cache that we won't use the zlib slot anymore
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||||
|
let info = unwrap!(ToifInfo::parse(self.toif), "Invalid image");
|
||||||
|
if info.is_grayscale() {
|
||||||
|
self.draw_grayscale(canvas, cache);
|
||||||
|
} else {
|
||||||
|
self.draw_rgb(canvas, cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ShapeClone<'a> for ToifImage<'a> {
|
||||||
|
fn clone_at_bump<T>(self, bump: &'a T) -> Option<&'a mut dyn Shape<'a>>
|
||||||
|
where
|
||||||
|
T: LocalAllocLeakExt<'a>,
|
||||||
|
{
|
||||||
|
let clone = bump.alloc_t()?;
|
||||||
|
Some(clone.uninit.init(ToifImage { ..self }))
|
||||||
|
}
|
||||||
|
}
|
357
core/embed/rust/src/ui/shape/utils/blur.rs
Normal file
357
core/embed/rust/src/ui/shape/utils/blur.rs
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
use crate::{trezorhal::display, ui::geometry::Offset};
|
||||||
|
/// This is a simple and fast blurring algorithm that uses a box filter -
|
||||||
|
/// a square kernel with all coefficients set to 1.
|
||||||
|
///
|
||||||
|
/// The `BlurFilter` structure holds the context of a simple 2D window averaging
|
||||||
|
/// filter - a sliding window and the sum of all rows in the sliding window.
|
||||||
|
///
|
||||||
|
/// The `BlurFilter` implements only five public functions - `new`, `push`,
|
||||||
|
/// `push_read`, `pop` and `pop_ready`.
|
||||||
|
///
|
||||||
|
/// The `new()` function creates a blur filter context.
|
||||||
|
/// - The `size` argument specifies the size of the blurred area.
|
||||||
|
/// - The `radius` argument specifies the length of the kernel side.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let blur = BlurFilter::new(size, radius);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The `push_ready()` function returns the row from the source bitmap
|
||||||
|
/// needed to be pushed
|
||||||
|
///
|
||||||
|
/// The `push()` function pushes source row data into the sliding window and
|
||||||
|
/// performs all necessary calculations.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// if let Some(y) = blur.push_ready() {
|
||||||
|
/// blur.push(&src_bitmap.row(y)[x0..x1]);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The `pop_ready()` function returns the row from the destination bitmap
|
||||||
|
/// that can be popped out
|
||||||
|
///
|
||||||
|
/// The `pop()` function pops the blurred row from the sliding window.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// if let Some(y) = blur.pop_ready() {
|
||||||
|
/// blur.pop(&mut dst_bitmap.row(y)[x0..x1]);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
const MAX_RADIUS: usize = 4;
|
||||||
|
const MAX_SIDE: usize = 1 + MAX_RADIUS * 2;
|
||||||
|
const MAX_WIDTH: usize = display::DISPLAY_RESX as usize;
|
||||||
|
|
||||||
|
pub type BlurBuff = [u8; MAX_WIDTH * (MAX_SIDE * 3 + size_of::<u16>() * 3) + 8];
|
||||||
|
|
||||||
|
type PixelColor = u16;
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone)]
|
||||||
|
struct Rgb<T> {
|
||||||
|
pub r: T,
|
||||||
|
pub g: T,
|
||||||
|
pub b: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn mulshift(&self, multiplier: u32, shift: u8) -> Rgb<u8> {
|
||||||
|
Rgb::<u8> {
|
||||||
|
r: ((self.r as u32 * multiplier) >> shift) as u8,
|
||||||
|
g: ((self.g as u32 * multiplier) >> shift) as u8,
|
||||||
|
b: ((self.b as u32 * multiplier) >> shift) as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
r: (value >> 8) & 0xF8,
|
||||||
|
g: (value >> 3) & 0xFC,
|
||||||
|
b: (value << 3) & 0xF8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::AddAssign<u16> for Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn add_assign(&mut self, rhs: u16) {
|
||||||
|
let rgb: Self = rhs.into();
|
||||||
|
*self += rgb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::SubAssign<u16> for Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn sub_assign(&mut self, rhs: u16) {
|
||||||
|
let rgb: Self = rhs.into();
|
||||||
|
*self -= rgb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::AddAssign for Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.r += rhs.r;
|
||||||
|
self.g += rhs.g;
|
||||||
|
self.b += rhs.b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::SubAssign for Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
self.r -= rhs.r;
|
||||||
|
self.g -= rhs.g;
|
||||||
|
self.b -= rhs.b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Rgb<u8>> for u16 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: Rgb<u8>) -> u16 {
|
||||||
|
let r = (value.r as u16 & 0xF8) << 8;
|
||||||
|
let g = (value.g as u16 & 0xFC) << 3;
|
||||||
|
let b = (value.b as u16 & 0xF8) >> 3;
|
||||||
|
r | g | b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Rgb<u16>> for Rgb<u8> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: Rgb<u16>) -> Self {
|
||||||
|
Self {
|
||||||
|
r: value.r as u8,
|
||||||
|
g: value.g as u8,
|
||||||
|
b: value.b as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::AddAssign<Rgb<u8>> for Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn add_assign(&mut self, rhs: Rgb<u8>) {
|
||||||
|
self.r += rhs.r as u16;
|
||||||
|
self.g += rhs.g as u16;
|
||||||
|
self.b += rhs.b as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::SubAssign<Rgb<u8>> for Rgb<u16> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn sub_assign(&mut self, rhs: Rgb<u8>) {
|
||||||
|
self.r -= rhs.r as u16;
|
||||||
|
self.g -= rhs.g as u16;
|
||||||
|
self.b -= rhs.b as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BlurAlgorithm<'a> {
|
||||||
|
size: Offset,
|
||||||
|
radius: usize,
|
||||||
|
row: usize,
|
||||||
|
totals: &'a mut [Rgb<u16>],
|
||||||
|
window: &'a mut [Rgb<u8>],
|
||||||
|
row_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlurAlgorithm<'a> {
|
||||||
|
/// Constraints:
|
||||||
|
/// width <= MAX_WIDTH
|
||||||
|
/// radius <= MAX_RADIUS
|
||||||
|
/// width >= radius
|
||||||
|
pub fn new(size: Offset, radius: usize, memory: &'a mut BlurBuff) -> Result<Self, ()> {
|
||||||
|
assert!(size.x as usize <= MAX_WIDTH);
|
||||||
|
assert!(radius <= MAX_RADIUS);
|
||||||
|
assert!(size.x as usize > 2 * radius - 1);
|
||||||
|
|
||||||
|
// Split buffer into two parts
|
||||||
|
let window_size = size.x as usize * (1 + radius * 2);
|
||||||
|
let (window_buff, total_buff) =
|
||||||
|
memory.split_at_mut(window_size * core::mem::size_of::<Rgb<u8>>());
|
||||||
|
|
||||||
|
// Allocate `window` from the beginning of the buffer
|
||||||
|
let (_, window_buff, _) = unsafe { window_buff.align_to_mut() };
|
||||||
|
if window_buff.len() < window_size {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
let window = &mut window_buff[..window_size];
|
||||||
|
window.iter_mut().for_each(|it| *it = Rgb::<u8>::default());
|
||||||
|
|
||||||
|
// Allocate `totals` from the rest of the buffer
|
||||||
|
let (_, totals_buff, _) = unsafe { total_buff.align_to_mut() };
|
||||||
|
if totals_buff.len() < size.x as usize {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
let totals = &mut totals_buff[..size.x as usize];
|
||||||
|
totals.iter_mut().for_each(|it| *it = Rgb::<u16>::default());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
size,
|
||||||
|
radius,
|
||||||
|
row: 0,
|
||||||
|
window,
|
||||||
|
totals,
|
||||||
|
row_count: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the box filter side.
|
||||||
|
fn box_side(&self) -> usize {
|
||||||
|
1 + self.radius * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes an input row and calculates the same-sized vector
|
||||||
|
/// as the floating average of n subsequent elements where n = 2 * radius +
|
||||||
|
/// 1. Finally, it stores it into the specifed row in the sliding
|
||||||
|
/// window.
|
||||||
|
fn average_to_row(&mut self, inp: &[PixelColor], row: usize) {
|
||||||
|
let radius = self.radius;
|
||||||
|
let offset = self.size.x as usize * row;
|
||||||
|
let row = &mut self.window[offset..offset + self.size.x as usize];
|
||||||
|
|
||||||
|
let mut sum = Rgb::<u16>::default();
|
||||||
|
|
||||||
|
let divisor = (radius * 2 + 1) as u16;
|
||||||
|
let shift = 10;
|
||||||
|
let multiplier = (1 << shift) as u32 / divisor as u32;
|
||||||
|
|
||||||
|
// Prepare before averaging
|
||||||
|
for i in 0..radius {
|
||||||
|
sum += inp[0]; // Duplicate pixels on the left
|
||||||
|
sum += inp[i]; // Add first radius pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the first few pixels of the row
|
||||||
|
for i in 0..radius {
|
||||||
|
sum += inp[i + radius];
|
||||||
|
row[i] = sum.mulshift(multiplier, shift);
|
||||||
|
sum -= inp[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the inner part of the row
|
||||||
|
for i in radius..row.len() - radius {
|
||||||
|
sum += inp[i + radius];
|
||||||
|
row[i] = sum.mulshift(multiplier, shift);
|
||||||
|
sum -= inp[i - radius];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the last few pixels of the row
|
||||||
|
for i in (row.len() - radius)..row.len() {
|
||||||
|
sum += inp[inp.len() - 1];
|
||||||
|
row[i] = sum.mulshift(multiplier, shift);
|
||||||
|
sum -= inp[i - radius]; // Duplicate pixels on the right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy one row from the window to the another row.
|
||||||
|
fn copy_row(&mut self, from_row: usize, to_row: usize) {
|
||||||
|
let from_offset = self.size.x as usize * from_row;
|
||||||
|
let to_offset = self.size.x as usize * to_row;
|
||||||
|
for i in 0..self.size.x as usize {
|
||||||
|
self.window[to_offset + i] = self.window[from_offset + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracts the specified row of sliding window from `totals[]`.
|
||||||
|
fn subtract_row(&mut self, row: usize) {
|
||||||
|
let offset = self.size.x as usize * row;
|
||||||
|
let row = &self.window[offset..offset + self.size.x as usize];
|
||||||
|
|
||||||
|
for (i, item) in row.iter().enumerate() {
|
||||||
|
self.totals[i] -= *item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the specified row of sliding window to `totals[]`.
|
||||||
|
fn add_row(&mut self, row: usize) {
|
||||||
|
let offset = self.size.x as usize * row;
|
||||||
|
let row = &self.window[offset..offset + self.size.x as usize];
|
||||||
|
|
||||||
|
for (i, item) in row.iter().enumerate() {
|
||||||
|
self.totals[i] += *item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes the most recently pushed row again.
|
||||||
|
fn push_last_row(&mut self) {
|
||||||
|
let to_row = self.row;
|
||||||
|
let from_row = if to_row > 0 {
|
||||||
|
to_row - 1
|
||||||
|
} else {
|
||||||
|
self.box_side() - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
self.subtract_row(to_row);
|
||||||
|
self.copy_row(from_row, to_row);
|
||||||
|
self.add_row(to_row);
|
||||||
|
|
||||||
|
self.row = (to_row + 1) % self.box_side();
|
||||||
|
self.row_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index of the row needed to be pushed into.
|
||||||
|
pub fn push_ready(&self) -> Option<i16> {
|
||||||
|
let y = core::cmp::max(0, self.row_count as i16 - self.radius as i16);
|
||||||
|
if y < self.size.y {
|
||||||
|
Some(y)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the source row and pushes it into the sliding window.
|
||||||
|
pub fn push(&mut self, input: &[PixelColor]) {
|
||||||
|
let row = self.row;
|
||||||
|
|
||||||
|
self.subtract_row(row);
|
||||||
|
self.average_to_row(input, row);
|
||||||
|
self.add_row(row);
|
||||||
|
|
||||||
|
self.row = (row + 1) % self.box_side();
|
||||||
|
self.row_count += 1;
|
||||||
|
|
||||||
|
while self.row_count <= self.radius {
|
||||||
|
self.push_last_row();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index of row ready to be popped out.
|
||||||
|
pub fn pop_ready(&self) -> Option<i16> {
|
||||||
|
let y = self.row_count as i16 - self.box_side() as i16;
|
||||||
|
if y < 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies the current content of `totals[]` to the output buffer.
|
||||||
|
pub fn pop(&mut self, output: &mut [PixelColor], dim: Option<u8>) {
|
||||||
|
let divisor = match dim {
|
||||||
|
Some(dim) => {
|
||||||
|
if dim > 0 {
|
||||||
|
(self.box_side() as u16 * 255) / dim as u16
|
||||||
|
} else {
|
||||||
|
65535u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => self.box_side() as u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
let shift = 10;
|
||||||
|
let multiplier = (1 << shift) as u32 / divisor as u32;
|
||||||
|
|
||||||
|
for (i, item) in output.iter_mut().enumerate() {
|
||||||
|
*item = self.totals[i].mulshift(multiplier, shift).into();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.push_ready().is_none() {
|
||||||
|
self.push_last_row();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
core/embed/rust/src/ui/shape/utils/circle.rs
Normal file
76
core/embed/rust/src/ui/shape/utils/circle.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/// Iterator providing points for 1/8th of a circle (single octant)
|
||||||
|
///
|
||||||
|
/// The iterator supplies coordinates of pixels relative to the
|
||||||
|
/// circle's center point, along with an alpha value in
|
||||||
|
/// the range (0..255), indicating the proportion of the pixel
|
||||||
|
/// that lies inside the circle.
|
||||||
|
///
|
||||||
|
/// for p in circle_points(radius) {
|
||||||
|
/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>..<see_below>
|
||||||
|
/// println!("{}", p.frac); // distance from the circle <0..255>
|
||||||
|
/// println!("{}", p.first); // `v` has changed
|
||||||
|
/// println!("{}", p.last); // next `v` will change
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// `u` axis is the main and increments at each iteration.
|
||||||
|
///
|
||||||
|
/// endpoint [t, t] or [t - 1, t] where t = radius * (1 / sqrt(2))
|
||||||
|
|
||||||
|
pub fn circle_points(radius: i16) -> CirclePoints {
|
||||||
|
CirclePoints {
|
||||||
|
radius,
|
||||||
|
u: 0,
|
||||||
|
v: radius,
|
||||||
|
t1: radius / 16,
|
||||||
|
first: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CirclePoints {
|
||||||
|
radius: i16,
|
||||||
|
u: i16,
|
||||||
|
v: i16,
|
||||||
|
t1: i16,
|
||||||
|
first: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct CirclePointsItem {
|
||||||
|
pub u: i16,
|
||||||
|
pub v: i16,
|
||||||
|
pub frac: u8,
|
||||||
|
pub first: bool,
|
||||||
|
pub last: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for CirclePoints {
|
||||||
|
type Item = CirclePointsItem;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.v >= self.u {
|
||||||
|
let mut item = CirclePointsItem {
|
||||||
|
u: self.u,
|
||||||
|
v: self.v,
|
||||||
|
frac: 255 - ((self.t1 as i32 * 255) / self.radius as i32) as u8,
|
||||||
|
first: self.first,
|
||||||
|
last: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.first = false;
|
||||||
|
self.u += 1;
|
||||||
|
self.t1 += self.u;
|
||||||
|
let t2 = self.t1 - self.v;
|
||||||
|
if t2 >= 0 {
|
||||||
|
self.t1 = t2;
|
||||||
|
self.v -= 1;
|
||||||
|
self.first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.last = item.v != self.v;
|
||||||
|
|
||||||
|
Some(item)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
core/embed/rust/src/ui/shape/utils/line.rs
Normal file
94
core/embed/rust/src/ui/shape/utils/line.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/// Iterator providing points on a line (using bresenham's algorithm)
|
||||||
|
///
|
||||||
|
/// The iterator supplies coordinates of pixels relative to the
|
||||||
|
/// line's start point.
|
||||||
|
///
|
||||||
|
/// constraint: `du` >= `dv`, `start_u` < `du`
|
||||||
|
///
|
||||||
|
/// for p in line_points(du, dv, start_u) {
|
||||||
|
/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>..<du-1, dv-1>
|
||||||
|
/// println!("{}", p.frac); // distance from the line <0..255>
|
||||||
|
/// println!("{}", p.first); // `v` has changed
|
||||||
|
/// println!("{}", p.last); // next `v` will change
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// `u` axis is the main and increments at each iteration.
|
||||||
|
|
||||||
|
pub fn line_points(du: i16, dv: i16, start_u: i16) -> LinePoints {
|
||||||
|
let mut d = 2 * du - 2 * dv;
|
||||||
|
let mut y = 0;
|
||||||
|
|
||||||
|
for _ in 0..start_u {
|
||||||
|
if d <= 0 {
|
||||||
|
d += 2 * du - 2 * dv;
|
||||||
|
y += 1;
|
||||||
|
} else {
|
||||||
|
d -= 2 * dv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LinePoints {
|
||||||
|
du,
|
||||||
|
dv,
|
||||||
|
d,
|
||||||
|
u: start_u,
|
||||||
|
v: y,
|
||||||
|
first: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LinePoints {
|
||||||
|
du: i16,
|
||||||
|
dv: i16,
|
||||||
|
d: i16,
|
||||||
|
u: i16,
|
||||||
|
v: i16,
|
||||||
|
first: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct LinePointsItem {
|
||||||
|
pub u: i16,
|
||||||
|
pub v: i16,
|
||||||
|
pub frac: u8,
|
||||||
|
pub first: bool,
|
||||||
|
pub last: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for LinePoints {
|
||||||
|
type Item = LinePointsItem;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.u < self.du {
|
||||||
|
let frac = if self.dv < self.du {
|
||||||
|
255 - ((self.d + 2 * self.dv - 1) as i32 * 255 / (2 * self.du - 1) as i32) as u8
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let next = LinePointsItem {
|
||||||
|
u: self.u,
|
||||||
|
v: self.v,
|
||||||
|
frac,
|
||||||
|
first: self.first,
|
||||||
|
last: self.d <= 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.d <= 0 {
|
||||||
|
self.d += 2 * self.du - 2 * self.dv;
|
||||||
|
self.v += 1;
|
||||||
|
self.first = true;
|
||||||
|
} else {
|
||||||
|
self.d -= 2 * self.dv;
|
||||||
|
self.first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.u += 1;
|
||||||
|
|
||||||
|
Some(next)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
core/embed/rust/src/ui/shape/utils/mod.rs
Normal file
9
core/embed/rust/src/ui/shape/utils/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
mod blur;
|
||||||
|
mod circle;
|
||||||
|
mod line;
|
||||||
|
mod trigo;
|
||||||
|
|
||||||
|
pub use blur::{BlurAlgorithm, BlurBuff};
|
||||||
|
pub use circle::circle_points;
|
||||||
|
pub use line::line_points;
|
||||||
|
pub use trigo::{sin_i16, PI4};
|
29
core/embed/rust/src/ui/shape/utils/trigo.rs
Normal file
29
core/embed/rust/src/ui/shape/utils/trigo.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/// Integer representing an angle of 45 degress (PI/4).
|
||||||
|
//
|
||||||
|
// Changing this constant requires revisiting isin() algorithm
|
||||||
|
// (for higher values consider changing T type to i64 or f32)
|
||||||
|
pub const PI4: i16 = 45;
|
||||||
|
|
||||||
|
/// Fast sine approximation.
|
||||||
|
///
|
||||||
|
/// Returns mult * sin(angle).
|
||||||
|
///
|
||||||
|
/// Angle must be in range <0..PI4>.
|
||||||
|
/// This function provides an error within +-1 for multiplier up to 500
|
||||||
|
pub fn sin_i16(angle: i16, mult: i16) -> i16 {
|
||||||
|
assert!((0..=PI4).contains(&angle));
|
||||||
|
assert!(mult <= 2500);
|
||||||
|
|
||||||
|
type T = i32;
|
||||||
|
|
||||||
|
// Based on polynomial x - x^3 / 6
|
||||||
|
let x = angle as T;
|
||||||
|
|
||||||
|
// Constants for the approximation
|
||||||
|
const K: f32 = (PI4 as f32) * 4.0 / core::f32::consts::PI;
|
||||||
|
const M: T = (6.0 * K * K + 0.5) as T;
|
||||||
|
const N: T = (6.0 * K * K * K + 0.5) as T;
|
||||||
|
|
||||||
|
// Applying the approximation
|
||||||
|
(((M * x - x * x * x) * mult as T + N / 2) / N) as i16
|
||||||
|
}
|
2
vendor/micropython
vendored
2
vendor/micropython
vendored
@ -1 +1 @@
|
|||||||
Subproject commit e63e8b868ef717ae84a694eaa6782899a396a1af
|
Subproject commit 27acb6c49f7dedbe5d60b16b8f64ea6c9de82aae
|
Loading…
Reference in New Issue
Block a user