From dcda5e01428dfe946013d45c1b5a79ae58234664 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Tue, 21 Mar 2023 16:49:37 +0100 Subject: [PATCH] feat(core): support old homescreen format [no changelog] --- core/embed/rust/src/ui/component/image.rs | 2 +- core/embed/rust/src/ui/display/toif.rs | 12 +- .../ui/model_tt/component/homescreen/mod.rs | 105 +++++++++--- .../model_tt/component/homescreen/render.rs | 150 +++++++++++++----- 4 files changed, 204 insertions(+), 65 deletions(-) diff --git a/core/embed/rust/src/ui/component/image.rs b/core/embed/rust/src/ui/component/image.rs index d3a0efcd2..f431a644b 100644 --- a/core/embed/rust/src/ui/component/image.rs +++ b/core/embed/rust/src/ui/component/image.rs @@ -10,7 +10,7 @@ use crate::{ #[derive(PartialEq, Eq, Clone, Copy)] pub struct Image { - pub toif: Toif, + pub toif: Toif<'static>, area: Rect, } diff --git a/core/embed/rust/src/ui/display/toif.rs b/core/embed/rust/src/ui/display/toif.rs index 6bf34bc90..b61641c97 100644 --- a/core/embed/rust/src/ui/display/toif.rs +++ b/core/embed/rust/src/ui/display/toif.rs @@ -52,12 +52,12 @@ pub fn icon(icon: &Icon, center: Point, fg_color: Color, bg_color: Color) { /// Holding toif data and allowing it to draw itself. /// See https://docs.trezor.io/trezor-firmware/misc/toif.html for data format. #[derive(PartialEq, Eq, Clone, Copy)] -pub struct Toif { - data: &'static [u8], +pub struct Toif<'i> { + data: &'i [u8], } -impl Toif { - pub const fn new(data: &'static [u8]) -> Option { +impl<'i> Toif<'i> { + pub const fn new(data: &'i [u8]) -> Option { if data.len() < TOIF_HEADER_LENGTH || data[0] != b'T' || data[1] != b'O' || data[2] != b'I' { return None; @@ -91,7 +91,7 @@ impl Toif { Offset::new(self.width(), self.height()) } - pub fn zdata(&self) -> &'static [u8] { + pub fn zdata(&self) -> &'i [u8] { &self.data[TOIF_HEADER_LENGTH..] } @@ -110,7 +110,7 @@ impl Toif { #[derive(PartialEq, Eq, Clone, Copy)] pub struct Icon { - pub toif: Toif, + pub toif: Toif<'static>, } impl Icon { diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs index d951083bc..0e20f3fe4 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs @@ -14,6 +14,15 @@ use crate::{ }, }; +use crate::{ + trezorhal::{display::ToifFormat, uzlib::UZLIB_WINDOW_SIZE}, + ui::{ + display::{tjpgd::BufferInput, toif::Toif}, + model_tt::component::homescreen::render::{ + HomescreenJpeg, HomescreenToif, HOMESCREEN_TOIF_SIZE, + }, + }, +}; use render::{ homescreen, homescreen_blurred, HomescreenNotification, HomescreenText, HOMESCREEN_IMAGE_SIZE, }; @@ -195,16 +204,39 @@ where let notification = self.get_notification(); let res = get_image(); + let mut show_default = true; + if let Ok(data) = res { + if is_image_jpeg(data.as_ref()) { + let mut input = BufferInput(data.as_ref()); + let mut hs_img = HomescreenJpeg::new(&mut input); + homescreen( + &mut hs_img, + &[text], + notification, + self.paint_notification_only, + ); + show_default = false; + } else if is_image_toif(data.as_ref()) { + let input = unwrap!(Toif::new(data.as_ref())); + let mut window = [0; UZLIB_WINDOW_SIZE]; + let mut hs_img = + HomescreenToif::new(input.decompression_context(Some(&mut window))); + homescreen( + &mut hs_img, + &[text], + notification, + self.paint_notification_only, + ); + show_default = false; + } + } + + if show_default { + let mut input = BufferInput(IMAGE_HOMESCREEN); + let mut hs_img = HomescreenJpeg::new(&mut input); homescreen( - data.as_ref(), - &[text], - notification, - self.paint_notification_only, - ); - } else { - homescreen( - IMAGE_HOMESCREEN, + &mut hs_img, &[text], notification, self.paint_notification_only, @@ -289,10 +321,28 @@ where ]; let res = get_image(); + let mut show_default = true; + if let Ok(data) = res { - homescreen_blurred(data.as_ref(), &texts); - } else { - homescreen_blurred(IMAGE_HOMESCREEN, &texts); + if is_image_jpeg(data.as_ref()) { + let mut input = BufferInput(data.as_ref()); + let mut hs_img = HomescreenJpeg::new(&mut input); + homescreen_blurred(&mut hs_img, &texts); + show_default = false; + } else if is_image_toif(data.as_ref()) { + let input = unwrap!(Toif::new(data.as_ref())); + let mut window = [0; UZLIB_WINDOW_SIZE]; + let mut hs_img = + HomescreenToif::new(input.decompression_context(Some(&mut window))); + homescreen_blurred(&mut hs_img, &texts); + show_default = false; + } + } + + if show_default { + let mut input = BufferInput(IMAGE_HOMESCREEN); + let mut hs_img = HomescreenJpeg::new(&mut input); + homescreen_blurred(&mut hs_img, &texts); } } } @@ -303,21 +353,36 @@ fn get_image() -> Result, ()> { if let Ok(mut buffer) = result { let buf = unsafe { Gc::<[u8]>::as_mut(&mut buffer) }; if get_avatar(buf).is_ok() { - let jpeg = jpeg_info(buffer.as_ref()); - if let Some((size, mcu_height)) = jpeg { - if size.x == HOMESCREEN_IMAGE_SIZE - && size.y == HOMESCREEN_IMAGE_SIZE - && mcu_height <= 16 - { - return Ok(buffer); - } - } + return Ok(buffer); } } }; Err(()) } +fn is_image_jpeg(buffer: &[u8]) -> bool { + let jpeg = jpeg_info(buffer); + if let Some((size, mcu_height)) = jpeg { + if size.x == HOMESCREEN_IMAGE_SIZE && size.y == HOMESCREEN_IMAGE_SIZE && mcu_height <= 16 { + return true; + } + } + false +} + +fn is_image_toif(buffer: &[u8]) -> bool { + let toif = Toif::new(buffer); + if let Some(toif) = toif { + if toif.size().x == HOMESCREEN_TOIF_SIZE + && toif.size().y == HOMESCREEN_TOIF_SIZE + && toif.format() == ToifFormat::FullColorBE + { + return true; + } + } + false +} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Lockscreen { fn trace(&self, d: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs index 1a35e8cf7..e1f76bed4 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs @@ -8,6 +8,7 @@ use crate::{ buffers::{get_blurring_buffer, get_jpeg_buffer, get_jpeg_work_buffer, BufferJpeg}, display, display::bar_radius_buffer, + uzlib::UzlibContext, }, ui::{ constant::screen, @@ -51,6 +52,9 @@ struct HomescreenTextInfo { } pub const HOMESCREEN_IMAGE_SIZE: i16 = 240; +pub const HOMESCREEN_TOIF_SIZE: i16 = 144; +pub const HOMESCREEN_TOIF_Y_OFFSET: i16 = 27; +pub const HOMESCREEN_TOIF_X_OFFSET: usize = ((WIDTH - HOMESCREEN_TOIF_SIZE) / 2) as usize; const HOMESCREEN_MAX_ICON_SIZE: i16 = 20; const NOTIFICATION_HEIGHT: i16 = 36; @@ -79,6 +83,102 @@ const RED_IDX: usize = 0; const GREEN_IDX: usize = 1; const BLUE_IDX: usize = 2; +pub trait HomescreenDecompressor { + fn get_height(&self) -> i16; + fn decompress(&mut self); + fn get_data(&mut self) -> &mut BufferJpeg; +} + +pub struct HomescreenJpeg<'i> { + pub output: BufferOutput<'i>, + pub jdec: Option>, +} + +impl<'i> HomescreenJpeg<'i> { + pub fn new(input: &'i mut BufferInput<'i>) -> Self { + Self { + output: BufferOutput::new(unsafe { get_jpeg_buffer(0, true) }, WIDTH, 16), + jdec: JDEC::new(input, unsafe { + get_jpeg_work_buffer(0, true).buffer.as_mut_slice() + }) + .ok(), + } + } +} + +impl<'i> HomescreenDecompressor for HomescreenJpeg<'i> { + fn get_height(&self) -> i16 { + if let Some(dec) = self.jdec.as_ref() { + return dec.mcu_height(); + } + 1 + } + + fn decompress(&mut self) { + self.jdec.as_mut().map(|dec| dec.decomp(&mut self.output)); + } + + fn get_data(&mut self) -> &mut BufferJpeg { + self.output.buffer() + } +} + +pub struct HomescreenToif<'i> { + pub output: BufferOutput<'i>, + pub decomp_context: UzlibContext<'i>, + line: i16, +} + +impl<'i> HomescreenToif<'i> { + pub fn new(context: UzlibContext<'i>) -> Self { + Self { + output: BufferOutput::new(unsafe { get_jpeg_buffer(0, true) }, WIDTH, 16), + decomp_context: context, + line: 0, + } + } +} + +impl<'i> HomescreenDecompressor for HomescreenToif<'i> { + fn get_height(&self) -> i16 { + 1 + } + + fn decompress(&mut self) { + // SAFETY: Aligning to u8 slice is safe, because the original slice is aligned + // to 16 bits, therefore there are also no residuals (prefix/suffix). + // The data in the slices are integers, so these are valid for both u16 + // and u8. + if self.line >= HOMESCREEN_TOIF_Y_OFFSET + && self.line < HOMESCREEN_TOIF_Y_OFFSET + HOMESCREEN_TOIF_SIZE + { + let (_, workbuf, _) = unsafe { self.output.buffer().buffer.align_to_mut::() }; + let result = self.decomp_context.uncompress( + &mut workbuf[2 * HOMESCREEN_TOIF_X_OFFSET + ..2 * HOMESCREEN_TOIF_X_OFFSET + 2 * HOMESCREEN_TOIF_SIZE as usize], + ); + + if result.is_err() { + self.output.buffer().buffer.fill(0); + } else { + for i in 0..HOMESCREEN_TOIF_SIZE as usize { + workbuf.swap( + 2 * HOMESCREEN_TOIF_X_OFFSET + 2 * i, + 2 * HOMESCREEN_TOIF_X_OFFSET + 2 * i + 1, + ); + } + } + } else { + self.output.buffer().buffer.fill(0); + } + self.line += 1; + } + + fn get_data(&mut self) -> &mut BufferJpeg { + self.output.buffer() + } +} + fn homescreen_get_fg_text( y_tmp: i16, text_info: HomescreenTextInfo, @@ -428,7 +528,7 @@ fn get_data(buffer: &mut BufferJpeg, line_num: i16, mcu_height: i16) -> &mut [u1 &mut buffer.buffer[data_start..data_end] } -pub fn homescreen_blurred(data: &[u8], texts: &[HomescreenText]) { +pub fn homescreen_blurred(data: &mut dyn HomescreenDecompressor, texts: &[HomescreenText]) { let mut icon_data = [0_u8; (HOMESCREEN_MAX_ICON_SIZE * HOMESCREEN_MAX_ICON_SIZE / 2) as usize]; let text_buffer = unsafe { get_text_buffer(0, true) }; @@ -437,27 +537,15 @@ pub fn homescreen_blurred(data: &[u8], texts: &[HomescreenText]) { let mut text_info = homescreen_position_text(unwrap!(texts.get(0)), text_buffer, &mut icon_data); - let jpeg_pool = unsafe { get_jpeg_work_buffer(0, true).buffer.as_mut_slice() }; - let jpeg_buffer = unsafe { get_jpeg_buffer(0, true) }; - let mut jpeg_input = BufferInput(data); - let mut jpeg_output = BufferOutput::new(jpeg_buffer, WIDTH, 16); - let mut mcu_height = 8; - let mut jd: Option = JDEC::new(&mut jpeg_input, jpeg_pool).ok(); - - if let Some(dec) = &jd { - mcu_height = dec.mcu_height(); - if !(dec.width() == WIDTH && dec.height() == HEIGHT && mcu_height <= 16) { - jd = None - } - } - jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output)); + let mcu_height = data.get_height(); + data.decompress(); set_window(screen()); let mut blurring = BlurringContext::new(); // handling top edge case: preload the edge value N+1 times - blurring.compute_line_avgs(jpeg_output.buffer(), mcu_height); + blurring.compute_line_avgs(data.get_data(), mcu_height); for _ in 0..=BLUR_RADIUS { blurring.vertical_avg_add(); @@ -466,12 +554,12 @@ pub fn homescreen_blurred(data: &[u8], texts: &[HomescreenText]) { // load enough values to be able to compute first line averages for _ in 0..BLUR_RADIUS { - blurring.compute_line_avgs(jpeg_output.buffer(), mcu_height); + blurring.compute_line_avgs(data.get_data(), mcu_height); blurring.vertical_avg_add(); blurring.inc_add(); if (blurring.get_line_num() % mcu_height) == 0 { - jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output)); + data.decompress(); } } @@ -479,7 +567,7 @@ pub fn homescreen_blurred(data: &[u8], texts: &[HomescreenText]) { // several lines have been already decompressed before this loop, adjust for // that if y < HOMESCREEN_IMAGE_SIZE - (BLUR_RADIUS + 1) { - blurring.compute_line_avgs(jpeg_output.buffer(), mcu_height); + blurring.compute_line_avgs(data.get_data(), mcu_height); } let done = homescreen_line_blurred(&icon_data, text_buffer, text_info, &blurring, y); @@ -510,14 +598,14 @@ pub fn homescreen_blurred(data: &[u8], texts: &[HomescreenText]) { } if (blurring.get_line_num() % mcu_height) == 0 && (blurring.get_line_num() < HEIGHT) { - jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output)); + data.decompress(); } } dma2d_wait_for_transfer(); } pub fn homescreen( - data: &[u8], + data: &mut dyn HomescreenDecompressor, texts: &[HomescreenText], notification: Option, notification_only: bool, @@ -551,34 +639,20 @@ pub fn homescreen( homescreen_position_text(unwrap!(texts.get(0)), text_buffer, &mut icon_data) }; - let jpeg_pool = unsafe { get_jpeg_work_buffer(0, true).buffer.as_mut_slice() }; - let jpeg_buffer = unsafe { get_jpeg_buffer(0, true) }; - let mut jpeg_input = BufferInput(data); - let mut jpeg_output = BufferOutput::new(jpeg_buffer, WIDTH, 16); - let mut mcu_height = 8; - let mut jd: Option = JDEC::new(&mut jpeg_input, jpeg_pool).ok(); - - if let Some(dec) = &jd { - mcu_height = dec.mcu_height(); - if !(dec.width() == WIDTH && dec.height() == HEIGHT && mcu_height <= 16) { - jd = None - } - } - set_window(screen()); - let mcu_height = mcu_height as i16; + let mcu_height = data.get_height(); for y in 0..HEIGHT { if (y % mcu_height) == 0 { - jd.as_mut().map(|dec| dec.decomp(&mut jpeg_output)); + data.decompress(); } let done = homescreen_line( &icon_data, text_buffer, text_info, - jpeg_output.buffer(), + data.get_data(), mcu_height, y, );