diff --git a/core/.changelog.d/2414.added b/core/.changelog.d/2414.added
new file mode 100644
index 0000000000..33ba51897e
--- /dev/null
+++ b/core/.changelog.d/2414.added
@@ -0,0 +1 @@
+Using hardware acceleration (dma2d) for rendering
diff --git a/core/SConscript.boardloader b/core/SConscript.boardloader
index e4b548bd46..d85201f784 100644
--- a/core/SConscript.boardloader
+++ b/core/SConscript.boardloader
@@ -50,6 +50,8 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/extmod/modtrezorui/display.c',
+ 'embed/extmod/modtrezorui/colors.c',
+ f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c',
'embed/extmod/modtrezorui/fonts/fonts.c',
'embed/extmod/modtrezorui/fonts/font_bitmap.c',
]
diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index d5471ddb9c..ca1367699a 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -4,6 +4,7 @@ import os
import tools
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
+DMA2D = False
if TREZOR_MODEL in ('1', ):
# skip bootloader build
@@ -65,7 +66,10 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
+ 'embed/extmod/modtrezorui/buffers.c',
+ 'embed/extmod/modtrezorui/colors.c',
'embed/extmod/modtrezorui/display.c',
+ f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c',
'embed/extmod/modtrezorui/fonts/fonts.c',
'embed/extmod/modtrezorui/fonts/font_bitmap.c',
'vendor/micropython/lib/uzlib/adler32.c',
@@ -135,6 +139,12 @@ if TREZOR_MODEL in ('R'):
if TREZOR_MODEL in ('T',):
SOURCE_TREZORHAL.append('embed/trezorhal/touch.c')
+if DMA2D:
+ SOURCE_STMHAL.append('vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c')
+ SOURCE_TREZORHAL.append('embed/trezorhal/dma2d.c')
+ CPPDEFINES_MOD += [
+ 'USE_DMA2D',
+ ]
# fonts
tools.add_font('NORMAL', FONT_NORMAL, CPPDEFINES_MOD, SOURCE_MOD)
@@ -161,6 +171,12 @@ if TREZOR_MODEL in ('T', 'R'):
CPU_ASFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16'
CPU_CCFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 '
CPU_MODEL = 'STM32F427xx'
+ RUST_TARGET = 'thumbv7em-none-eabihf'
+elif TREZOR_MODEL in ('1',):
+ CPU_ASFLAGS = '-mthumb -mcpu=cortex-m3 -mfloat-abi=soft'
+ CPU_CCFLAGS = '-mthumb -mtune=cortex-m3 -mcpu=cortex-m3 -mfloat-abi=soft '
+ CPU_MODEL = 'STM32F405xx'
+ RUST_TARGET = 'thumbv7m-none-eabi'
else:
raise ValueError('Unknown Trezor model')
@@ -177,6 +193,7 @@ env.Replace(
CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB',
LINKFLAGS='-T embed/bootloader/memory.ld -Wl,--gc-sections -Wl,-Map=build/bootloader/bootloader.map -Wl,--warn-common',
CPPPATH=[
+ 'embed/rust',
'embed/bootloader',
'embed/bootloader/nanopb',
'embed/bootloader/protob',
diff --git a/core/SConscript.bootloader_ci b/core/SConscript.bootloader_ci
index 5807c26b5c..fd7fb81715 100644
--- a/core/SConscript.bootloader_ci
+++ b/core/SConscript.bootloader_ci
@@ -65,6 +65,8 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/extmod/modtrezorui/display.c',
+ 'embed/extmod/modtrezorui/colors.c',
+ f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c',
'embed/extmod/modtrezorui/fonts/fonts.c',
'embed/extmod/modtrezorui/fonts/font_bitmap.c',
'vendor/micropython/lib/uzlib/adler32.c',
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 3308220803..bb47726ec1 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -8,6 +8,7 @@ BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0')
EVERYTHING = BITCOIN_ONLY != '1'
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
UI2 = ARGUMENTS.get('UI2', '0') == '1' or TREZOR_MODEL in ('1', 'R')
+DMA2D = TREZOR_MODEL in ('T', )
FEATURE_FLAGS = {
"RDI": True,
@@ -177,7 +178,10 @@ CPPPATH_MOD += [
'vendor/micropython/lib/uzlib',
]
SOURCE_MOD += [
+ 'embed/extmod/modtrezorui/buffers.c',
+ 'embed/extmod/modtrezorui/colors.c',
'embed/extmod/modtrezorui/display.c',
+ f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c',
'embed/extmod/modtrezorui/fonts/fonts.c',
'embed/extmod/modtrezorui/fonts/font_bitmap.c',
'embed/extmod/modtrezorui/modtrezorui.c',
@@ -189,6 +193,7 @@ SOURCE_MOD += [
if UI2:
CPPDEFINES_MOD += [
'TREZOR_UI2',
+ 'USE_RUST_LOADER'
]
# modtrezorutils
@@ -396,6 +401,18 @@ elif TREZOR_MODEL in ('1',):
'embed/trezorhal/button.c',
]
+
+if DMA2D:
+ CPPDEFINES_MOD += [
+ 'USE_DMA2D',
+ ]
+ SOURCE_TREZORHAL += [
+ 'embed/trezorhal/dma2d.c',
+ ]
+ SOURCE_STMHAL += [
+ 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c',
+ ]
+
CPPDEFINES_MOD += ['USE_SVC_SHUTDOWN']
if FEATURE_FLAGS["RDI"]:
@@ -574,7 +591,7 @@ if FROZEN:
if EVERYTHING:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/altcoin.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/webauthn.py'))
- if TREZOR_MODEL in ('T') and UI2:
+ if TREZOR_MODEL in ('T',) and UI2:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/constants/tt.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/__init__.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/reset.py'))
@@ -699,10 +716,10 @@ if FROZEN:
protobuf_blobs = env.Command(
target=[
- f'rust/proto_enums.data',
- f'rust/proto_msgs.data',
- f'rust/proto_names.data',
- f'rust/proto_wire.data',
+ 'rust/proto_enums.data',
+ 'rust/proto_msgs.data',
+ 'rust/proto_names.data',
+ 'rust/proto_wire.data',
],
source=PROTO_SOURCES,
action='$PB2PY --bitcoin-only=%s --blob-outdir ${TARGET.dir} $SOURCES --qstr-defs build/firmware/genhdr/qstrdefs.generated.h' % BITCOIN_ONLY,
@@ -728,6 +745,8 @@ def cargo_build():
features.append('ui')
if PYOPT == '0':
features.append('ui_debug')
+ if DMA2D:
+ features.append('dma2d')
cargo_opts = [
f'--target={RUST_TARGET}',
diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest
index dc0226ca27..669351ad11 100644
--- a/core/SConscript.prodtest
+++ b/core/SConscript.prodtest
@@ -40,6 +40,8 @@ CPPPATH_MOD += [
SOURCE_MOD += [
'embed/extmod/modtrezorui/display.c',
+ 'embed/extmod/modtrezorui/colors.c',
+ f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c',
'embed/extmod/modtrezorui/fonts/fonts.c',
'embed/extmod/modtrezorui/fonts/font_bitmap.c',
'embed/extmod/modtrezorui/qr-code-generator/qrcodegen.c',
diff --git a/core/SConscript.reflash b/core/SConscript.reflash
index 774aebe99a..e72db989be 100644
--- a/core/SConscript.reflash
+++ b/core/SConscript.reflash
@@ -35,6 +35,8 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/extmod/modtrezorui/display.c',
+ 'embed/extmod/modtrezorui/colors.c',
+ f'embed/extmod/modtrezorui/display-stm32_{TREZOR_MODEL}.c',
'embed/extmod/modtrezorui/fonts/fonts.c',
'embed/extmod/modtrezorui/font_bitmap.c',
'vendor/micropython/lib/uzlib/adler32.c',
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 8b101f18db..782dab4526 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -8,6 +8,7 @@ BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0')
EVERYTHING = BITCOIN_ONLY != '1'
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
UI2 = ARGUMENTS.get('UI2', '0') == '1' or TREZOR_MODEL in ('1', 'R')
+DMA2D = TREZOR_MODEL in ('T', )
FEATURE_FLAGS = {
"SECP256K1_ZKP": True, # required for trezor.crypto.curve.bip340 (BIP340/Taproot)
@@ -70,15 +71,15 @@ CPPDEFINES_MOD += [
]
SOURCE_MOD += [
'embed/extmod/trezorobj.c',
- 'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'embed/extmod/modtrezorcrypto/crc.c',
+ 'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'vendor/trezor-crypto/address.c',
+ 'vendor/trezor-crypto/aes/aes_modes.c',
'vendor/trezor-crypto/aes/aescrypt.c',
'vendor/trezor-crypto/aes/aeskey.c',
- 'vendor/trezor-crypto/aes/aes_modes.c',
'vendor/trezor-crypto/aes/aestab.c',
- 'vendor/trezor-crypto/base58.c',
'vendor/trezor-crypto/base32.c',
+ 'vendor/trezor-crypto/base58.c',
'vendor/trezor-crypto/bignum.c',
'vendor/trezor-crypto/bip32.c',
'vendor/trezor-crypto/bip39.c',
@@ -86,22 +87,22 @@ SOURCE_MOD += [
'vendor/trezor-crypto/blake256.c',
'vendor/trezor-crypto/blake2b.c',
'vendor/trezor-crypto/blake2s.c',
- 'vendor/trezor-crypto/curves.c',
- 'vendor/trezor-crypto/ecdsa.c',
'vendor/trezor-crypto/chacha20poly1305/chacha20poly1305.c',
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
'vendor/trezor-crypto/chacha20poly1305/poly1305-donna.c',
'vendor/trezor-crypto/chacha20poly1305/rfc7539.c',
'vendor/trezor-crypto/chacha_drbg.c',
+ 'vendor/trezor-crypto/curves.c',
+ 'vendor/trezor-crypto/ecdsa.c',
'vendor/trezor-crypto/ed25519-donna/curve25519-donna-32bit.c',
'vendor/trezor-crypto/ed25519-donna/curve25519-donna-helpers.c',
'vendor/trezor-crypto/ed25519-donna/curve25519-donna-scalarmult-base.c',
- 'vendor/trezor-crypto/ed25519-donna/ed25519.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-donna-32bit-tables.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-donna-basepoint-table.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-donna-impl-base.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c',
+ 'vendor/trezor-crypto/ed25519-donna/ed25519.c',
'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c',
'vendor/trezor-crypto/groestl.c',
'vendor/trezor-crypto/hasher.c',
@@ -112,8 +113,8 @@ SOURCE_MOD += [
'vendor/trezor-crypto/nist256p1.c',
'vendor/trezor-crypto/pbkdf2.c',
'vendor/trezor-crypto/rand.c',
- 'vendor/trezor-crypto/ripemd160.c',
'vendor/trezor-crypto/rfc6979.c',
+ 'vendor/trezor-crypto/ripemd160.c',
'vendor/trezor-crypto/secp256k1.c',
'vendor/trezor-crypto/sha2.c',
'vendor/trezor-crypto/sha3.c',
@@ -173,7 +174,10 @@ CPPPATH_MOD += [
'vendor/micropython/lib/uzlib',
]
SOURCE_MOD += [
+ 'embed/extmod/modtrezorui/buffers.c',
+ 'embed/extmod/modtrezorui/colors.c',
'embed/extmod/modtrezorui/display.c',
+ 'embed/extmod/modtrezorui/display-unix.c',
'embed/extmod/modtrezorui/fonts/fonts.c',
'embed/extmod/modtrezorui/fonts/font_bitmap.c',
'embed/extmod/modtrezorui/modtrezorui.c',
@@ -185,6 +189,7 @@ SOURCE_MOD += [
if UI2:
CPPDEFINES_MOD += [
'TREZOR_UI2',
+ 'USE_RUST_LOADER'
]
if FROZEN:
CPPDEFINES_MOD += ['TREZOR_EMULATOR_FROZEN']
@@ -358,6 +363,15 @@ if TREZOR_MODEL in ('T',):
'embed/unix/sdcard.c',
]
+if DMA2D:
+ CPPDEFINES_MOD += [
+ 'USE_DMA2D',
+ ]
+ SOURCE_UNIX += [
+ 'embed/unix/dma2d.c',
+ ]
+
+
# fonts
tools.add_font('NORMAL', FONT_NORMAL, CPPDEFINES_MOD, SOURCE_MOD)
tools.add_font('BOLD', FONT_BOLD, CPPDEFINES_MOD, SOURCE_MOD)
@@ -683,6 +697,8 @@ def cargo_build():
features.append('ui')
if PYOPT == '0':
features.append('debug')
+ if DMA2D:
+ features.append('dma2d')
return f'cd embed/rust; cargo build --profile {RUST_PROFILE} --target-dir=../../build/unix/rust --no-default-features --features "{" ".join(features)}"'
diff --git a/core/embed/boardloader/.changelog.d/2414.added b/core/embed/boardloader/.changelog.d/2414.added
new file mode 100644
index 0000000000..33ba51897e
--- /dev/null
+++ b/core/embed/boardloader/.changelog.d/2414.added
@@ -0,0 +1 @@
+Using hardware acceleration (dma2d) for rendering
diff --git a/core/embed/boardloader/main.c b/core/embed/boardloader/main.c
index ec6f94217a..d11c1a1d97 100644
--- a/core/embed/boardloader/main.c
+++ b/core/embed/boardloader/main.c
@@ -192,6 +192,15 @@ static secbool copy_sdcard(void) {
}
#endif
+// this function resets settings changed in boardloader, which might be
+// incompatible with older bootloader versions, where this setting might be
+// unknown
+void set_bld_compatible_settings(void) {
+#ifdef TREZOR_MODEL_T
+ display_set_big_endian();
+#endif
+}
+
int main(void) {
reset_flags_reset();
@@ -210,6 +219,7 @@ int main(void) {
clear_otg_hs_memory();
display_init();
+ display_clear();
#if defined TREZOR_MODEL_T
sdcard_init();
@@ -233,6 +243,8 @@ int main(void) {
ensure(check_image_contents(&hdr, IMAGE_HEADER_SIZE, sectors, 1),
"invalid bootloader hash");
+ set_bld_compatible_settings();
+
jump_to(BOOTLOADER_START + IMAGE_HEADER_SIZE);
return 0;
diff --git a/core/embed/bootloader/.changelog.d/2414.added b/core/embed/bootloader/.changelog.d/2414.added
new file mode 100644
index 0000000000..33ba51897e
--- /dev/null
+++ b/core/embed/bootloader/.changelog.d/2414.added
@@ -0,0 +1 @@
+Using hardware acceleration (dma2d) for rendering
diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c
index 1038252419..fac7e13d1c 100644
--- a/core/embed/bootloader/bootui.c
+++ b/core/embed/bootloader/bootui.c
@@ -105,7 +105,7 @@ void ui_screen_boot(const vendor_header *const vhdr,
int image_top = show_string ? 30 : (DISPLAY_RESY - 120) / 2;
// check whether vendor image is 120x120
- if (memcmp(vimg, "TOIf\x78\x00\x78\x00", 4) == 0) {
+ if (memcmp(vimg, "TOIF\x78\x00\x78\x00", 4) == 0) {
uint32_t datalen = *(uint32_t *)(vimg + 8);
display_image((DISPLAY_RESX - 120) / 2, image_top, 120, 120, vimg + 12,
datalen);
diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c
index 1c91569593..d0494e8d17 100644
--- a/core/embed/bootloader/main.c
+++ b/core/embed/bootloader/main.c
@@ -30,6 +30,7 @@
#include "random_delays.h"
#include "secbool.h"
#ifdef TREZOR_MODEL_T
+#include "dma2d.h"
#include "touch.h"
#endif
#if defined TREZOR_MODEL_R
@@ -249,7 +250,12 @@ int main(void) {
random_delays_init();
// display_init_seq();
+#ifdef USE_DMA2D
+ dma2d_init();
+#endif
+
#if defined TREZOR_MODEL_T
+ display_set_little_endian();
touch_power_on();
touch_init();
#endif
@@ -413,6 +419,8 @@ int main(void) {
ui_fadeout();
}
+ ensure_compatible_settings();
+
// mpu_config_firmware();
// jump_to_unprivileged(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE);
diff --git a/core/embed/bootloader_ci/.changelog.d/2414.added b/core/embed/bootloader_ci/.changelog.d/2414.added
new file mode 100644
index 0000000000..33ba51897e
--- /dev/null
+++ b/core/embed/bootloader_ci/.changelog.d/2414.added
@@ -0,0 +1 @@
+Using hardware acceleration (dma2d) for rendering
diff --git a/core/embed/extmod/modtrezorui/buffers.c b/core/embed/extmod/modtrezorui/buffers.c
new file mode 100644
index 0000000000..b3d44d86b4
--- /dev/null
+++ b/core/embed/extmod/modtrezorui/buffers.c
@@ -0,0 +1,74 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "buffers.h"
+#include "common.h"
+#include "fonts/fonts.h"
+#include "memzero.h"
+
+#if USE_DMA2D
+
+#if defined BOOTLOADER
+#define BUFFER_SECTION __attribute__((section(".buf")))
+#else
+#define BUFFER_SECTION
+#endif
+
+#define BUFFERS_16BPP 3
+#define BUFFERS_4BPP 3
+#define BUFFERS_TEXT 1
+
+const int32_t text_buffer_height = FONT_MAX_HEIGHT;
+const int32_t buffer_width = DISPLAY_RESX;
+
+BUFFER_SECTION line_buffer_16bpp_t line_buffers_16bpp[BUFFERS_16BPP];
+BUFFER_SECTION line_buffer_4bpp_t line_buffers_4bpp[BUFFERS_4BPP];
+BUFFER_SECTION buffer_text_t text_buffers[BUFFERS_TEXT];
+
+line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear) {
+ if (idx >= BUFFERS_16BPP) {
+ return NULL;
+ }
+ if (clear) {
+ memzero(&line_buffers_16bpp[idx], sizeof(line_buffers_16bpp[idx]));
+ }
+ return &line_buffers_16bpp[idx];
+}
+
+line_buffer_4bpp_t* buffers_get_line_buffer_4bpp(uint16_t idx, bool clear) {
+ if (idx >= BUFFERS_4BPP) {
+ return NULL;
+ }
+ if (clear) {
+ memzero(&line_buffers_4bpp[idx], sizeof(line_buffers_4bpp[idx]));
+ }
+ return &line_buffers_4bpp[idx];
+}
+
+buffer_text_t* buffers_get_text_buffer(uint16_t idx, bool clear) {
+ if (idx >= BUFFERS_TEXT) {
+ return NULL;
+ }
+ if (clear) {
+ memzero(&text_buffers[idx], sizeof(text_buffers[idx]));
+ }
+ return &text_buffers[idx];
+}
+
+#endif
diff --git a/core/embed/extmod/modtrezorui/buffers.h b/core/embed/extmod/modtrezorui/buffers.h
new file mode 100644
index 0000000000..5acd07df12
--- /dev/null
+++ b/core/embed/extmod/modtrezorui/buffers.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _BUFFERS_H
+#define _BUFFERS_H
+
+#include
+
+#include "common.h"
+#include "display_defs.h"
+
+#define BUFFER_PIXELS DISPLAY_RESX
+
+#define TEXT_BUFFER_HEIGHT 24
+
+#if TEXT_BUFFER_HEIGHT < FONT_MAX_HEIGHT
+#error Text buffer height is too small, please adjust to match used fonts
+#endif
+
+#define LINE_BUFFER_16BPP_SIZE BUFFER_PIXELS * 2
+#define LINE_BUFFER_4BPP_SIZE BUFFER_PIXELS / 2
+#define TEXT_BUFFER_SIZE (BUFFER_PIXELS * TEXT_BUFFER_HEIGHT) / 2
+
+typedef __attribute__((aligned(4))) struct {
+ uint8_t buffer[LINE_BUFFER_16BPP_SIZE];
+} line_buffer_16bpp_t;
+
+typedef __attribute__((aligned(4))) struct {
+ uint8_t buffer[LINE_BUFFER_4BPP_SIZE];
+} line_buffer_4bpp_t;
+
+typedef __attribute__((aligned(4))) struct {
+ uint8_t buffer[TEXT_BUFFER_SIZE];
+} buffer_text_t;
+
+extern const int32_t text_buffer_height;
+extern const int32_t buffer_width;
+
+line_buffer_16bpp_t* buffers_get_line_buffer_16bpp(uint16_t idx, bool clear);
+line_buffer_4bpp_t* buffers_get_line_buffer_4bpp(uint16_t idx, bool clear);
+buffer_text_t* buffers_get_text_buffer(uint16_t idx, bool clear);
+
+#endif //_BUFFERS_H
diff --git a/core/embed/extmod/modtrezorui/colors.c b/core/embed/extmod/modtrezorui/colors.c
new file mode 100644
index 0000000000..abda0331a5
--- /dev/null
+++ b/core/embed/extmod/modtrezorui/colors.c
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "colors.h"
+
+uint32_t rgb565_to_rgb888(uint16_t color) {
+ uint32_t res = 0;
+ res |= ((((((uint32_t)color & 0xF800) >> 11) * 527) + 23) >> 6) << 16;
+ res |= ((((((uint32_t)color & 0x07E0) >> 5) * 259) + 33) >> 6) << 8;
+ res |= ((((((uint32_t)color & 0x001F) >> 0) * 527) + 23) >> 6) << 0;
+ return res;
+}
+
+uint32_t interpolate_rgb888_color(uint32_t color0, uint32_t color1,
+ uint8_t step) {
+ uint32_t cr, cg, cb;
+ cr = (((color0 & 0xFF0000) >> 16) * step +
+ ((color1 & 0xFF0000) >> 16) * (15 - step)) /
+ 15;
+ cg = (((color0 & 0xFF00) >> 8) * step +
+ ((color1 & 0xFF00) >> 8) * (15 - step)) /
+ 15;
+ cb = ((color0 & 0x00FF) * step + (color1 & 0x00FF) * (15 - step)) / 15;
+ return (cr << 16) | (cg << 8) | cb;
+}
diff --git a/core/embed/extmod/modtrezorui/colors.h b/core/embed/extmod/modtrezorui/colors.h
new file mode 100644
index 0000000000..b1b6397829
--- /dev/null
+++ b/core/embed/extmod/modtrezorui/colors.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _COLORS_H
+#define _COLORS_H
+
+#include "common.h"
+
+#ifdef TREZOR_MODEL_T
+#define RGB16(R, G, B) ((R & 0xF8) << 8) | ((G & 0xFC) << 3) | ((B & 0xF8) >> 3)
+#endif
+
+#define COLOR_WHITE 0xFFFF
+#define COLOR_BLACK 0x0000
+
+static inline uint16_t interpolate_color(uint16_t color0, uint16_t color1,
+ uint8_t step) {
+ uint8_t cr = 0, cg = 0, cb = 0;
+ cr = (((color0 & 0xF800) >> 11) * step +
+ ((color1 & 0xF800) >> 11) * (15 - step)) /
+ 15;
+ cg = (((color0 & 0x07E0) >> 5) * step +
+ ((color1 & 0x07E0) >> 5) * (15 - step)) /
+ 15;
+ cb = ((color0 & 0x001F) * step + (color1 & 0x001F) * (15 - step)) / 15;
+ return (cr << 11) | (cg << 5) | cb;
+}
+
+static inline void set_color_table(uint16_t colortable[16], uint16_t fgcolor,
+ uint16_t bgcolor) {
+ for (int i = 0; i < 16; i++) {
+ colortable[i] = interpolate_color(fgcolor, bgcolor, i);
+ }
+}
+
+uint32_t rgb565_to_rgb888(uint16_t color);
+
+uint32_t interpolate_rgb888_color(uint32_t color0, uint32_t color1,
+ uint8_t step);
+
+#endif //_COLORS_H
diff --git a/core/embed/extmod/modtrezorui/display-stm32_1.h b/core/embed/extmod/modtrezorui/display-stm32_1.c
similarity index 93%
rename from core/embed/extmod/modtrezorui/display-stm32_1.h
rename to core/embed/extmod/modtrezorui/display-stm32_1.c
index 7b838c9ac6..4777a9e4f0 100644
--- a/core/embed/extmod/modtrezorui/display-stm32_1.h
+++ b/core/embed/extmod/modtrezorui/display-stm32_1.c
@@ -17,6 +17,10 @@
* along with this program. If not, see .
*/
+#include
+#include
+#include "display_defs.h"
+#include "display_interface.h"
#include STM32_HAL_H
#define OLED_BUFSIZE (DISPLAY_RESX * DISPLAY_RESY / 8)
@@ -52,6 +56,8 @@
#define OLED_RST_PORT GPIOB
#define OLED_RST_PIN GPIO_PIN_1 // PB1 | Reset display
+static int DISPLAY_BACKLIGHT = -1;
+static int DISPLAY_ORIENTATION = -1;
static uint8_t OLED_BUFFER[OLED_BUFSIZE];
static struct {
@@ -91,7 +97,7 @@ void display_pixeldata(uint16_t c) {
#define PIXELDATA(c) display_pixeldata(c)
-static void display_reset_state() {}
+void display_reset_state() {}
void PIXELDATA_DIRTY() { pixeldata_dirty = true; }
@@ -104,9 +110,22 @@ void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
PIXELWINDOW.pos.y = y0;
}
-static void display_set_orientation(int degrees) { display_refresh(); }
+int display_orientation(int degrees) {
+ if (degrees != DISPLAY_ORIENTATION) {
+ if (degrees == 0 || degrees == 180) {
+ DISPLAY_ORIENTATION = degrees;
+ display_refresh();
+ }
+ }
+ return DISPLAY_ORIENTATION;
+}
-static void display_set_backlight(int val) {}
+int display_get_orientation(void) { return DISPLAY_ORIENTATION; }
+
+int display_backlight(int val) {
+ DISPLAY_BACKLIGHT = 255;
+ return DISPLAY_BACKLIGHT;
+}
SPI_HandleTypeDef spi_handle;
@@ -207,7 +226,6 @@ void display_init(void) {
spi_send(s, 25);
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); // SPI deselect
- display_clear();
display_refresh();
}
diff --git a/core/embed/extmod/modtrezorui/display-stm32_R.h b/core/embed/extmod/modtrezorui/display-stm32_R.c
similarity index 90%
rename from core/embed/extmod/modtrezorui/display-stm32_R.h
rename to core/embed/extmod/modtrezorui/display-stm32_R.c
index 33e7c798bd..1b09a13747 100644
--- a/core/embed/extmod/modtrezorui/display-stm32_R.h
+++ b/core/embed/extmod/modtrezorui/display-stm32_R.c
@@ -17,6 +17,10 @@
* along with this program. If not, see .
*/
+#include
+#include "display_defs.h"
+#include "display_interface.h"
+#include "memzero.h"
#include STM32_HAL_H
// FSMC/FMC Bank 1 - NOR/PSRAM 1
@@ -32,6 +36,8 @@
// noop on TR as we don't need to push data to display
#define PIXELDATA_DIRTY()
+static int DISPLAY_BACKLIGHT = -1;
+static int DISPLAY_ORIENTATION = -1;
struct {
uint8_t RAM[DISPLAY_RESY / 8][DISPLAY_RESX];
uint32_t row;
@@ -95,8 +101,8 @@ void display_pixeldata(uint16_t c) {
#define PIXELDATA(c) display_pixeldata(c)
-static void display_reset_state(void) {
- memset(DISPLAY_STATE.RAM, 0, sizeof(DISPLAY_STATE.RAM));
+void display_reset_state(void) {
+ memzero(DISPLAY_STATE.RAM, sizeof(DISPLAY_STATE.RAM));
DISPLAY_STATE.row = 0;
DISPLAY_STATE.col = 0;
DISPLAY_STATE.window_x0 = 0;
@@ -138,25 +144,38 @@ void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
}
}
-static void display_set_orientation(int degrees) {
- if (degrees == 0) {
- // Set Segment Re-map: (A0H - A1H)
- CMD(0xA1);
- // Set COM Output Scan Direction
- CMD(0xC8);
- }
- if (degrees == 180) {
- // Set Segment Re-map: (A0H - A1H)
- CMD(0xA0);
- // Set COM Output Scan Direction
- CMD(0xC0);
+int display_orientation(int degrees) {
+ if (degrees != DISPLAY_ORIENTATION) {
+ if (degrees == 0 || degrees == 180) {
+ DISPLAY_ORIENTATION = degrees;
+ if (degrees == 0) {
+ // Set Segment Re-map: (A0H - A1H)
+ CMD(0xA1);
+ // Set COM Output Scan Direction
+ CMD(0xC8);
+ }
+ if (degrees == 180) {
+ // Set Segment Re-map: (A0H - A1H)
+ CMD(0xA0);
+ // Set COM Output Scan Direction
+ CMD(0xC0);
+ }
+ }
}
+
+ return DISPLAY_ORIENTATION;
}
-static void display_set_backlight(int val) {
- // Set Contrast Control Register: (Double Bytes Command)
- CMD(0x81);
- CMD(val & 0xFF);
+int display_get_orientation(void) { return DISPLAY_ORIENTATION; }
+
+int display_backlight(int val) {
+ if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) {
+ DISPLAY_BACKLIGHT = val;
+ // Set Contrast Control Register: (Double Bytes Command)
+ CMD(0x81);
+ CMD(val & 0xFF);
+ }
+ return DISPLAY_BACKLIGHT;
}
static void send_init_seq_SH1107(void) {
@@ -246,7 +265,6 @@ void display_init_seq(void) {
send_init_seq_SH1107();
- display_clear();
display_unsleep();
}
diff --git a/core/embed/extmod/modtrezorui/display-stm32_T.h b/core/embed/extmod/modtrezorui/display-stm32_T.c
similarity index 83%
rename from core/embed/extmod/modtrezorui/display-stm32_T.h
rename to core/embed/extmod/modtrezorui/display-stm32_T.c
index 15c570f41c..d6b5152778 100644
--- a/core/embed/extmod/modtrezorui/display-stm32_T.h
+++ b/core/embed/extmod/modtrezorui/display-stm32_T.c
@@ -17,13 +17,16 @@
* along with this program. If not, see .
*/
+#include
+#include "display_defs.h"
+#include "display_interface.h"
+#include "memzero.h"
#include STM32_HAL_H
// using const volatile instead of #define results in binaries that change
// only in 1-byte when the flag changes.
// using #define leads compiler to over-optimize the code leading to bigger
// differencies in the resulting binaries.
-
const volatile uint8_t DISPLAY_ST7789V_INVERT_COLORS = 0;
// FSMC/FMC Bank 1 - NOR/PSRAM 1
@@ -36,15 +39,6 @@ __IO uint8_t *const DISPLAY_DATA_ADDRESS =
(__IO uint8_t *const)((uint32_t)DISPLAY_MEMORY_BASE |
(1 << DISPLAY_MEMORY_PIN));
-#define CMD(X) (*DISPLAY_CMD_ADDRESS = (X))
-#define DATA(X) (*DISPLAY_DATA_ADDRESS = (X))
-#define PIXELDATA(X) \
- DATA((X) >> 8); \
- DATA((X)&0xFF)
-
-// noop on TT as we don't need to push data to display
-#define PIXELDATA_DIRTY()
-
#define LED_PWM_TIM_PERIOD (10000)
// section "9.1.3 RDDID (04h): Read Display ID"
@@ -59,6 +53,9 @@ __IO uint8_t *const DISPLAY_DATA_ADDRESS =
// of ILI9341V datasheet
#define DISPLAY_ID_ILI9341V 0x009341U
+static int DISPLAY_BACKLIGHT = -1;
+static int DISPLAY_ORIENTATION = -1;
+
void display_pixeldata(uint16_t c) { PIXELDATA(c); }
static uint32_t read_display_id(uint8_t command) {
@@ -66,7 +63,7 @@ static uint32_t read_display_id(uint8_t command) {
uint32_t id = 0;
CMD(command);
c = *DISPLAY_DATA_ADDRESS; // first returned value is a dummy value and
- // should be discarded
+ // should be discarded
c = *DISPLAY_DATA_ADDRESS;
id |= (c << 16);
c = *DISPLAY_DATA_ADDRESS;
@@ -97,7 +94,7 @@ static uint32_t display_identify(void) {
return id;
}
-static void display_reset_state() {}
+void display_reset_state() {}
static void __attribute__((unused)) display_sleep(void) {
uint32_t id = display_identify();
@@ -106,7 +103,7 @@ static void __attribute__((unused)) display_sleep(void) {
CMD(0x28); // DISPOFF: Display Off
CMD(0x10); // SLPIN: Sleep in
HAL_Delay(5); // need to wait 5 milliseconds after "sleep in" before
- // sending any new commands
+ // sending any new commands
}
}
@@ -116,8 +113,8 @@ static void display_unsleep(void) {
(id == DISPLAY_ID_ST7789V)) {
CMD(0x11); // SLPOUT: Sleep Out
HAL_Delay(5); // need to wait 5 milliseconds after "sleep out" before
- // sending any new commands
- CMD(0x29); // DISPON: Display On
+ // sending any new commands
+ CMD(0x29); // DISPON: Display On
}
}
@@ -145,52 +142,65 @@ void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
}
}
-static void display_set_orientation(int degrees) {
- char BX = 0, BY = 0;
- uint32_t id = display_identify();
- if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
- (id == DISPLAY_ID_ST7789V)) {
+int display_orientation(int degrees) {
+ if (degrees != DISPLAY_ORIENTATION) {
+ if (degrees == 0 || degrees == 90 || degrees == 180 || degrees == 270) {
+ DISPLAY_ORIENTATION = degrees;
+
+ char BX = 0, BY = 0;
+ uint32_t id = display_identify();
+ if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
+ (id == DISPLAY_ID_ST7789V)) {
#define RGB (1 << 3)
#define MV (1 << 5)
#define MX (1 << 6)
#define MY (1 << 7)
- // MADCTL: Memory Data Access Control - reference:
- // section 9.3 in the ILI9341 manual
- // section 6.2.18 in the GC9307 manual
- // section 8.12 in the ST7789V manual
- uint8_t display_command_parameter = 0;
- switch (degrees) {
- case 0:
- display_command_parameter = 0;
- BY = (id == DISPLAY_ID_GC9307);
- break;
- case 90:
- display_command_parameter = MV | MX;
- BX = (id == DISPLAY_ID_GC9307);
- break;
- case 180:
- display_command_parameter = MX | MY;
- BY = (id != DISPLAY_ID_GC9307);
- break;
- case 270:
- display_command_parameter = MV | MY;
- BX = (id != DISPLAY_ID_GC9307);
- break;
+ // MADCTL: Memory Data Access Control - reference:
+ // section 9.3 in the ILI9341 manual
+ // section 6.2.18 in the GC9307 manual
+ // section 8.12 in the ST7789V manual
+ uint8_t display_command_parameter = 0;
+ switch (degrees) {
+ case 0:
+ display_command_parameter = 0;
+ BY = (id == DISPLAY_ID_GC9307);
+ break;
+ case 90:
+ display_command_parameter = MV | MX;
+ BX = (id == DISPLAY_ID_GC9307);
+ break;
+ case 180:
+ display_command_parameter = MX | MY;
+ BY = (id != DISPLAY_ID_GC9307);
+ break;
+ case 270:
+ display_command_parameter = MV | MY;
+ BX = (id != DISPLAY_ID_GC9307);
+ break;
+ }
+ if (id == DISPLAY_ID_GC9307) {
+ display_command_parameter ^= RGB | MY; // XOR RGB and MY settings
+ }
+ CMD(0x36);
+ DATA(display_command_parameter);
+ // reset the column and page extents
+ display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
+ }
+ BUFFER_OFFSET.x = BX ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0;
+ BUFFER_OFFSET.y = BY ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0;
}
- if (id == DISPLAY_ID_GC9307) {
- display_command_parameter ^= RGB | MY; // XOR RGB and MY settings
- }
- CMD(0x36);
- DATA(display_command_parameter);
- // reset the column and page extents
- display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
}
- BUFFER_OFFSET.x = BX ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0;
- BUFFER_OFFSET.y = BY ? (MAX_DISPLAY_RESY - DISPLAY_RESY) : 0;
+ return DISPLAY_ORIENTATION;
}
-static void display_set_backlight(int val) {
- TIM1->CCR1 = LED_PWM_TIM_PERIOD * val / 255;
+int display_get_orientation(void) { return DISPLAY_ORIENTATION; }
+
+int display_backlight(int val) {
+ if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) {
+ DISPLAY_BACKLIGHT = val;
+ TIM1->CCR1 = LED_PWM_TIM_PERIOD * val / 255;
+ }
+ return DISPLAY_BACKLIGHT;
}
static void send_init_seq_GC9307(void) {
@@ -472,7 +482,6 @@ void display_init_seq(void) {
send_init_seq_ILI9341V();
}
- display_clear();
display_unsleep();
}
@@ -588,6 +597,8 @@ void display_init(void) {
HAL_SRAM_Init(&external_display_data_sram, &normal_mode_timing, NULL);
display_init_seq();
+
+ display_set_little_endian();
}
void display_refresh(void) {
@@ -602,6 +613,40 @@ void display_refresh(void) {
}
}
+void display_set_little_endian(void) {
+ uint32_t id = display_identify();
+ if (id == DISPLAY_ID_GC9307) {
+ // CANNOT SET ENDIAN FOR GC9307
+ } else if (id == DISPLAY_ID_ST7789V) {
+ CMD(0xB0);
+ DATA(0x00);
+ DATA(0xF8);
+ } else if (id == DISPLAY_ID_ILI9341V) {
+ // Interface Control: XOR BGR as ST7789V does
+ CMD(0xF6);
+ DATA(0x09);
+ DATA(0x30);
+ DATA(0x20);
+ }
+}
+
+void display_set_big_endian(void) {
+ uint32_t id = display_identify();
+ if (id == DISPLAY_ID_GC9307) {
+ // CANNOT SET ENDIAN FOR GC9307
+ } else if (id == DISPLAY_ID_ST7789V) {
+ CMD(0xB0);
+ DATA(0x00);
+ DATA(0xF0);
+ } else if (id == DISPLAY_ID_ILI9341V) {
+ // Interface Control: XOR BGR as ST7789V does
+ CMD(0xF6);
+ DATA(0x09);
+ DATA(0x30);
+ DATA(0x00);
+ }
+}
+
const char *display_save(const char *prefix) { return NULL; }
void display_clear_save(void) {}
diff --git a/core/embed/extmod/modtrezorui/display-unix.h b/core/embed/extmod/modtrezorui/display-unix.c
similarity index 87%
rename from core/embed/extmod/modtrezorui/display-unix.h
rename to core/embed/extmod/modtrezorui/display-unix.c
index a8ccd29201..c3a336297d 100644
--- a/core/embed/extmod/modtrezorui/display-unix.h
+++ b/core/embed/extmod/modtrezorui/display-unix.c
@@ -16,11 +16,21 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+#define _GNU_SOURCE
#include
#include
+#include
+#include
+#include
+#include
#include
#include
+#include
+
+#include "common.h"
+#include "display_defs.h"
+#include "display_interface.h"
#include "profile.h"
#define EMULATOR_BORDER 16
@@ -57,9 +67,14 @@ static SDL_Texture *TEXTURE, *BACKGROUND;
static SDL_Surface *PREV_SAVED;
+static int DISPLAY_BACKLIGHT = -1;
+static int DISPLAY_ORIENTATION = -1;
int sdl_display_res_x = DISPLAY_RESX, sdl_display_res_y = DISPLAY_RESY;
int sdl_touch_offset_x, sdl_touch_offset_y;
+// this is just for compatibility with DMA2D using algorithms
+uint8_t *const DISPLAY_DATA_ADDRESS = 0;
+
static struct {
struct {
uint16_t x, y;
@@ -72,9 +87,6 @@ static struct {
} pos;
} PIXELWINDOW;
-// noop on unix, display is refreshed every loop step
-#define PIXELDATA_DIRTY()
-
void display_pixeldata(uint16_t c) {
#if defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
// set to white if highest bits of all R, G, B values are set to 1
@@ -100,7 +112,7 @@ void display_pixeldata(uint16_t c) {
#define PIXELDATA(c) display_pixeldata(c)
-static void display_reset_state() {}
+void display_reset_state() {}
void display_init_seq(void) {}
@@ -248,9 +260,34 @@ void display_refresh(void) {
SDL_RenderPresent(RENDERER);
}
-static void display_set_orientation(int degrees) { display_refresh(); }
+int display_orientation(int degrees) {
+ if (degrees != DISPLAY_ORIENTATION) {
+#if defined TREZOR_MODEL_T
+ if (degrees == 0 || degrees == 90 || degrees == 180 || degrees == 270) {
+#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
+ if (degrees == 0 || degrees == 180) {
+#else
+#error Unknown Trezor model
+#endif
+ DISPLAY_ORIENTATION = degrees;
+ display_refresh();
+ }
+ }
+ return DISPLAY_ORIENTATION;
+}
-static void display_set_backlight(int val) { display_refresh(); }
+int display_get_orientation(void) { return DISPLAY_ORIENTATION; }
+
+int display_backlight(int val) {
+#if defined TREZOR_MODEL_1
+ val = 255;
+#endif
+ if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) {
+ DISPLAY_BACKLIGHT = val;
+ display_refresh();
+ }
+ return DISPLAY_BACKLIGHT;
+}
const char *display_save(const char *prefix) {
if (!RENDERER) {
diff --git a/core/embed/extmod/modtrezorui/display.c b/core/embed/extmod/modtrezorui/display.c
index 979303128a..b5599cb4c5 100644
--- a/core/embed/extmod/modtrezorui/display.c
+++ b/core/embed/extmod/modtrezorui/display.c
@@ -23,9 +23,18 @@
#include "uzlib.h"
+#include "buffers.h"
#include "common.h"
#include "display.h"
+#ifdef USE_DMA2D
+#include "dma2d.h"
+#endif
+
+#ifdef USE_RUST_LOADER
+#include "rust_ui.h"
+#endif
+
#include "fonts/fonts.h"
#include
@@ -33,47 +42,12 @@
#include "memzero.h"
-static int DISPLAY_BACKLIGHT = -1;
-static int DISPLAY_ORIENTATION = -1;
+#include "display_interface.h"
static struct { int x, y; } DISPLAY_OFFSET;
-#ifdef TREZOR_EMULATOR
-#include "display-unix.h"
-#else
-#if defined TREZOR_MODEL_T
-#include "display-stm32_T.h"
-#elif defined TREZOR_MODEL_1
-#include "display-stm32_1.h"
-#elif defined TREZOR_MODEL_R
-#include "display-stm32_R.h"
-#else
-#error Unknown Trezor model
-#endif
-#endif
-
// common display functions
-static inline uint16_t interpolate_color(uint16_t color0, uint16_t color1,
- uint8_t step) {
- uint8_t cr = 0, cg = 0, cb = 0;
- cr = (((color0 & 0xF800) >> 11) * step +
- ((color1 & 0xF800) >> 11) * (15 - step)) /
- 15;
- cg = (((color0 & 0x07E0) >> 5) * step +
- ((color1 & 0x07E0) >> 5) * (15 - step)) /
- 15;
- cb = ((color0 & 0x001F) * step + (color1 & 0x001F) * (15 - step)) / 15;
- return (cr << 11) | (cg << 5) | cb;
-}
-
-static inline void set_color_table(uint16_t colortable[16], uint16_t fgcolor,
- uint16_t bgcolor) {
- for (int i = 0; i < 16; i++) {
- colortable[i] = interpolate_color(fgcolor, bgcolor, i);
- }
-}
-
static inline void clamp_coords(int x, int y, int w, int h, int *x0, int *y0,
int *x1, int *y1) {
*x0 = MAX(x, 0);
@@ -83,7 +57,7 @@ static inline void clamp_coords(int x, int y, int w, int h, int *x0, int *y0,
}
void display_clear(void) {
- const int saved_orientation = DISPLAY_ORIENTATION;
+ const int saved_orientation = display_get_orientation();
display_reset_state();
@@ -190,6 +164,70 @@ static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window,
uzlib_uncompress_init(decomp, window, window ? UZLIB_WINDOW_SIZE : 0);
}
+void display_text_render_buffer(const char *text, int textlen, int font,
+ buffer_text_t *buffer, int text_offset) {
+ // determine text length if not provided
+ if (textlen < 0) {
+ textlen = strlen(text);
+ }
+
+ int x = 0;
+ int max_height = font_max_height(font);
+ int baseline = font_baseline(font);
+
+ // render glyphs
+ for (int c_idx = 0; c_idx < textlen; c_idx++) {
+ const uint8_t *g = font_get_glyph(font, (uint8_t)text[c_idx]);
+ if (!g) continue;
+ const uint8_t w = g[0]; // width
+ const uint8_t h = g[1]; // height
+ const uint8_t adv = g[2]; // advance
+ const uint8_t bearX = g[3]; // bearingX
+ const uint8_t bearY = g[4]; // bearingY
+ if (w && h) {
+ for (int j = 0; j < h; j++) {
+ for (int i = 0; i < w; i++) {
+ const int a = i + j * w;
+#if TREZOR_FONT_BPP == 1
+ const uint8_t c = ((g[5 + a / 8] >> (7 - (a % 8) * 1)) & 0x01) * 15;
+#elif TREZOR_FONT_BPP == 2
+ const uint8_t c = ((g[5 + a / 4] >> (6 - (a % 4) * 2)) & 0x03) * 5;
+#elif TREZOR_FONT_BPP == 4
+ const uint8_t c = (g[5 + a / 2] >> (4 - (a % 2) * 4)) & 0x0F;
+#elif TREZOR_FONT_BPP == 8
+#error Rendering into buffer not supported when using TREZOR_FONT_BPP = 8
+ // const uint8_t c = g[5 + a / 1] >> 4;
+#else
+#error Unsupported TREZOR_FONT_BPP value
+#endif
+
+ int x_pos = text_offset + i + x + bearX;
+ int y_pos = j + max_height - bearY - baseline;
+
+ if (y_pos < 0) continue;
+
+ if (x_pos >= BUFFER_PIXELS || x_pos < 0) {
+ continue;
+ }
+
+ int buffer_pos = x_pos + y_pos * BUFFER_PIXELS;
+
+ if (buffer_pos < (sizeof(buffer_text_t) * 2)) {
+ int b = buffer_pos / 2;
+ if (buffer_pos % 2) {
+ buffer->buffer[b] |= c << 4;
+ } else {
+ buffer->buffer[b] |= (c);
+ }
+ }
+ }
+ }
+ }
+ x += adv;
+ }
+}
+
+#ifndef USE_DMA2D
void display_image(int x, int y, int w, int h, const void *data,
uint32_t datalen) {
#if defined TREZOR_MODEL_T
@@ -217,12 +255,48 @@ void display_image(int x, int y, int w, int h, const void *data,
const int px = pos % w;
const int py = pos / w;
if (px >= x0 && px <= x1 && py >= y0 && py <= y1) {
- PIXELDATA((decomp_out[0] << 8) | decomp_out[1]);
+ PIXELDATA((decomp_out[1] << 8) | decomp_out[0]);
}
decomp.dest = (uint8_t *)&decomp_out;
}
#endif
}
+#else
+void display_image(int x, int y, int w, int h, const void *data,
+ uint32_t datalen) {
+ x += DISPLAY_OFFSET.x;
+ y += DISPLAY_OFFSET.y;
+ int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
+ clamp_coords(x, y, w, h, &x0, &y0, &x1, &y1);
+ display_set_window(x0, y0, x1, y1);
+ x0 -= x;
+ x1 -= x;
+ y0 -= y;
+ y1 -= y;
+
+ struct uzlib_uncomp decomp = {0};
+ uint8_t decomp_window[UZLIB_WINDOW_SIZE] = {0};
+
+ line_buffer_16bpp_t *b1 = buffers_get_line_buffer_16bpp(0, false);
+ line_buffer_16bpp_t *b2 = buffers_get_line_buffer_16bpp(1, false);
+
+ uzlib_prepare(&decomp, decomp_window, data, datalen, b1, w * 2);
+
+ dma2d_setup_16bpp();
+
+ for (int32_t pos = 0; pos < h; pos++) {
+ int32_t pixels = w;
+ line_buffer_16bpp_t *next_buf = (pos % 2 == 1) ? b1 : b2;
+ decomp.dest = next_buf->buffer;
+ decomp.dest_limit = next_buf->buffer + w * 2;
+ int st = uzlib_uncompress(&decomp);
+ if (st < 0) break; // error
+ dma2d_wait_for_transfer();
+ dma2d_start(next_buf->buffer, (uint8_t *)DISPLAY_DATA_ADDRESS, pixels);
+ }
+ dma2d_wait_for_transfer();
+}
+#endif
#define AVATAR_BORDER_SIZE 4
#define AVATAR_BORDER_LOW \
@@ -289,6 +363,7 @@ void display_avatar(int x, int y, const void *data, uint32_t datalen,
#endif
}
+#ifndef USE_DMA2D
void display_icon(int x, int y, int w, int h, const void *data,
uint32_t datalen, uint16_t fgcolor, uint16_t bgcolor) {
x += DISPLAY_OFFSET.x;
@@ -318,25 +393,74 @@ void display_icon(int x, int y, int w, int h, const void *data,
const int px = (pos * 2) % w;
const int py = (pos * 2) / w;
if (px >= x0 && px <= x1 && py >= y0 && py <= y1) {
- PIXELDATA(colortable[decomp_out >> 4]);
PIXELDATA(colortable[decomp_out & 0x0F]);
+ PIXELDATA(colortable[decomp_out >> 4]);
}
decomp.dest = (uint8_t *)&decomp_out;
}
PIXELDATA_DIRTY();
}
+#else
+void display_icon(int x, int y, int w, int h, const void *data,
+ uint32_t datalen, uint16_t fgcolor, uint16_t bgcolor) {
+ x += DISPLAY_OFFSET.x;
+ y += DISPLAY_OFFSET.y;
+ x &= ~1; // cannot draw at odd coordinate
+ w &= ~1; // cannot draw odd-wide icons
+ int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
+ clamp_coords(x, y, w, h, &x0, &y0, &x1, &y1);
+ display_set_window(x0, y0, x1, y1);
+ x0 -= x;
+ x1 -= x;
+ y0 -= y;
+ y1 -= y;
+
+ int width = x1 - x0 + 1;
+
+ uint8_t b[DISPLAY_RESX / 2];
+ line_buffer_4bpp_t *b1 = buffers_get_line_buffer_4bpp(0, false);
+ line_buffer_4bpp_t *b2 = buffers_get_line_buffer_4bpp(1, false);
+
+ struct uzlib_uncomp decomp = {0};
+ uint8_t decomp_window[UZLIB_WINDOW_SIZE] = {0};
+
+ uzlib_prepare(&decomp, decomp_window, data, datalen, b, w / 2);
+
+ dma2d_setup_4bpp(fgcolor, bgcolor);
+
+ int off_x = x < 0 ? -x : 0;
+
+ for (uint32_t pos = 0; pos < h; pos++) {
+ line_buffer_4bpp_t *next_buf = (pos % 2 == 0) ? b1 : b2;
+ decomp.dest = b;
+ decomp.dest_limit = b + w / 2;
+ int st = uzlib_uncompress(&decomp);
+ if (st < 0) break; // error
+ if (pos >= y0 && pos <= y1) {
+ memcpy(next_buf->buffer, &b[off_x / 2], width / 2);
+ dma2d_wait_for_transfer();
+ dma2d_start(next_buf->buffer, (uint8_t *)DISPLAY_DATA_ADDRESS, width);
+ }
+ }
+ dma2d_wait_for_transfer();
+}
+#endif
// see docs/misc/toif.md for definition of the TOIF format
bool display_toif_info(const uint8_t *data, uint32_t len, uint16_t *out_w,
- uint16_t *out_h, bool *out_grayscale) {
+ uint16_t *out_h, toif_format_t *out_format) {
if (len < 12 || memcmp(data, "TOI", 3) != 0) {
return false;
}
- bool grayscale = false;
+ toif_format_t format = false;
if (data[3] == 'f') {
- grayscale = false;
+ format = TOIF_FULL_COLOR_BE;
} else if (data[3] == 'g') {
- grayscale = true;
+ format = TOIF_GRAYSCALE_OH;
+ } else if (data[3] == 'F') {
+ format = TOIF_FULL_COLOR_LE;
+ } else if (data[3] == 'G') {
+ format = TOIF_GRAYSCALE_EH;
} else {
return false;
}
@@ -349,14 +473,15 @@ bool display_toif_info(const uint8_t *data, uint32_t len, uint16_t *out_w,
return false;
}
- if (out_w != NULL && out_h != NULL && out_grayscale != NULL) {
+ if (out_w != NULL && out_h != NULL && out_format != NULL) {
*out_w = w;
*out_h = h;
- *out_grayscale = grayscale;
+ *out_format = format;
}
return true;
}
+#ifndef USE_RUST_LOADER
#if defined TREZOR_MODEL_T
#include "loader_T.h"
#elif defined TREZOR_MODEL_R
@@ -381,7 +506,7 @@ void display_loader(uint16_t progress, bool indeterminate, int yoffset,
DISPLAY_RESX / 2 + img_loader_size - 1,
DISPLAY_RESY / 2 + img_loader_size - 1 + yoffset);
uint8_t icondata[(LOADER_ICON_SIZE * LOADER_ICON_SIZE) / 2] = {0};
- if (icon && memcmp(icon, "TOIg", 4) == 0 &&
+ if (icon && memcmp(icon, "TOIG", 4) == 0 &&
LOADER_ICON_SIZE == *(uint16_t *)(icon + 4) &&
LOADER_ICON_SIZE == *(uint16_t *)(icon + 6) &&
iconlen == 12 + *(uint32_t *)(icon + 8)) {
@@ -423,9 +548,9 @@ void display_loader(uint16_t progress, bool indeterminate, int yoffset,
(y - (img_loader_size - (LOADER_ICON_SIZE / 2))) * LOADER_ICON_SIZE;
uint8_t c = 0;
if (i % 2) {
- c = icon[i / 2] & 0x0F;
- } else {
c = (icon[i / 2] & 0xF0) >> 4;
+ } else {
+ c = icon[i / 2] & 0x0F;
}
PIXELDATA(iconcolortable[c]);
} else {
@@ -453,6 +578,17 @@ void display_loader(uint16_t progress, bool indeterminate, int yoffset,
PIXELDATA_DIRTY();
#endif
}
+#else
+
+void display_loader(uint16_t progress, bool indeterminate, int yoffset,
+ uint16_t fgcolor, uint16_t bgcolor, const uint8_t *icon,
+ uint32_t iconlen, uint16_t iconfgcolor) {
+#if defined TREZOR_MODEL_T || defined TREZOR_MODEL_R
+ loader_uncompress_r(yoffset, fgcolor, bgcolor, iconfgcolor, progress,
+ indeterminate, icon, iconlen);
+#endif
+}
+#endif
#ifndef TREZOR_PRINT_DISABLE
@@ -560,7 +696,6 @@ void display_printf(const char *fmt, ...) {
#endif // TREZOR_PRINT_DISABLE
-
static void display_text_render(int x, int y, const char *text, int textlen,
int font, uint16_t fgcolor, uint16_t bgcolor) {
// determine text length if not provided
@@ -738,33 +873,6 @@ void display_offset(int set_xy[2], int *get_x, int *get_y) {
*get_y = DISPLAY_OFFSET.y;
}
-int display_orientation(int degrees) {
- if (degrees != DISPLAY_ORIENTATION) {
-#if defined TREZOR_MODEL_T
- if (degrees == 0 || degrees == 90 || degrees == 180 || degrees == 270) {
-#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
- if (degrees == 0 || degrees == 180) {
-#else
-#error Unknown Trezor model
-#endif
- DISPLAY_ORIENTATION = degrees;
- display_set_orientation(degrees);
- }
- }
- return DISPLAY_ORIENTATION;
-}
-
-int display_backlight(int val) {
-#if defined TREZOR_MODEL_1
- val = 255;
-#endif
- if (DISPLAY_BACKLIGHT != val && val >= 0 && val <= 255) {
- DISPLAY_BACKLIGHT = val;
- display_set_backlight(val);
- }
- return DISPLAY_BACKLIGHT;
-}
-
void display_fade(int start, int end, int delay) {
for (int i = 0; i < 100; i++) {
display_backlight(start + i * (end - start) / 100);
diff --git a/core/embed/extmod/modtrezorui/display.h b/core/embed/extmod/modtrezorui/display.h
index bfcf4ad17a..cc2a1f55cb 100644
--- a/core/embed/extmod/modtrezorui/display.h
+++ b/core/embed/extmod/modtrezorui/display.h
@@ -24,35 +24,11 @@
#include
#include
+#include "buffers.h"
+#include "colors.h"
+#include "display_defs.h"
+#include "display_interface.h"
#include "fonts/fonts.h"
-#if defined TREZOR_MODEL_T
-
-// ILI9341V, GC9307 and ST7789V drivers support 240px x 320px display resolution
-#define MAX_DISPLAY_RESX 240
-#define MAX_DISPLAY_RESY 320
-#define DISPLAY_RESX 240
-#define DISPLAY_RESY 240
-#define TREZOR_FONT_BPP 4
-
-#elif defined TREZOR_MODEL_1
-
-#define MAX_DISPLAY_RESX 128
-#define MAX_DISPLAY_RESY 64
-#define DISPLAY_RESX 128
-#define DISPLAY_RESY 64
-#define TREZOR_FONT_BPP 1
-
-#elif defined TREZOR_MODEL_R
-
-#define MAX_DISPLAY_RESX 128
-#define MAX_DISPLAY_RESY 128
-#define DISPLAY_RESX 128
-#define DISPLAY_RESY 128
-#define TREZOR_FONT_BPP 1
-
-#else
-#error Unknown Trezor model
-#endif
#define AVATAR_IMAGE_SIZE 144
#if defined TREZOR_MODEL_T || defined TREZOR_MODEL_1
@@ -63,12 +39,12 @@
#error Unknown Trezor model
#endif
-#ifdef TREZOR_MODEL_T
-#define RGB16(R, G, B) ((R & 0xF8) << 8) | ((G & 0xFC) << 3) | ((B & 0xF8) >> 3)
-#endif
-
-#define COLOR_WHITE 0xFFFF
-#define COLOR_BLACK 0x0000
+typedef enum {
+ TOIF_FULL_COLOR_BE = 0, // big endian
+ TOIF_GRAYSCALE_OH = 1, // odd hi
+ TOIF_FULL_COLOR_LE = 2, // little endian
+ TOIF_GRAYSCALE_EH = 3, // even hi
+} toif_format_t;
// provided by port
@@ -87,7 +63,7 @@ void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b,
uint8_t r);
bool display_toif_info(const uint8_t *buf, uint32_t len, uint16_t *out_w,
- uint16_t *out_h, bool *out_grayscale);
+ uint16_t *out_h, toif_format_t *out_format);
void display_image(int x, int y, int w, int h, const void *data,
uint32_t datalen);
void display_avatar(int x, int y, const void *data, uint32_t datalen,
@@ -114,26 +90,19 @@ void display_text_right(int x, int y, const char *text, int textlen, int font,
int display_text_width(const char *text, int textlen, int font);
int display_text_split(const char *text, int textlen, int font,
int requested_width);
+void display_text_render_buffer(const char *text, int textlen, int font,
+ buffer_text_t *buffer, int text_offset);
void display_qrcode(int x, int y, const char *data, uint8_t scale);
void display_offset(int set_xy[2], int *get_x, int *get_y);
-int display_orientation(int degrees);
-int display_backlight(int val);
void display_fade(int start, int end, int delay);
// helper for locating a substring in buffer with utf-8 string
void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off,
int char_len, const char **out_start, int *out_len);
-// pixeldata accessors
-void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
-void display_pixeldata(uint16_t c);
+// pixeldata accessor
void display_pixeldata_dirty();
-#if !(defined EMULATOR) && (defined TREZOR_MODEL_T)
-extern volatile uint8_t *const DISPLAY_CMD_ADDRESS;
-extern volatile uint8_t *const DISPLAY_DATA_ADDRESS;
-#endif
-
#endif
diff --git a/core/embed/extmod/modtrezorui/display_defs.h b/core/embed/extmod/modtrezorui/display_defs.h
new file mode 100644
index 0000000000..dadb339bc3
--- /dev/null
+++ b/core/embed/extmod/modtrezorui/display_defs.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _DISPLAY_DEFS_H
+#define _DISPLAY_DEFS_H
+
+#if defined TREZOR_MODEL_T
+
+// ILI9341V, GC9307 and ST7789V drivers support 240px x 320px display resolution
+#define MAX_DISPLAY_RESX 240
+#define MAX_DISPLAY_RESY 320
+#define DISPLAY_RESX 240
+#define DISPLAY_RESY 240
+#define TREZOR_FONT_BPP 4
+
+#elif defined TREZOR_MODEL_1
+
+#define MAX_DISPLAY_RESX 128
+#define MAX_DISPLAY_RESY 64
+#define DISPLAY_RESX 128
+#define DISPLAY_RESY 64
+#define TREZOR_FONT_BPP 1
+
+#elif defined TREZOR_MODEL_R
+
+#define MAX_DISPLAY_RESX 128
+#define MAX_DISPLAY_RESY 128
+#define DISPLAY_RESX 128
+#define DISPLAY_RESY 128
+#define TREZOR_FONT_BPP 1
+
+#else
+#error Unknown Trezor model
+#endif
+
+#endif //_DISPLAY_DEFS_H
diff --git a/core/embed/extmod/modtrezorui/display_interface.h b/core/embed/extmod/modtrezorui/display_interface.h
new file mode 100644
index 0000000000..bd0a804df4
--- /dev/null
+++ b/core/embed/extmod/modtrezorui/display_interface.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _DISPLAY_INTERFACE_H
+#define _DISPLAY_INTERFACE_H
+
+#include
+#include "common.h"
+
+#if !defined TREZOR_EMULATOR
+#include STM32_HAL_H
+#endif
+
+#if (defined TREZOR_MODEL_T) && !(defined TREZOR_EMULATOR)
+
+extern __IO uint8_t *const DISPLAY_CMD_ADDRESS;
+extern __IO uint8_t *const DISPLAY_DATA_ADDRESS;
+
+#define CMD(X) (*DISPLAY_CMD_ADDRESS = (X))
+#define DATA(X) (*DISPLAY_DATA_ADDRESS = (X))
+#define PIXELDATA(X) \
+ DATA((X)&0xFF); \
+ DATA((X) >> 8)
+
+#else
+#define PIXELDATA(c) display_pixeldata(c)
+#endif
+
+#ifdef TREZOR_EMULATOR
+extern uint8_t *const DISPLAY_DATA_ADDRESS;
+#endif
+
+void display_pixeldata(uint16_t c);
+
+#if (defined TREZOR_MODEL_1) && !(defined TREZOR_EMULATOR)
+void PIXELDATA_DIRTY();
+#else
+// noop
+#define PIXELDATA_DIRTY()
+#endif
+
+void display_reset_state();
+
+void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
+int display_orientation(int degrees);
+int display_get_orientation(void);
+int display_backlight(int val);
+
+void display_init(void);
+void display_refresh(void);
+const char *display_save(const char *prefix);
+void display_clear_save(void);
+
+#ifdef TREZOR_MODEL_T
+void display_set_little_endian(void);
+void display_set_big_endian(void);
+#endif
+
+#endif //_DISPLAY_INTERFACE_H
diff --git a/core/embed/extmod/modtrezorui/modtrezorui-display.h b/core/embed/extmod/modtrezorui/modtrezorui-display.h
index 0fb03c65ea..9625b59d89 100644
--- a/core/embed/extmod/modtrezorui/modtrezorui-display.h
+++ b/core/embed/extmod/modtrezorui/modtrezorui-display.h
@@ -29,6 +29,10 @@
/// FONT_MONO: int # id of monospace font
/// FONT_NORMAL: int # id of normal-width font
/// FONT_BOLD: int # id of bold-width font
+/// TOIF_FULL_COLOR_BE: int # full color big endian TOI image
+/// TOIF_FULL_COLOR_LE: int # full color little endian TOI image
+/// TOIF_GRAYSCALE_EH: int # grayscale even high TOI image
+/// TOIF_FULL_COLOR_BE: int # grayscale odd high TOI image
///
typedef struct _mp_obj_Display_t {
mp_obj_base_t base;
@@ -117,11 +121,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_bar_radius_obj,
8, 8,
mod_trezorui_Display_bar_radius);
-/// def toif_info(self, image: bytes) -> tuple[int, int, bool]:
+/// def toif_info(self, image: bytes) -> tuple[int, int, int]:
/// """
/// Returns tuple containing TOIF image dimensions: width, height, and
-/// whether it is grayscale.
-/// Raises an exception for corrupted images.
+/// format Raises an exception for corrupted images.
/// """
STATIC mp_obj_t mod_trezorui_Display_toif_info(mp_obj_t self, mp_obj_t image) {
mp_buffer_info_t buffer = {0};
@@ -129,8 +132,8 @@ STATIC mp_obj_t mod_trezorui_Display_toif_info(mp_obj_t self, mp_obj_t image) {
uint16_t w = 0;
uint16_t h = 0;
- bool grayscale = false;
- bool valid = display_toif_info(buffer.buf, buffer.len, &w, &h, &grayscale);
+ toif_format_t format = TOIF_FULL_COLOR_BE;
+ bool valid = display_toif_info(buffer.buf, buffer.len, &w, &h, &format);
if (!valid) {
mp_raise_ValueError("Invalid image format");
@@ -138,7 +141,7 @@ STATIC mp_obj_t mod_trezorui_Display_toif_info(mp_obj_t self, mp_obj_t image) {
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL));
tuple->items[0] = MP_OBJ_NEW_SMALL_INT(w);
tuple->items[1] = MP_OBJ_NEW_SMALL_INT(h);
- tuple->items[2] = mp_obj_new_bool(grayscale);
+ tuple->items[2] = MP_OBJ_NEW_SMALL_INT(format);
return MP_OBJ_FROM_PTR(tuple);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_toif_info_obj,
@@ -160,9 +163,9 @@ STATIC mp_obj_t mod_trezorui_Display_image(size_t n_args,
uint16_t w = 0;
uint16_t h = 0;
- bool grayscale = false;
- bool valid = display_toif_info(data, image.len, &w, &h, &grayscale);
- if (!valid || grayscale) {
+ toif_format_t format = TOIF_FULL_COLOR_BE;
+ bool valid = display_toif_info(data, image.len, &w, &h, &format);
+ if (!valid || format != TOIF_FULL_COLOR_LE) {
mp_raise_ValueError("Invalid image format");
}
display_image(x, y, w, h, data + 12, image.len - 12);
@@ -190,9 +193,9 @@ STATIC mp_obj_t mod_trezorui_Display_avatar(size_t n_args,
uint16_t w = 0;
uint16_t h = 0;
- bool grayscale = false;
- bool valid = display_toif_info(data, image.len, &w, &h, &grayscale);
- if (!valid || grayscale) {
+ toif_format_t format = TOIF_FULL_COLOR_BE;
+ bool valid = display_toif_info(data, image.len, &w, &h, &format);
+ if (!valid || format != TOIF_FULL_COLOR_BE) {
mp_raise_ValueError("Invalid image format");
}
if (w != AVATAR_IMAGE_SIZE || h != AVATAR_IMAGE_SIZE) {
@@ -223,10 +226,10 @@ STATIC mp_obj_t mod_trezorui_Display_icon(size_t n_args, const mp_obj_t *args) {
uint16_t w = 0;
uint16_t h = 0;
- bool grayscale = false;
- bool valid = display_toif_info(data, icon.len, &w, &h, &grayscale);
- if (!valid || !grayscale) {
- mp_raise_ValueError("Invalid image format");
+ toif_format_t format = TOIF_FULL_COLOR_BE;
+ bool valid = display_toif_info(data, icon.len, &w, &h, &format);
+ if (!valid || format != TOIF_GRAYSCALE_EH) {
+ mp_raise_ValueError("Invalid icon format");
}
mp_int_t fgcolor = mp_obj_get_int(args[4]);
mp_int_t bgcolor = mp_obj_get_int(args[5]);
@@ -265,7 +268,7 @@ STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args,
mp_buffer_info_t icon = {0};
mp_get_buffer_raise(args[6], &icon, MP_BUFFER_READ);
const uint8_t *data = icon.buf;
- if (icon.len < 8 || memcmp(data, "TOIg", 4) != 0) {
+ if (icon.len < 8 || memcmp(data, "TOIG", 4) != 0) {
mp_raise_ValueError("Invalid image format");
}
mp_int_t w = *(uint16_t *)(data + 4);
@@ -642,6 +645,10 @@ STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR_FONT_NORMAL), MP_ROM_INT(FONT_NORMAL)},
{MP_ROM_QSTR(MP_QSTR_FONT_BOLD), MP_ROM_INT(FONT_BOLD)},
{MP_ROM_QSTR(MP_QSTR_FONT_MONO), MP_ROM_INT(FONT_MONO)},
+ {MP_ROM_QSTR(MP_QSTR_TOIF_FULL_COLOR_BE), MP_ROM_INT(TOIF_FULL_COLOR_BE)},
+ {MP_ROM_QSTR(MP_QSTR_TOIF_FULL_COLOR_LE), MP_ROM_INT(TOIF_FULL_COLOR_LE)},
+ {MP_ROM_QSTR(MP_QSTR_TOIF_GRAYSCALE_EH), MP_ROM_INT(TOIF_GRAYSCALE_EH)},
+ {MP_ROM_QSTR(MP_QSTR_TOIF_GRAYSCALE_OH), MP_ROM_INT(TOIF_GRAYSCALE_OH)},
};
STATIC MP_DEFINE_CONST_DICT(mod_trezorui_Display_locals_dict,
mod_trezorui_Display_locals_dict_table);
diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c
index 1a63c7a41d..2977b62068 100644
--- a/core/embed/firmware/main.c
+++ b/core/embed/firmware/main.c
@@ -47,6 +47,9 @@
#ifdef TREZOR_MODEL_R
#include "rgb_led.h"
#endif
+#ifdef TREZOR_MODEL_T
+#include "dma2d.h"
+#endif
#if defined TREZOR_MODEL_R || defined TREZOR_MODEL_1
#include "button.h"
#endif
@@ -96,6 +99,10 @@ int main(void) {
// Init peripherals
pendsv_init();
+#ifdef USE_DMA2D
+ dma2d_init();
+#endif
+
#if !PRODUCTION
// enable BUS fault and USAGE fault handlers
SCB->SHCSR |= (SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk);
@@ -103,6 +110,7 @@ int main(void) {
#if defined TREZOR_MODEL_1
display_init();
+ display_clear();
button_init();
#endif
@@ -114,7 +122,7 @@ int main(void) {
#if defined TREZOR_MODEL_T
touch_init();
- // display_init_seq();
+ display_set_little_endian();
sdcard_init();
display_clear();
#endif
@@ -230,6 +238,7 @@ void SVC_C_Handler(uint32_t *stack) {
;
break;
case SVC_REBOOT_TO_BOOTLOADER:
+ ensure_compatible_settings();
mpu_config_bootloader();
__asm__ volatile("msr control, %0" ::"r"(0x0));
__asm__ volatile("isb");
diff --git a/core/embed/rust/Cargo.lock b/core/embed/rust/Cargo.lock
index abcb1132b2..27965aa7e3 100644
--- a/core/embed/rust/Cargo.lock
+++ b/core/embed/rust/Cargo.lock
@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
[[package]]
name = "bindgen"
version = "0.60.1"
@@ -166,6 +172,26 @@ dependencies = [
"minimal-lexical",
]
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@@ -238,6 +264,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+[[package]]
+name = "syn"
+version = "1.0.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
[[package]]
name = "trezor_lib"
version = "0.1.0"
@@ -248,6 +285,8 @@ dependencies = [
"cty",
"glob",
"heapless",
+ "num-derive",
+ "num-traits",
]
[[package]]
diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml
index 1f6cdd4b57..2bf223023d 100644
--- a/core/embed/rust/Cargo.toml
+++ b/core/embed/rust/Cargo.toml
@@ -14,6 +14,7 @@ model_tr = ["buttons"]
micropython = []
protobuf = ["micropython"]
ui = []
+dma2d = []
ui_debug = []
buttons = []
touch = []
@@ -39,6 +40,7 @@ debug = 2
[profile.test]
split-debuginfo = "off"
+debug = 2
# Runtime dependencies
@@ -49,6 +51,13 @@ version = "0.2.2"
version = "0.7.3"
default_features = false
+[dependencies.num-traits]
+version = "0.2.15"
+default_features = false
+
+[dependencies.num-derive]
+version = "0.3.3"
+
[dependencies.cstr_core]
version = "0.2.4"
default_features = false
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index fe003f1699..c8993cec49 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -105,6 +105,7 @@ fn prepare_bindings() -> bindgen::Builder {
"-I../unix",
"-I../../build/unix",
"-I../../vendor/micropython/ports/unix",
+ "-DTREZOR_EMULATOR",
]);
}
@@ -258,6 +259,7 @@ fn generate_trezorhal_bindings() {
.allowlist_function("display_refresh")
.allowlist_function("display_backlight")
.allowlist_function("display_text")
+ .allowlist_function("display_text_render_buffer")
.allowlist_function("display_text_width")
.allowlist_function("display_bar")
.allowlist_function("display_bar_radius")
@@ -270,8 +272,11 @@ fn generate_trezorhal_bindings() {
.allowlist_function("display_set_window")
.allowlist_var("DISPLAY_CMD_ADDRESS")
.allowlist_var("DISPLAY_DATA_ADDRESS")
+ .allowlist_type("toif_format_t")
// fonts
.allowlist_function("font_height")
+ .allowlist_function("font_max_height")
+ .allowlist_function("font_baseline")
.allowlist_function("font_get_glyph")
// uzlib
.allowlist_function("uzlib_uncompress_init")
@@ -289,8 +294,18 @@ fn generate_trezorhal_bindings() {
.allowlist_function("rgb_led_set_color")
// time
.allowlist_function("hal_delay")
- .allowlist_function("hal_ticks_ms");
-
+ .allowlist_function("hal_ticks_ms")
+ // dma2d
+ .allowlist_function("dma2d_setup_4bpp_over_4bpp")
+ .allowlist_function("dma2d_setup_4bpp_over_16bpp")
+ .allowlist_function("dma2d_start_blend")
+ .allowlist_function("dma2d_wait_for_transfer")
+ //buffers
+ .allowlist_function("buffers_get_line_buffer_16bpp")
+ .allowlist_function("buffers_get_line_buffer_4bpp")
+ .allowlist_function("buffers_get_text_buffer")
+ .allowlist_var("text_buffer_height")
+ .allowlist_var("buffer_width");
// Write the bindings to a file in the OUR_DIR.
bindings
.generate()
diff --git a/core/embed/rust/rust_ui.h b/core/embed/rust/rust_ui.h
new file mode 100644
index 0000000000..2bda41a2ae
--- /dev/null
+++ b/core/embed/rust/rust_ui.h
@@ -0,0 +1,6 @@
+#include "common.h"
+
+void loader_uncompress_r(int32_t y_offset, uint16_t fg_color, uint16_t bg_color,
+ uint16_t icon_color, int32_t progress,
+ int32_t indeterminate, const uint8_t* icon_data,
+ uint32_t icon_data_size);
diff --git a/core/embed/rust/src/lib.rs b/core/embed/rust/src/lib.rs
index 2f63df12b4..396e09fa41 100644
--- a/core/embed/rust/src/lib.rs
+++ b/core/embed/rust/src/lib.rs
@@ -4,6 +4,9 @@
#![deny(unsafe_op_in_unsafe_fn)]
#![allow(dead_code)]
+#[macro_use]
+extern crate num_derive;
+
mod error;
// use trezorhal for its macros early
#[macro_use]
diff --git a/core/embed/rust/src/trezorhal/buffers.rs b/core/embed/rust/src/trezorhal/buffers.rs
new file mode 100644
index 0000000000..41879dac76
--- /dev/null
+++ b/core/embed/rust/src/trezorhal/buffers.rs
@@ -0,0 +1,45 @@
+use super::ffi;
+
+pub use ffi::{
+ buffer_text_t as BufferText, line_buffer_16bpp_t as LineBuffer16Bpp,
+ line_buffer_4bpp_t as LineBuffer4Bpp,
+};
+
+/// Returns a buffer for one line of 16bpp data
+///
+/// # Safety
+///
+/// This function is unsafe because the caller has to guarantee
+/// that he doesn't use buffer on same index multiple times
+pub unsafe fn get_buffer_16bpp(idx: u16, clear: bool) -> &'static mut LineBuffer16Bpp {
+ unsafe {
+ let ptr = ffi::buffers_get_line_buffer_16bpp(idx, clear);
+ unwrap!(ptr.as_mut())
+ }
+}
+
+/// Returns a buffer for one line of 4bpp data
+///
+/// # Safety
+///
+/// This function is unsafe because the caller has to guarantee
+/// that he doesn't use buffer on same index multiple times
+pub unsafe fn get_buffer_4bpp(idx: u16, clear: bool) -> &'static mut LineBuffer4Bpp {
+ unsafe {
+ let ptr = ffi::buffers_get_line_buffer_4bpp(idx, clear);
+ unwrap!(ptr.as_mut())
+ }
+}
+
+/// Returns a buffer for one line of text
+///
+/// # Safety
+///
+/// This function is unsafe because the caller has to guarantee
+/// that he doesn't use buffer on same index multiple times
+pub unsafe fn get_text_buffer(idx: u16, clear: bool) -> &'static mut BufferText {
+ unsafe {
+ let ptr = ffi::buffers_get_text_buffer(idx, clear);
+ unwrap!(ptr.as_mut())
+ }
+}
diff --git a/core/embed/rust/src/trezorhal/display.rs b/core/embed/rust/src/trezorhal/display.rs
index 78b810d778..d23b603b1c 100644
--- a/core/embed/rust/src/trezorhal/display.rs
+++ b/core/embed/rust/src/trezorhal/display.rs
@@ -1,11 +1,22 @@
use super::ffi;
use core::ptr;
use cty::c_int;
+use num_traits::FromPrimitive;
+
+use crate::trezorhal::buffers::BufferText;
+
+#[derive(PartialEq, Debug, Eq, FromPrimitive)]
+pub enum ToifFormat {
+ FullColorBE = ffi::toif_format_t_TOIF_FULL_COLOR_BE as _,
+ GrayScaleOH = ffi::toif_format_t_TOIF_GRAYSCALE_OH as _,
+ FullColorLE = ffi::toif_format_t_TOIF_FULL_COLOR_LE as _,
+ GrayScaleEH = ffi::toif_format_t_TOIF_GRAYSCALE_EH as _,
+}
pub struct ToifInfo {
pub width: u16,
pub height: u16,
- pub grayscale: bool,
+ pub format: ToifFormat,
}
pub fn backlight(val: i32) -> i32 {
@@ -26,6 +37,18 @@ pub fn text(baseline_x: i16, baseline_y: i16, text: &str, font: i32, fgcolor: u1
}
}
+pub fn text_into_buffer(text: &str, font: i32, buffer: &mut BufferText, x_offset: i16) {
+ unsafe {
+ ffi::display_text_render_buffer(
+ text.as_ptr() as _,
+ text.len() as _,
+ font,
+ buffer as _,
+ x_offset.into(),
+ )
+ }
+}
+
pub fn text_width(text: &str, font: i32) -> i16 {
unsafe {
ffi::display_text_width(text.as_ptr() as _, text.len() as _, font)
@@ -45,11 +68,15 @@ pub fn get_char_glyph(ch: u8, font: i32) -> *const u8 {
}
pub fn text_height(font: i32) -> i16 {
- unsafe {
- ffi::font_height(font)
- .try_into()
- .unwrap_or(i16::MAX)
- }
+ unsafe { ffi::font_height(font).try_into().unwrap_or(i16::MAX) }
+}
+
+pub fn text_max_height(font: i32) -> i16 {
+ unsafe { ffi::font_max_height(font).try_into().unwrap_or(i16::MAX) }
+}
+
+pub fn text_baseline(font: i32) -> i16 {
+ unsafe { ffi::font_baseline(font).try_into().unwrap_or(i16::MAX) }
}
pub fn bar(x: i16, y: i16, w: i16, h: i16, fgcolor: u16) {
@@ -101,20 +128,22 @@ pub fn image(x: i16, y: i16, w: i16, h: i16, data: &[u8]) {
pub fn toif_info(data: &[u8]) -> Result {
let mut width: cty::uint16_t = 0;
let mut height: cty::uint16_t = 0;
- let mut grayscale: bool = false;
+ let mut format: ffi::toif_format_t = ffi::toif_format_t_TOIF_FULL_COLOR_BE;
if unsafe {
ffi::display_toif_info(
data.as_ptr() as _,
data.len() as _,
&mut width,
&mut height,
- &mut grayscale,
+ &mut format,
)
} {
+ let format = ToifFormat::from_usize(format as usize).unwrap_or(ToifFormat::FullColorBE);
+
Ok(ToifInfo {
width,
height,
- grayscale,
+ format,
})
} else {
Err(())
@@ -148,8 +177,8 @@ pub fn loader(
#[cfg(all(feature = "model_tt", target_arch = "arm"))]
pub fn pixeldata(c: u16) {
unsafe {
- ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8);
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8);
+ ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8);
}
}
diff --git a/core/embed/rust/src/trezorhal/dma2d.rs b/core/embed/rust/src/trezorhal/dma2d.rs
new file mode 100644
index 0000000000..ed56e3eacc
--- /dev/null
+++ b/core/embed/rust/src/trezorhal/dma2d.rs
@@ -0,0 +1,26 @@
+use super::ffi;
+
+pub fn dma2d_setup_4bpp_over_4bpp(fg_color: u16, bg_color: u16, overlay_color: u16) {
+ unsafe { ffi::dma2d_setup_4bpp_over_4bpp(fg_color, bg_color, overlay_color) }
+}
+
+pub fn dma2d_setup_4bpp_over_16bpp(overlay_color: u16) {
+ unsafe { ffi::dma2d_setup_4bpp_over_16bpp(overlay_color) }
+}
+
+pub fn dma2d_start_blend(overlay_buffer: &[u8], bg_buffer: &[u8], pixels: i16) {
+ unsafe {
+ ffi::dma2d_start_blend(
+ overlay_buffer.as_ptr() as _,
+ bg_buffer.as_ptr() as _,
+ ffi::DISPLAY_DATA_ADDRESS as _,
+ pixels as _,
+ );
+ }
+}
+
+pub fn dma2d_wait_for_transfer() {
+ unsafe {
+ ffi::dma2d_wait_for_transfer();
+ }
+}
diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs
index ee23c39970..560dfb7186 100644
--- a/core/embed/rust/src/trezorhal/mod.rs
+++ b/core/embed/rust/src/trezorhal/mod.rs
@@ -4,6 +4,8 @@ pub mod bip39;
pub mod common;
#[cfg(feature = "ui")]
pub mod display;
+#[cfg(feature = "dma2d")]
+pub mod dma2d;
mod ffi;
pub mod qr;
pub mod random;
@@ -12,7 +14,9 @@ pub mod rgb_led;
pub mod slip39;
pub mod uzlib;
+pub mod buffers;
#[cfg(not(feature = "micropython"))]
pub mod time;
+
#[cfg(feature = "micropython")]
pub use crate::micropython::time;
diff --git a/core/embed/rust/src/ui/constant.rs b/core/embed/rust/src/ui/constant.rs
index d329c74bb3..2216fffe64 100644
--- a/core/embed/rust/src/ui/constant.rs
+++ b/core/embed/rust/src/ui/constant.rs
@@ -1,13 +1,13 @@
//! Reexporting the `constant` module according to the
//! current feature (Trezor model)
-#[cfg(feature = "model_t1")]
-pub use super::model_t1::constant::*;
-#[cfg(all(feature = "model_tr", not(feature = "model_t1")))]
-pub use super::model_tr::constant::*;
#[cfg(all(
- feature = "model_tt",
+ feature = "model_t1",
not(feature = "model_tr"),
- not(feature = "model_t1")
+ not(feature = "model_tt")
))]
+pub use super::model_t1::constant::*;
+#[cfg(all(feature = "model_tr", not(feature = "model_tt")))]
+pub use super::model_tr::constant::*;
+#[cfg(feature = "model_tt")]
pub use super::model_tt::constant::*;
diff --git a/core/embed/rust/src/ui/display/loader.rs b/core/embed/rust/src/ui/display/loader.rs
new file mode 100644
index 0000000000..104527d763
--- /dev/null
+++ b/core/embed/rust/src/ui/display/loader.rs
@@ -0,0 +1,396 @@
+use crate::{
+ trezorhal::uzlib::UzlibContext,
+ ui::{
+ constant, display,
+ display::{Color, ToifFormat},
+ geometry::{Offset, Point, Rect},
+ },
+};
+use core::slice::from_raw_parts;
+
+#[cfg(feature = "dma2d")]
+use crate::trezorhal::{
+ buffers::{get_buffer_16bpp, get_buffer_4bpp},
+ dma2d::{dma2d_setup_4bpp_over_4bpp, dma2d_start_blend, dma2d_wait_for_transfer},
+};
+
+use crate::ui::{
+ constant::{screen, LOADER_OUTER},
+ display::toif_info_ensure,
+};
+
+pub const LOADER_MIN: u16 = 0;
+pub const LOADER_MAX: u16 = 1000;
+
+const LOADER_SIZE: i32 = (LOADER_OUTER * 2.0) as i32;
+
+const OUTER: f32 = constant::LOADER_OUTER;
+const INNER: f32 = constant::LOADER_INNER;
+const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE;
+
+const IN_INNER_ANTI: i32 = ((INNER - 0.5) * (INNER - 0.5)) as i32;
+const INNER_MIN: i32 = ((INNER + 0.5) * (INNER + 0.5)) as i32;
+const INNER_MAX: i32 = ((INNER + 1.5) * (INNER + 1.5)) as i32;
+const INNER_OUTER_ANTI: i32 = ((INNER + 2.5) * (INNER + 2.5)) as i32;
+const OUTER_OUT_ANTI: i32 = ((OUTER - 1.5) * (OUTER - 1.5)) as i32;
+const OUTER_MAX: i32 = ((OUTER - 0.5) * (OUTER - 0.5)) as i32;
+
+pub fn loader_uncompress(
+ y_offset: i16,
+ fg_color: Color,
+ bg_color: Color,
+ progress: u16,
+ indeterminate: bool,
+ icon: Option<(&[u8], Color)>,
+) {
+ const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE;
+
+ if let Some((data, color)) = icon {
+ let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH);
+ if toif_size.x <= ICON_MAX_SIZE && toif_size.y <= ICON_MAX_SIZE {
+ let mut icon_data = [0_u8; ((ICON_MAX_SIZE * ICON_MAX_SIZE) / 2) as usize];
+ let mut ctx = UzlibContext::new(toif_data, None);
+ unwrap!(ctx.uncompress(&mut icon_data), "Decompression failed");
+ let i = Some((icon_data.as_ref(), color, toif_size));
+ loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, i);
+ } else {
+ loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None);
+ }
+ } else {
+ loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn loader_uncompress_r(
+ y_offset: cty::int32_t,
+ fg_color: cty::uint16_t,
+ bg_color: cty::uint16_t,
+ icon_color: cty::uint16_t,
+ progress: cty::int32_t,
+ indeterminate: cty::int32_t,
+ icon_data: cty::uintptr_t,
+ icon_data_size: cty::uint32_t,
+) {
+ let fg = Color::from_u16(fg_color);
+ let bg = Color::from_u16(bg_color);
+ let ic_color = Color::from_u16(icon_color);
+
+ let i = if icon_data != 0 {
+ let data_slice = unsafe { from_raw_parts(icon_data as _, icon_data_size as _) };
+ Some((data_slice, ic_color))
+ } else {
+ None
+ };
+
+ loader_uncompress(y_offset as _, fg, bg, progress as _, indeterminate != 0, i);
+}
+
+#[inline(always)]
+fn get_loader_vectors(indeterminate: bool, progress: u16) -> (Point, Point) {
+ let (start_progress, end_progress) = if indeterminate {
+ const LOADER_INDETERMINATE_WIDTH: u16 = 100;
+ (
+ progress - LOADER_INDETERMINATE_WIDTH,
+ progress + LOADER_INDETERMINATE_WIDTH,
+ )
+ } else {
+ (0, progress)
+ };
+
+ let start = ((360 * start_progress as i32) / 1000) % 360;
+ let end = ((360 * end_progress as i32) / 1000) % 360;
+
+ let start_vector;
+ let end_vector;
+
+ if indeterminate {
+ start_vector = display::get_vector(start as _);
+ end_vector = display::get_vector(end as _);
+ } else if progress >= 1000 {
+ start_vector = Point::zero();
+ end_vector = Point::zero();
+ } else if progress > 500 {
+ start_vector = display::get_vector(end as _);
+ end_vector = display::get_vector(start as _);
+ } else {
+ start_vector = display::get_vector(start as _);
+ end_vector = display::get_vector(end as _);
+ }
+
+ (start_vector, end_vector)
+}
+
+#[inline(always)]
+fn loader_get_pixel_color_idx(
+ show_all: bool,
+ inverted: bool,
+ end_vector: Point,
+ n_start: Point,
+ x_c: i16,
+ y_c: i16,
+ center: Point,
+) -> u8 {
+ let y_p = -(y_c - center.y);
+ let x_p = x_c - center.x;
+
+ let vx = Point::new(x_p, y_p);
+ let n_vx = Point::new(-y_p, x_p);
+
+ let d = y_p as i32 * y_p as i32 + x_p as i32 * x_p as i32;
+
+ let included = if inverted {
+ !display::is_clockwise_or_equal(n_start, vx)
+ || !display::is_clockwise_or_equal_inc(n_vx, end_vector)
+ } else {
+ display::is_clockwise_or_equal(n_start, vx)
+ && display::is_clockwise_or_equal_inc(n_vx, end_vector)
+ };
+
+ // The antialiasing calculation below uses simplified distance difference
+ // calculation. Optimally, SQRT should be used, but assuming
+ // diameter large enough and antialiasing over distance
+ // r_outer-r_inner = 1, the difference between simplified:
+ // (d^2-r_inner^2)/(r_outer^2-r_inner^2) and precise: (sqrt(d^2)
+ // - r_inner)/(r_outer-r_inner) is negligible
+ if show_all || included {
+ //active part
+ if d <= IN_INNER_ANTI {
+ 0
+ } else if d <= INNER_MIN {
+ ((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8
+ } else if d <= OUTER_OUT_ANTI {
+ 15
+ } else if d <= OUTER_MAX {
+ (15 - ((15 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI))) as u8
+ } else {
+ 0
+ }
+ } else {
+ //inactive part
+ if d <= IN_INNER_ANTI {
+ 0
+ } else if d <= INNER_MIN {
+ ((15 * (d - IN_INNER_ANTI)) / (INNER_MIN - IN_INNER_ANTI)) as u8
+ } else if d <= INNER_MAX {
+ 15
+ } else if d <= INNER_OUTER_ANTI {
+ (15 - ((10 * (d - INNER_MAX)) / (INNER_OUTER_ANTI - INNER_MAX))) as u8
+ } else if d <= OUTER_OUT_ANTI {
+ 5
+ } else if d <= OUTER_MAX {
+ 5 - ((5 * (d - OUTER_OUT_ANTI)) / (OUTER_MAX - OUTER_OUT_ANTI)) as u8
+ } else {
+ 0
+ }
+ }
+}
+
+#[cfg(not(feature = "dma2d"))]
+pub fn loader_rust(
+ y_offset: i16,
+ fg_color: Color,
+ bg_color: Color,
+ progress: u16,
+ indeterminate: bool,
+ icon: Option<(&[u8], Color, Offset)>,
+) {
+ let center = screen().center() + Offset::new(0, y_offset);
+ let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2));
+ let clamped = r.clamp(constant::screen());
+ display::set_window(clamped);
+
+ let center = r.center();
+
+ let colortable = display::get_color_table(fg_color, bg_color);
+ let mut icon_colortable = colortable;
+
+ let mut use_icon = false;
+ let mut icon_area = Rect::zero();
+ let mut icon_area_clamped = Rect::zero();
+ let mut icon_width = 0;
+ let mut icon_data = [].as_ref();
+
+ if let Some((data, color, size)) = icon {
+ if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE {
+ icon_width = size.x;
+ icon_area = Rect::from_center_and_size(center, size);
+ icon_area_clamped = icon_area.clamp(constant::screen());
+ icon_data = data;
+ use_icon = true;
+ icon_colortable = display::get_color_table(color, bg_color);
+ }
+ }
+
+ let show_all = !indeterminate && progress >= 1000;
+ let inverted = !indeterminate && progress > 500;
+ let (start_vector, end_vector) = get_loader_vectors(indeterminate, progress);
+
+ let n_start = Point::new(-start_vector.y, start_vector.x);
+
+ for y_c in r.y0..r.y1 {
+ for x_c in r.x0..r.x1 {
+ let p = Point::new(x_c, y_c);
+ let mut icon_pixel = false;
+
+ let mut underlying_color = bg_color;
+
+ if use_icon && icon_area_clamped.contains(p) {
+ let x = x_c - center.x;
+ let y = y_c - center.y;
+ if (x as i32 * x as i32 + y as i32 * y as i32) <= IN_INNER_ANTI {
+ let x_i = x_c - icon_area.x0;
+ let y_i = y_c - icon_area.y0;
+
+ let data = icon_data[(((x_i & 0xFE) + (y_i * icon_width)) / 2) as usize];
+ if (x_i & 0x01) == 0 {
+ underlying_color = icon_colortable[(data & 0xF) as usize];
+ } else {
+ underlying_color = icon_colortable[(data >> 4) as usize];
+ }
+ icon_pixel = true;
+ }
+ }
+
+ if clamped.contains(p) && !icon_pixel {
+ let pix_c_idx = loader_get_pixel_color_idx(
+ show_all, inverted, end_vector, n_start, x_c, y_c, center,
+ );
+ underlying_color = colortable[pix_c_idx as usize];
+ }
+
+ display::pixeldata(underlying_color);
+ }
+ }
+
+ display::pixeldata_dirty();
+}
+
+#[cfg(feature = "dma2d")]
+pub fn loader_rust(
+ y_offset: i16,
+ fg_color: Color,
+ bg_color: Color,
+ progress: u16,
+ indeterminate: bool,
+ icon: Option<(&[u8], Color, Offset)>,
+) {
+ let center = screen().center() + Offset::new(0, y_offset);
+ let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2));
+ let clamped = r.clamp(constant::screen());
+ display::set_window(clamped);
+
+ let center = r.center();
+
+ let mut use_icon = false;
+ let mut icon_area = Rect::zero();
+ let mut icon_area_clamped = Rect::zero();
+ let mut icon_width = 0;
+ let mut icon_offset = 0;
+ let mut icon_color = Color::from_u16(0);
+ let mut icon_data = [].as_ref();
+
+ if let Some((data, color, size)) = icon {
+ if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE {
+ icon_width = size.x;
+ icon_area = Rect::from_center_and_size(center, size);
+ icon_area_clamped = icon_area.clamp(constant::screen());
+ icon_offset = (icon_area_clamped.x0 - r.x0) / 2;
+ icon_color = color;
+ icon_data = data;
+ use_icon = true;
+ }
+ }
+
+ let show_all = !indeterminate && progress >= 1000;
+ let inverted = !indeterminate && progress > 500;
+ let (start_vector, end_vector) = get_loader_vectors(indeterminate, progress);
+
+ let n_start = Point::new(-start_vector.y, start_vector.x);
+
+ let b1 = unsafe { get_buffer_16bpp(0, false) };
+ let b2 = unsafe { get_buffer_16bpp(1, false) };
+ let ib1 = unsafe { get_buffer_4bpp(0, true) };
+ let ib2 = unsafe { get_buffer_4bpp(1, true) };
+ let empty_line = unsafe { get_buffer_4bpp(2, true) };
+
+ dma2d_setup_4bpp_over_4bpp(fg_color.into(), bg_color.into(), icon_color.into());
+
+ for y_c in r.y0..r.y1 {
+ let mut icon_buffer = &mut *empty_line;
+ let icon_buffer_used;
+ let loader_buffer;
+
+ if y_c % 2 == 0 {
+ icon_buffer_used = &mut *ib1;
+ loader_buffer = &mut *b1;
+ } else {
+ icon_buffer_used = &mut *ib2;
+ loader_buffer = &mut *b2;
+ }
+
+ if use_icon && y_c >= icon_area_clamped.y0 && y_c < icon_area_clamped.y1 {
+ let y_i = y_c - icon_area.y0;
+
+ // Optimally, we should cut corners of the icon if it happens to be large enough
+ // to invade loader area. but this would require calculation of circle chord
+ // length (since we need to limit data copied to the buffer),
+ // which requires expensive SQRT. Therefore, when using this method of loader
+ // drawing, special care needs to be taken to ensure that the icons
+ // have transparent corners.
+
+ icon_buffer_used.buffer[icon_offset as usize..(icon_offset + icon_width / 2) as usize]
+ .copy_from_slice(
+ &icon_data[(y_i * (icon_width / 2)) as usize
+ ..((y_i + 1) * (icon_width / 2)) as usize],
+ );
+ icon_buffer = icon_buffer_used;
+ }
+
+ let mut pix_c_idx_prev: u8 = 0;
+
+ for x_c in r.x0..r.x1 {
+ let p = Point::new(x_c, y_c);
+
+ let pix_c_idx = if clamped.contains(p) {
+ loader_get_pixel_color_idx(
+ show_all, inverted, end_vector, n_start, x_c, y_c, center,
+ )
+ } else {
+ 0
+ };
+
+ let x = x_c - r.x0;
+ if x % 2 == 0 {
+ pix_c_idx_prev = pix_c_idx;
+ } else {
+ loader_buffer.buffer[(x >> 1) as usize] = pix_c_idx_prev | pix_c_idx << 4;
+ }
+ }
+
+ dma2d_wait_for_transfer();
+ dma2d_start_blend(&icon_buffer.buffer, &loader_buffer.buffer, clamped.width());
+ }
+
+ dma2d_wait_for_transfer();
+}
+
+pub fn loader(
+ progress: u16,
+ y_offset: i16,
+ fg_color: Color,
+ bg_color: Color,
+ icon: Option<(&[u8], Color)>,
+) {
+ loader_uncompress(y_offset, fg_color, bg_color, progress, false, icon);
+}
+
+pub fn loader_indeterminate(
+ progress: u16,
+ y_offset: i16,
+ fg_color: Color,
+ bg_color: Color,
+ icon: Option<(&[u8], Color)>,
+) {
+ loader_uncompress(y_offset, fg_color, bg_color, progress, true, icon);
+}
diff --git a/core/embed/rust/src/ui/display.rs b/core/embed/rust/src/ui/display/mod.rs
similarity index 62%
rename from core/embed/rust/src/ui/display.rs
rename to core/embed/rust/src/ui/display/mod.rs
index f8cf364017..02c56cccf0 100644
--- a/core/embed/rust/src/ui/display.rs
+++ b/core/embed/rust/src/ui/display/mod.rs
@@ -1,18 +1,34 @@
+#[cfg(any(feature = "model_tt", feature = "model_tr"))]
+pub mod loader;
+
use super::{
constant,
geometry::{Offset, Point, Rect},
};
+#[cfg(feature = "dma2d")]
+use crate::trezorhal::{
+ buffers::{get_buffer_16bpp, get_buffer_4bpp, get_text_buffer},
+ dma2d::{
+ dma2d_setup_4bpp_over_16bpp, dma2d_setup_4bpp_over_4bpp, dma2d_start_blend,
+ dma2d_wait_for_transfer,
+ },
+};
use crate::{
error::Error,
time::Duration,
trezorhal::{
- display, qr, time,
+ display,
+ display::ToifFormat,
+ qr, time,
uzlib::{UzlibContext, UZLIB_WINDOW_SIZE},
},
ui::lerp::Lerp,
};
use core::slice;
+#[cfg(any(feature = "model_tt", feature = "model_tr"))]
+pub use loader::{loader, loader_indeterminate, LOADER_MAX, LOADER_MIN};
+
pub fn backlight() -> i32 {
display::backlight(-1)
}
@@ -66,7 +82,7 @@ pub fn rect_fill_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u8)
/// NOTE: Cannot start at odd x-coordinate. In this case icon is shifted 1px
/// left.
pub fn icon_top_left(top_left: Point, data: &[u8], fg_color: Color, bg_color: Color) {
- let (toif_size, toif_data) = toif_info_ensure(data, true);
+ let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH);
display::icon(
top_left.x,
top_left.y,
@@ -79,7 +95,7 @@ pub fn icon_top_left(top_left: Point, data: &[u8], fg_color: Color, bg_color: Co
}
pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
- let (toif_size, toif_data) = toif_info_ensure(data, true);
+ let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH);
let r = Rect::from_center_and_size(center, toif_size);
display::icon(
r.x0,
@@ -93,7 +109,7 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
}
pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
- let (toif_size, toif_data) = toif_info_ensure(data, true);
+ let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH);
let r = Rect::from_center_and_size(center, toif_size);
let area = r.translate(get_offset());
@@ -115,9 +131,9 @@ pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
if clamped.contains(p) {
if x % 2 == 0 {
unwrap!(ctx.uncompress(&mut dest), "Decompression failed");
- pixeldata(colortable[(dest[0] >> 4) as usize]);
- } else {
pixeldata(colortable[(dest[0] & 0xF) as usize]);
+ } else {
+ pixeldata(colortable[(dest[0] >> 4) as usize]);
}
} else if x % 2 == 0 {
//continue unzipping but dont write to display
@@ -130,20 +146,20 @@ pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
}
pub fn image(center: Point, data: &[u8]) {
- let (toif_size, toif_data) = toif_info_ensure(data, false);
+ let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::FullColorLE);
let r = Rect::from_center_and_size(center, toif_size);
display::image(r.x0, r.y0, r.width(), r.height(), toif_data);
}
-pub fn toif_info(data: &[u8]) -> Option<(Offset, bool)> {
+pub fn toif_info(data: &[u8]) -> Option<(Offset, ToifFormat)> {
if let Ok(info) = display::toif_info(data) {
Some((
Offset::new(
unwrap!(info.width.try_into()),
unwrap!(info.height.try_into()),
),
- info.grayscale,
+ info.format,
))
} else {
None
@@ -152,9 +168,9 @@ pub fn toif_info(data: &[u8]) -> Option<(Offset, bool)> {
/// Aborts if the TOIF file does not have the correct grayscale flag, do not use
/// with user-supplied inputs.
-fn toif_info_ensure(data: &[u8], grayscale: bool) -> (Offset, &[u8]) {
+fn toif_info_ensure(data: &[u8], format: ToifFormat) -> (Offset, &[u8]) {
let info = unwrap!(display::toif_info(data), "Invalid TOIF data");
- assert_eq!(grayscale, info.grayscale);
+ assert_eq!(info.format, format);
let size = Offset::new(
unwrap!(info.width.try_into()),
unwrap!(info.height.try_into()),
@@ -266,7 +282,7 @@ fn get_vector(angle: i16) -> Point {
/// ( if v1=(x1,y1), then the counter-clockwise normal is n_v1=(-y1,x1)
#[inline(always)]
fn is_clockwise_or_equal(n_v1: Point, v2: Point) -> bool {
- let psize = v2.x * n_v1.x + v2.y * n_v1.y;
+ let psize = v2.x as i32 * n_v1.x as i32 + v2.y as i32 * n_v1.y as i32;
psize < 0
}
@@ -275,7 +291,7 @@ fn is_clockwise_or_equal(n_v1: Point, v2: Point) -> bool {
/// ( if v1=(x1,y1), then the counter-clockwise normal is n_v1=(-y1,x1)
#[inline(always)]
fn is_clockwise_or_equal_inc(n_v1: Point, v2: Point) -> bool {
- let psize = v2.x * n_v1.x + v2.y * n_v1.y;
+ let psize = v2.x as i32 * n_v1.x as i32 + v2.y as i32 * n_v1.y as i32;
psize <= 0
}
@@ -308,7 +324,7 @@ pub fn rect_rounded2_partial(
let mut icon_width = 0;
if let Some((icon_bytes, icon_color)) = icon {
- let (toif_size, toif_data) = toif_info_ensure(icon_bytes, true);
+ let (toif_size, toif_data) = toif_info_ensure(icon_bytes, ToifFormat::GrayScaleEH);
if toif_size.x <= MAX_ICON_SIZE && toif_size.y <= MAX_ICON_SIZE {
icon_area = Rect::from_center_and_size(center, toif_size);
@@ -357,9 +373,9 @@ pub fn rect_rounded2_partial(
let data = icon_data[(((x_i & 0xFE) + (y_i * icon_width)) / 2) as usize];
if (x_i & 0x01) == 0 {
- pixeldata(icon_colortable[(data >> 4) as usize]);
- } else {
pixeldata(icon_colortable[(data & 0xF) as usize]);
+ } else {
+ pixeldata(icon_colortable[(data > 4) as usize]);
}
icon_pixel = true;
}
@@ -393,6 +409,345 @@ pub fn rect_rounded2_partial(
pixeldata_dirty();
}
+/// Shifts position of pixel data in `src_buffer` horizontally by `offset_x`
+/// pixels and places the result into `dest_buffer`. Or in another words,
+/// `src_buffer[n]` is copied into `dest_buffer[n+offset_x]`, if it fits the
+/// `dest_buffer`.
+///
+/// Buffers hold one line of pixels on the screen, the copying is limited to
+/// respect the size of screen.
+///
+/// `buffer_bpp` determines size of pixel data
+/// `data_width` sets the width of valid data in the `src_buffer`
+fn position_buffer(
+ dest_buffer: &mut [u8],
+ src_buffer: &[u8],
+ buffer_bpp: usize,
+ offset_x: i16,
+ data_width: i16,
+) {
+ let start: usize = (offset_x).clamp(0, constant::WIDTH) as usize;
+ let end: usize = (offset_x + data_width).clamp(0, constant::WIDTH) as usize;
+ let width = end - start;
+ // if the offset is negative, need to skip beginning of uncompressed data
+ let x_sh = if offset_x < 0 {
+ (-offset_x).clamp(0, constant::WIDTH - width as i16) as usize
+ } else {
+ 0
+ };
+ dest_buffer[((start * buffer_bpp) / 8)..((start + width) * buffer_bpp) / 8].copy_from_slice(
+ &src_buffer[((x_sh * buffer_bpp) / 8) as usize..((x_sh as usize + width) * buffer_bpp) / 8],
+ );
+}
+
+/// Performs decompression of one line of pixels,
+/// vertically positions the line against the display area (current position of
+/// which is described by `display_area_y`) by skipping relevant number of lines
+/// and finally horizontally positions the line against the display area
+/// by calling `position_buffer`.
+///
+/// Number of already decompressed lines is stored in `decompressed_lines` to
+/// keep track of how many need to be skipped.
+///
+/// Signals to the caller whether some data should be drawn on this line.
+fn process_buffer(
+ display_area_y: i16,
+ img_area: Rect,
+ offset: Offset,
+ ctx: &mut UzlibContext,
+ buffer: &mut [u8],
+ decompressed_lines: &mut i16,
+ buffer_bpp: usize,
+) -> bool {
+ let mut not_empty = false;
+ let uncomp_buffer =
+ &mut [0u8; (constant::WIDTH * 2) as usize][..((constant::WIDTH as usize) * buffer_bpp) / 8];
+
+ if display_area_y >= img_area.y0 && display_area_y < img_area.y1 {
+ let img_line_idx = display_area_y - img_area.y0;
+
+ while *decompressed_lines < img_line_idx {
+ //compensate uncompressed unused lines
+ unwrap!(
+ ctx.uncompress(
+ &mut uncomp_buffer[0..((img_area.width() * buffer_bpp as i16) / 8) as usize]
+ ),
+ "Decompression failed"
+ );
+
+ (*decompressed_lines) += 1;
+ }
+ // decompress whole line
+ unwrap!(
+ ctx.uncompress(
+ &mut uncomp_buffer[0..((img_area.width() * buffer_bpp as i16) / 8) as usize]
+ ),
+ "Decompression failed"
+ );
+
+ (*decompressed_lines) += 1;
+
+ position_buffer(
+ buffer,
+ uncomp_buffer,
+ buffer_bpp,
+ offset.x,
+ img_area.width(),
+ );
+
+ not_empty = true;
+ }
+
+ not_empty
+}
+
+/// Renders text over image background
+/// If `bg_area` is given, it is filled with its color in places where there are
+/// neither text or image Positioning also depends on whether `bg_area` is
+/// provided:
+/// - if it is, text and image are positioned relative to the `bg_area` top left
+/// corner, using respective offsets. Nothing is drawn outside the `bg_area`.
+/// - if it is not, text is positioned relative to the images top left corner
+/// using `offset_text` and image is positioned on the screen using
+/// `offset_img`. Nothing is drawn outside the image.
+/// `offset_text` is interpreted as baseline, so using (0,0) will position most
+/// of the text outside the drawing area in either case.
+///
+/// The drawing area is coerced to even width, which is due to dma2d limitation
+/// when using 4bpp
+#[cfg(feature = "dma2d")]
+pub fn text_over_image(
+ bg_area: Option<(Rect, Color)>,
+ image_data: &[u8],
+ text: &str,
+ font: Font,
+ offset_img: Offset,
+ offset_text: Offset,
+ text_color: Color,
+) {
+ let text_buffer = unsafe { get_text_buffer(0, true) };
+ let img1 = unsafe { get_buffer_16bpp(0, true) };
+ let img2 = unsafe { get_buffer_16bpp(1, true) };
+ let empty_img = unsafe { get_buffer_16bpp(2, true) };
+ let t1 = unsafe { get_buffer_4bpp(0, true) };
+ let t2 = unsafe { get_buffer_4bpp(1, true) };
+ let empty_t = unsafe { get_buffer_4bpp(2, true) };
+
+ let (toif_size, toif_data) = toif_info_ensure(image_data, ToifFormat::FullColorLE);
+
+ let r_img;
+ let area;
+ let offset_img_final;
+ if let Some((a, color)) = bg_area {
+ let hi = color.hi_byte();
+ let lo = color.lo_byte();
+ //prefill image/bg buffers with the bg color
+ for i in 0..(constant::WIDTH) as usize {
+ img1.buffer[2 * i] = lo;
+ img1.buffer[2 * i + 1] = hi;
+ }
+ img2.buffer.copy_from_slice(&img1.buffer);
+ empty_img.buffer.copy_from_slice(&img1.buffer);
+
+ area = a;
+ r_img = Rect::from_top_left_and_size(a.top_left() + offset_img, toif_size);
+ offset_img_final = offset_img;
+ } else {
+ area = Rect::from_top_left_and_size(offset_img.into(), toif_size);
+ r_img = area;
+ offset_img_final = Offset::zero();
+ }
+ let clamped = area.clamp(constant::screen()).ensure_even_width();
+
+ let text_width = display::text_width(text, font.into());
+ let font_max_height = display::text_max_height(font.into());
+ let font_baseline = display::text_baseline(font.into());
+ let text_width_clamped = text_width.clamp(0, clamped.width());
+
+ let text_top = area.y0 + offset_text.y - font_max_height + font_baseline;
+ let text_bottom = area.y0 + offset_text.y + font_baseline;
+ let text_left = area.x0 + offset_text.x;
+ let text_right = area.x0 + offset_text.x + text_width_clamped;
+
+ let text_area = Rect::new(
+ Point::new(text_left, text_top),
+ Point::new(text_right, text_bottom),
+ );
+
+ display::text_into_buffer(text, font.into(), text_buffer, 0);
+
+ set_window(clamped);
+
+ let mut window = [0; UZLIB_WINDOW_SIZE];
+ let mut ctx = UzlibContext::new(toif_data, Some(&mut window));
+
+ dma2d_setup_4bpp_over_16bpp(text_color.into());
+
+ let mut i = 0;
+
+ for y in clamped.y0..clamped.y1 {
+ let mut img_buffer = &mut *empty_img;
+ let mut t_buffer = &mut *empty_t;
+ let img_buffer_used;
+ let t_buffer_used;
+
+ if y % 2 == 0 {
+ t_buffer_used = &mut *t1;
+ img_buffer_used = &mut *img1;
+ } else {
+ t_buffer_used = &mut *t2;
+ img_buffer_used = &mut *img2;
+ }
+
+ let using_img = process_buffer(
+ y,
+ r_img,
+ offset_img_final,
+ &mut ctx,
+ &mut img_buffer_used.buffer,
+ &mut i,
+ 16,
+ );
+
+ if y >= text_area.y0 && y < text_area.y1 {
+ let y_pos = y - text_area.y0;
+ position_buffer(
+ &mut t_buffer_used.buffer,
+ &text_buffer.buffer[(y_pos * constant::WIDTH / 2) as usize
+ ..((y_pos + 1) * constant::WIDTH / 2) as usize],
+ 4,
+ offset_text.x,
+ text_width,
+ );
+ t_buffer = t_buffer_used;
+ }
+
+ if using_img {
+ img_buffer = img_buffer_used;
+ }
+
+ dma2d_wait_for_transfer();
+ dma2d_start_blend(&t_buffer.buffer, &img_buffer.buffer, clamped.width());
+ }
+
+ dma2d_wait_for_transfer();
+}
+
+/// Renders text over image background
+/// If `bg_area` is given, it is filled with its color in places where there is
+/// neither icon. Positioning also depends on whether `bg_area` is provided:
+/// - if it is, icons are positioned relative to the `bg_area` top left corner,
+/// using respective offsets. Nothing is drawn outside the `bg_area`.
+/// - if it is not, `fg` icon is positioned relative to the `bg` icons top left
+/// corner using its offset and `fg` icon is positioned on the screen using
+/// its offset. Nothing is drawn outside the `bg` icon.
+///
+/// The drawing area is coerced to even width, which is due to dma2d limitation
+/// when using 4bpp
+#[cfg(feature = "dma2d")]
+pub fn icon_over_icon(
+ bg_area: Option,
+ bg: (&[u8], Offset, Color),
+ fg: (&[u8], Offset, Color),
+ bg_color: Color,
+) {
+ let bg1 = unsafe { get_buffer_16bpp(0, true) };
+ let bg2 = unsafe { get_buffer_16bpp(1, true) };
+ let empty1 = unsafe { get_buffer_16bpp(2, true) };
+ let fg1 = unsafe { get_buffer_4bpp(0, true) };
+ let fg2 = unsafe { get_buffer_4bpp(1, true) };
+ let empty2 = unsafe { get_buffer_4bpp(2, true) };
+
+ let (data_bg, offset_bg, color_icon_bg) = bg;
+ let (data_fg, offset_fg, color_icon_fg) = fg;
+
+ let (toif_bg_size, toif_bg_data) = toif_info_ensure(data_bg, ToifFormat::GrayScaleEH);
+ assert!(toif_bg_size.x <= constant::WIDTH);
+ assert_eq!(toif_bg_size.x % 2, 0);
+
+ let (toif_fg_size, toif_fg_data) = toif_info_ensure(data_fg, ToifFormat::GrayScaleEH);
+ assert!(toif_bg_size.x <= constant::WIDTH);
+ assert_eq!(toif_bg_size.x % 2, 0);
+
+ let area;
+ let r_bg;
+ let final_offset_bg;
+ if let Some(a) = bg_area {
+ area = a;
+ r_bg = Rect::from_top_left_and_size(a.top_left() + offset_bg, toif_bg_size);
+ final_offset_bg = offset_bg;
+ } else {
+ r_bg = Rect::from_top_left_and_size(Point::new(offset_bg.x, offset_bg.y), toif_bg_size);
+ area = r_bg;
+ final_offset_bg = Offset::zero();
+ }
+
+ let r_fg = Rect::from_top_left_and_size(area.top_left() + offset_fg, toif_fg_size);
+
+ let clamped = area.clamp(constant::screen()).ensure_even_width();
+
+ set_window(clamped);
+
+ let mut window_bg = [0; UZLIB_WINDOW_SIZE];
+ let mut ctx_bg = UzlibContext::new(toif_bg_data, Some(&mut window_bg));
+
+ let mut window_fg = [0; UZLIB_WINDOW_SIZE];
+ let mut ctx_fg = UzlibContext::new(toif_fg_data, Some(&mut window_fg));
+
+ dma2d_setup_4bpp_over_4bpp(color_icon_bg.into(), bg_color.into(), color_icon_fg.into());
+
+ let mut fg_i = 0;
+ let mut bg_i = 0;
+
+ for y in clamped.y0..clamped.y1 {
+ let mut fg_buffer = &mut *empty2;
+ let mut bg_buffer = &mut *empty1;
+ let fg_buffer_used;
+ let bg_buffer_used;
+
+ if y % 2 == 0 {
+ fg_buffer_used = &mut *fg1;
+ bg_buffer_used = &mut *bg1;
+ } else {
+ fg_buffer_used = &mut *fg2;
+ bg_buffer_used = &mut *bg2;
+ }
+
+ const BUFFER_BPP: usize = 4;
+
+ let using_fg = process_buffer(
+ y,
+ r_fg,
+ offset_fg,
+ &mut ctx_fg,
+ &mut fg_buffer_used.buffer,
+ &mut fg_i,
+ 4,
+ );
+ let using_bg = process_buffer(
+ y,
+ r_bg,
+ final_offset_bg,
+ &mut ctx_bg,
+ &mut bg_buffer_used.buffer,
+ &mut bg_i,
+ 4,
+ );
+
+ if using_fg {
+ fg_buffer = fg_buffer_used;
+ }
+ if using_bg {
+ bg_buffer = bg_buffer_used;
+ }
+
+ dma2d_wait_for_transfer();
+ dma2d_start_blend(&fg_buffer.buffer, &bg_buffer.buffer, clamped.width());
+ }
+
+ dma2d_wait_for_transfer();
+}
+
/// Gets a color of a pixel on `p` coordinates of rounded rectangle with corner
/// radius 2
fn rect_rounded2_get_pixel(
@@ -478,45 +833,6 @@ pub fn dotted_line(start: Point, width: i16, color: Color) {
}
}
-pub const LOADER_MIN: u16 = 0;
-pub const LOADER_MAX: u16 = 1000;
-
-pub fn loader(
- progress: u16,
- y_offset: i16,
- fg_color: Color,
- bg_color: Color,
- icon: Option<(&[u8], Color)>,
-) {
- display::loader(
- progress,
- false,
- y_offset,
- fg_color.into(),
- bg_color.into(),
- icon.map(|i| i.0),
- icon.map(|i| i.1.into()).unwrap_or(0),
- );
-}
-
-pub fn loader_indeterminate(
- progress: u16,
- y_offset: i16,
- fg_color: Color,
- bg_color: Color,
- icon: Option<(&[u8], Color)>,
-) {
- display::loader(
- progress,
- true,
- y_offset,
- fg_color.into(),
- bg_color.into(),
- icon.map(|i| i.0),
- icon.map(|i| i.1.into()).unwrap_or(0),
- );
-}
-
pub fn qrcode(center: Point, data: &str, max_size: u32, case_sensitive: bool) -> Result<(), Error> {
qr::render_qrcode(center.x, center.y, data, max_size, case_sensitive)
}
@@ -758,6 +1074,12 @@ impl Color {
Self(r | g | b)
}
+ pub const fn luminance(self) -> u32 {
+ ((self.r() as u32 * 299) / 1000)
+ + (self.g() as u32 * 587) / 1000
+ + (self.b() as u32 * 114) / 1000
+ }
+
pub const fn r(self) -> u8 {
(self.0 >> 8) as u8 & 0xF8
}
@@ -774,6 +1096,14 @@ impl Color {
self.0
}
+ pub fn hi_byte(self) -> u8 {
+ (self.to_u16() >> 8) as u8
+ }
+
+ pub fn lo_byte(self) -> u8 {
+ (self.to_u16() & 0xFF) as u8
+ }
+
pub fn negate(self) -> Self {
Self(!self.0)
}
diff --git a/core/embed/rust/src/ui/geometry.rs b/core/embed/rust/src/ui/geometry.rs
index a8e40a80fb..09703cb76a 100644
--- a/core/embed/rust/src/ui/geometry.rs
+++ b/core/embed/rust/src/ui/geometry.rs
@@ -128,6 +128,12 @@ impl Sub for Offset {
}
}
+impl From for Offset {
+ fn from(val: Point) -> Self {
+ Offset::new(val.x, val.y)
+ }
+}
+
/// A point in 2D space defined by the the `x` and `y` coordinate. Relative
/// coordinates, vectors, and offsets are represented by the `Offset` type.
#[derive(Copy, Clone, PartialEq, Eq)]
@@ -188,6 +194,12 @@ impl Lerp for Point {
}
}
+impl From for Point {
+ fn from(val: Offset) -> Self {
+ Point::new(val.x, val.y)
+ }
+}
+
/// A rectangle in 2D space defined by the top-left point `x0`,`y0` and the
/// bottom-right point `x1`,`y1`.
#[derive(Copy, Clone, PartialEq, Eq)]
@@ -367,6 +379,14 @@ impl Rect {
}
}
+ pub const fn ensure_even_width(self) -> Self {
+ if self.width() % 2 == 0 {
+ self
+ } else {
+ self.with_size(Offset::new(self.size().x - 1, self.size().y))
+ }
+ }
+
pub const fn translate(&self, offset: Offset) -> Self {
Self {
x0: self.x0 + offset.x,
diff --git a/core/embed/rust/src/ui/model_tr/constant.rs b/core/embed/rust/src/ui/model_tr/constant.rs
index 2e96ffccc1..dc5f341e5c 100644
--- a/core/embed/rust/src/ui/model_tr/constant.rs
+++ b/core/embed/rust/src/ui/model_tr/constant.rs
@@ -5,6 +5,10 @@ pub const HEIGHT: i16 = 128;
pub const LINE_SPACE: i16 = 1;
pub const FONT_BPP: i16 = 1;
+pub const LOADER_OUTER: f32 = 20_f32;
+pub const LOADER_INNER: f32 = 14_f32;
+pub const LOADER_ICON_MAX_SIZE: i16 = 24;
+
pub const fn size() -> Offset {
Offset::new(WIDTH, HEIGHT)
}
diff --git a/core/embed/rust/src/ui/model_tt/constant.rs b/core/embed/rust/src/ui/model_tt/constant.rs
index cbc66328b6..ceed7fe464 100644
--- a/core/embed/rust/src/ui/model_tt/constant.rs
+++ b/core/embed/rust/src/ui/model_tt/constant.rs
@@ -5,6 +5,10 @@ pub const HEIGHT: i16 = 240;
pub const LINE_SPACE: i16 = 4;
pub const FONT_BPP: i16 = 4;
+pub const LOADER_OUTER: f32 = 60_f32;
+pub const LOADER_INNER: f32 = 42_f32;
+pub const LOADER_ICON_MAX_SIZE: i16 = 64;
+
pub const fn size() -> Offset {
Offset::new(WIDTH, HEIGHT)
}
diff --git a/core/embed/rust/trezorhal.h b/core/embed/rust/trezorhal.h
index 6a9a5f68e8..dd18552dee 100644
--- a/core/embed/rust/trezorhal.h
+++ b/core/embed/rust/trezorhal.h
@@ -1,5 +1,8 @@
+#include "buffers.h"
#include "common.h"
#include "display.h"
+#include "display_interface.h"
+#include "dma2d.h"
#include "fonts/fonts.h"
#include "rgb_led.h"
#include "secbool.h"
diff --git a/core/embed/trezorhal/common.c b/core/embed/trezorhal/common.c
index f0396a7be3..0bacc5f1a1 100644
--- a/core/embed/trezorhal/common.c
+++ b/core/embed/trezorhal/common.c
@@ -184,3 +184,12 @@ void collect_hw_entropy(void) {
FLASH_OTP_BLOCK_SIZE),
NULL);
}
+
+// this function resets settings changed in one layer (bootloader/firmware),
+// which might be incompatible with the other layers older versions,
+// where this setting might be unknown
+void ensure_compatible_settings(void) {
+#ifdef TREZOR_MODEL_T
+ display_set_big_endian();
+#endif
+}
diff --git a/core/embed/trezorhal/common.h b/core/embed/trezorhal/common.h
index 1bee151f98..770e7ec97c 100644
--- a/core/embed/trezorhal/common.h
+++ b/core/embed/trezorhal/common.h
@@ -82,5 +82,6 @@ void memset_reg(volatile void *start, volatile void *stop, uint32_t val);
void jump_to(uint32_t address);
void jump_to_unprivileged(uint32_t address);
void jump_to_with_flag(uint32_t address, uint32_t register_flag);
+void ensure_compatible_settings(void);
#endif
diff --git a/core/embed/trezorhal/dma2d.c b/core/embed/trezorhal/dma2d.c
new file mode 100644
index 0000000000..ea8e2e7b37
--- /dev/null
+++ b/core/embed/trezorhal/dma2d.c
@@ -0,0 +1,148 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "dma2d.h"
+#include "colors.h"
+#include STM32_HAL_H
+
+typedef enum {
+ DMA2D_LAYER_FG = 1,
+ DMA2D_LAYER_BG = 0,
+} dma2d_layer_t;
+
+static DMA2D_HandleTypeDef dma2d_handle = {0};
+
+void dma2d_init(void) {
+ __HAL_RCC_DMA2D_CLK_ENABLE();
+
+ dma2d_handle.Instance = (DMA2D_TypeDef*)DMA2D_BASE;
+ dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
+ dma2d_handle.Init.OutputOffset = 0;
+}
+
+static void dma2d_init_clut(uint16_t fg, uint16_t bg, dma2d_layer_t layer) {
+ volatile uint32_t* table = NULL;
+ if (layer == DMA2D_LAYER_BG) {
+ table = dma2d_handle.Instance->BGCLUT;
+ } else {
+ table = dma2d_handle.Instance->FGCLUT;
+ }
+
+ uint32_t fg32 = rgb565_to_rgb888(fg);
+ uint32_t bg32 = rgb565_to_rgb888(bg);
+
+ for (uint8_t i = 0; i < 16; i++) {
+ table[i] = interpolate_rgb888_color(fg32, bg32, i);
+ }
+
+ DMA2D_CLUTCfgTypeDef clut;
+ clut.CLUTColorMode = DMA2D_CCM_ARGB8888;
+ clut.Size = 0xf;
+ clut.pCLUT = 0; // loading directly
+
+ HAL_DMA2D_ConfigCLUT(&dma2d_handle, clut, layer);
+}
+
+void dma2d_setup_const(void) {
+ dma2d_handle.Init.Mode = DMA2D_R2M;
+ HAL_DMA2D_Init(&dma2d_handle);
+}
+
+void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color) {
+ dma2d_handle.Init.Mode = DMA2D_M2M_PFC;
+ dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
+ dma2d_handle.LayerCfg[1].InputOffset = 0;
+ dma2d_handle.LayerCfg[1].AlphaMode = 0;
+ dma2d_handle.LayerCfg[1].InputAlpha = 0;
+
+ dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_FG);
+
+ HAL_DMA2D_Init(&dma2d_handle);
+ HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
+}
+
+void dma2d_setup_16bpp(void) {
+ dma2d_handle.Init.Mode = DMA2D_M2M_PFC;
+ dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
+ dma2d_handle.LayerCfg[1].InputOffset = 0;
+ dma2d_handle.LayerCfg[1].AlphaMode = 0;
+ dma2d_handle.LayerCfg[1].InputAlpha = 0;
+
+ HAL_DMA2D_Init(&dma2d_handle);
+ HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
+}
+
+void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color) {
+ dma2d_handle.Init.Mode = DMA2D_M2M_BLEND;
+ dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4;
+ dma2d_handle.LayerCfg[1].InputOffset = 0;
+ dma2d_handle.LayerCfg[1].AlphaMode = 0;
+ dma2d_handle.LayerCfg[1].InputAlpha =
+ 0xFF000000 | rgb565_to_rgb888(overlay_color);
+
+ dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
+ dma2d_handle.LayerCfg[0].InputOffset = 0;
+ dma2d_handle.LayerCfg[0].AlphaMode = 0;
+ dma2d_handle.LayerCfg[0].InputAlpha = 0;
+
+ HAL_DMA2D_Init(&dma2d_handle);
+ HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
+ HAL_DMA2D_ConfigLayer(&dma2d_handle, 0);
+}
+
+void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color,
+ uint16_t overlay_color) {
+ dma2d_handle.Init.Mode = DMA2D_M2M_BLEND;
+ dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4;
+ dma2d_handle.LayerCfg[1].InputOffset = 0;
+ dma2d_handle.LayerCfg[1].AlphaMode = 0;
+ dma2d_handle.LayerCfg[1].InputAlpha = rgb565_to_rgb888(overlay_color);
+
+ dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_L4;
+ dma2d_handle.LayerCfg[0].InputOffset = 0;
+ dma2d_handle.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA;
+ dma2d_handle.LayerCfg[0].InputAlpha = 0xFF;
+
+ dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_BG);
+
+ HAL_DMA2D_Init(&dma2d_handle);
+ HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
+ HAL_DMA2D_ConfigLayer(&dma2d_handle, 0);
+}
+
+void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels) {
+ HAL_DMA2D_Start(&dma2d_handle, (uint32_t)in_addr, (uint32_t)out_addr, pixels,
+ 1);
+}
+
+void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels) {
+ HAL_DMA2D_Start(&dma2d_handle, rgb565_to_rgb888(color), (uint32_t)out_addr,
+ pixels, 1);
+}
+
+void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr,
+ uint8_t* out_addr, int32_t pixels) {
+ HAL_DMA2D_BlendingStart(&dma2d_handle, (uint32_t)overlay_addr,
+ (uint32_t)bg_addr, (uint32_t)out_addr, pixels, 1);
+}
+
+void dma2d_wait_for_transfer(void) {
+ while (HAL_DMA2D_PollForTransfer(&dma2d_handle, 10) != HAL_OK)
+ ;
+}
diff --git a/core/embed/trezorhal/dma2d.h b/core/embed/trezorhal/dma2d.h
new file mode 100644
index 0000000000..790cbc2d62
--- /dev/null
+++ b/core/embed/trezorhal/dma2d.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TREZORHAL_DMA2D_H
+#define TREZORHAL_DMA2D_H
+
+#include "common.h"
+
+void dma2d_init(void);
+
+void dma2d_setup_const(void);
+void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color);
+void dma2d_setup_16bpp(void);
+void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color,
+ uint16_t overlay_color);
+void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color);
+
+void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels);
+void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels);
+void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr,
+ uint8_t* out_addr, int32_t pixels);
+
+void dma2d_wait_for_transfer(void);
+
+#endif // TREZORHAL_DMA2D_H
diff --git a/core/embed/trezorhal/stm32f4xx_hal_conf.h b/core/embed/trezorhal/stm32f4xx_hal_conf.h
index 2882f8a16b..0258c1a326 100644
--- a/core/embed/trezorhal/stm32f4xx_hal_conf.h
+++ b/core/embed/trezorhal/stm32f4xx_hal_conf.h
@@ -69,7 +69,7 @@
/* #define HAL_DAC_MODULE_ENABLED */
/* #define HAL_DCMI_MODULE_ENABLED */
#define HAL_DMA_MODULE_ENABLED
-/* #define HAL_DMA2D_MODULE_ENABLED */
+#define HAL_DMA2D_MODULE_ENABLED
/* #define HAL_ETH_MODULE_ENABLED */
#define HAL_FLASH_MODULE_ENABLED
/* #define HAL_NAND_MODULE_ENABLED */
diff --git a/core/embed/unix/dma2d.c b/core/embed/unix/dma2d.c
new file mode 100644
index 0000000000..32af3438d6
--- /dev/null
+++ b/core/embed/unix/dma2d.c
@@ -0,0 +1,145 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "dma2d.h"
+#include "colors.h"
+#include "display_interface.h"
+
+typedef enum {
+ DMA2D_LAYER_FG = 1,
+ DMA2D_LAYER_BG = 0,
+} dma2d_layer_t;
+
+typedef enum {
+ DMA2D_MODE_CONST = 0,
+ DMA2D_MODE_4BPP,
+ DMA2D_MODE_16BPP,
+ DMA2D_MODE_4BPP_OVER_4BPP,
+ DMA2D_MODE_4BPP_OVER_16BPP,
+} dma2d_mode_t;
+
+static uint16_t clut_bg[16];
+static uint16_t clut_fg[16];
+static uint16_t dma2d_color;
+static dma2d_mode_t mode = 0;
+
+void dma2d_init(void) {
+ // do nothing
+}
+
+void dma2d_init_clut(uint16_t fg, uint16_t bg, dma2d_layer_t layer) {
+ uint16_t* table;
+ if (layer == DMA2D_LAYER_BG) {
+ table = clut_bg;
+ } else {
+ table = clut_fg;
+ }
+
+ set_color_table(table, fg, bg);
+}
+
+void dma2d_setup_const(void) { mode = DMA2D_MODE_CONST; }
+
+void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color) {
+ dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_FG);
+ mode = DMA2D_MODE_4BPP;
+}
+
+void dma2d_setup_16bpp(void) { mode = DMA2D_MODE_16BPP; }
+
+void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color) {
+ mode = DMA2D_MODE_4BPP_OVER_16BPP;
+ dma2d_color = overlay_color;
+}
+
+void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color,
+ uint16_t overlay_color) {
+ mode = DMA2D_MODE_4BPP_OVER_4BPP;
+
+ dma2d_color = overlay_color;
+ dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_BG);
+}
+
+void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels) {
+ (void)out_addr;
+ for (int i = 0; i < pixels; i++) {
+ if (mode == DMA2D_MODE_4BPP) {
+ uint8_t c = ((uint8_t*)in_addr)[i / 2];
+ uint8_t even_pix = c >> 4;
+ uint8_t odd_pix = c & 0xF;
+ PIXELDATA(clut_fg[odd_pix]);
+ PIXELDATA(clut_fg[even_pix]);
+ i++; // wrote two pixels
+ }
+ if (mode == DMA2D_MODE_16BPP) {
+ uint16_t c = ((uint16_t*)in_addr)[i];
+ PIXELDATA(c);
+ }
+ }
+}
+
+void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels) {
+ (void)out_addr;
+ for (int i = 0; i < pixels; i++) {
+ PIXELDATA(color);
+ }
+}
+
+void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr,
+ uint8_t* out_addr, int32_t pixels) {
+ (void)out_addr;
+ for (int i = 0; i < pixels; i++) {
+ if (mode == DMA2D_MODE_4BPP_OVER_4BPP) {
+ uint8_t c = overlay_addr[i / 2];
+ uint8_t b = bg_addr[i / 2];
+
+ uint8_t odd_overlay_pix = c & 0xF;
+ uint8_t odd_bg_pix = b & 0xF;
+ uint16_t c_odd_bg = clut_bg[odd_bg_pix];
+ uint16_t final_odd_color =
+ interpolate_color(dma2d_color, c_odd_bg, odd_overlay_pix);
+ PIXELDATA(final_odd_color);
+
+ uint8_t even_overlay_pix = c >> 4;
+ uint8_t even_bg_pix = b >> 4;
+ uint16_t c_even_bg = clut_bg[even_bg_pix];
+ uint16_t final_even_color =
+ interpolate_color(dma2d_color, c_even_bg, even_overlay_pix);
+ PIXELDATA(final_even_color);
+
+ i++; // wrote two pixels
+ }
+ if (mode == DMA2D_MODE_4BPP_OVER_16BPP) {
+ uint16_t c = ((uint16_t*)bg_addr)[i];
+ uint8_t o = overlay_addr[i / 2];
+ uint8_t o_pix;
+ if (i % 2 == 0) {
+ o_pix = o & 0xF;
+ } else {
+ o_pix = o >> 4;
+ }
+ uint16_t final_odd_color = interpolate_color(dma2d_color, c, o_pix);
+ PIXELDATA(final_odd_color);
+ }
+ }
+}
+
+void dma2d_wait_for_transfer(void) {
+ // done in place when emulating, so no need for wait here
+}
diff --git a/core/embed/unix/dma2d.h b/core/embed/unix/dma2d.h
new file mode 100644
index 0000000000..263ec54eb9
--- /dev/null
+++ b/core/embed/unix/dma2d.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _DMA2D_H
+#define _DMA2D_H
+
+// Mockup file so that rust doesn't complain when building bindings
+
+#include "common.h"
+
+void dma2d_init(void);
+
+void dma2d_setup_const(void);
+void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color);
+void dma2d_setup_16bpp(void);
+void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color,
+ uint16_t overlay_color);
+void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color);
+
+void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels);
+void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels);
+void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr,
+ uint8_t* out_addr, int32_t pixels);
+
+void dma2d_wait_for_transfer(void);
+
+#endif //_DMA2D_H
diff --git a/core/mocks/generated/trezorui.pyi b/core/mocks/generated/trezorui.pyi
index 2c992d7797..233f1f9159 100644
--- a/core/mocks/generated/trezorui.pyi
+++ b/core/mocks/generated/trezorui.pyi
@@ -11,6 +11,10 @@ class Display:
FONT_MONO: int # id of monospace font
FONT_NORMAL: int # id of normal-width font
FONT_BOLD: int # id of bold-width font
+ TOIF_FULL_COLOR_BE: int # full color big endian TOI image
+ TOIF_FULL_COLOR_LE: int # full color little endian TOI image
+ TOIF_GRAYSCALE_EH: int # grayscale even high TOI image
+ TOIF_FULL_COLOR_BE: int # grayscale odd high TOI image
def __init__(self) -> None:
"""
@@ -49,11 +53,10 @@ class Display:
are drawn with radius radius.
"""
- def toif_info(self, image: bytes) -> tuple[int, int, bool]:
+ def toif_info(self, image: bytes) -> tuple[int, int, int]:
"""
Returns tuple containing TOIF image dimensions: width, height, and
- whether it is grayscale.
- Raises an exception for corrupted images.
+ format Raises an exception for corrupted images.
"""
def image(self, x: int, y: int, image: bytes) -> None:
diff --git a/core/src/apps/management/apply_settings.py b/core/src/apps/management/apply_settings.py
index 438d70fa10..fdf8bfd34d 100644
--- a/core/src/apps/management/apply_settings.py
+++ b/core/src/apps/management/apply_settings.py
@@ -24,12 +24,12 @@ def validate_homescreen(homescreen: bytes) -> None:
)
try:
- w, h, grayscale = ui.display.toif_info(homescreen)
+ w, h, toif_format = ui.display.toif_info(homescreen)
except ValueError:
raise wire.DataError("Invalid homescreen")
if w != 144 or h != 144:
raise wire.DataError("Homescreen must be 144x144 pixel large")
- if grayscale:
+ if toif_format != ui.display.TOIF_FULL_COLOR_BE:
raise wire.DataError("Homescreen must be full-color TOIF image")
diff --git a/core/tools/snippets/change_icon_format.py b/core/tools/snippets/change_icon_format.py
new file mode 100644
index 0000000000..2c65a658fe
--- /dev/null
+++ b/core/tools/snippets/change_icon_format.py
@@ -0,0 +1,161 @@
+import os
+
+from trezorlib import toif
+
+
+def process_line(infile, outfile):
+ line = infile.readline()
+ data = [x.strip().lower() for x in line.split(',')]
+ for c in data:
+ if len(c) == 4:
+ outfile.write(bytes((int(c, 16),)))
+
+
+def header_to_toif(path) -> bool:
+ with open(path, "r") as infile, open('tmp.toif', "wb") as outfile:
+ infile.readline()
+ name_line = infile.readline()
+ name = name_line.split(" ")[3].split("[")[0]
+ infile.readline()
+ magic_line = infile.readline().split(',')[3]
+
+ outfile.write(bytes((0x54,)))
+ outfile.write(bytes((0x4f,)))
+ outfile.write(bytes((0x49,)))
+ if "g" in magic_line:
+ outfile.write(bytes((ord('g'),)))
+ elif "G" in magic_line:
+ outfile.write(bytes((ord('G'),)))
+ elif "f" in magic_line:
+ outfile.write(bytes((ord('f'),)))
+ elif "F" in magic_line:
+ outfile.write(bytes((ord('F'),)))
+ else:
+ print(magic_line)
+ raise Exception("Unknown format")
+
+ infile.readline()
+ process_line(infile, outfile)
+ infile.readline()
+ process_line(infile, outfile)
+ infile.readline()
+ process_line(infile, outfile)
+ infile.readline()
+ return name
+
+
+def toif_to_header(path, name):
+ with open('tmp_c.toif', "rb") as infile, open(path, "w") as outfile:
+ b = infile.read(4)
+ outfile.write("// clang-format off\n")
+ outfile.write(f'static const uint8_t {name}[] = {{\n',)
+ outfile.write(" // magic\n",)
+ if b[3] == ord('f'):
+ outfile.write(" 'T', 'O', 'I', 'f',\n",)
+ elif b[3] == ord('F'):
+ outfile.write(" 'T', 'O', 'I', 'F',\n",)
+ elif b[3] == ord('g'):
+ outfile.write(" 'T', 'O', 'I', 'g',\n",)
+ elif b[3] == ord('G'):
+ outfile.write(" 'T', 'O', 'I', 'G',\n",)
+ else:
+ raise Exception("Unknown format")
+
+
+ outfile.write(" // width (16-bit), height (16-bit)\n",)
+ outfile.write(" ")
+ for i in range(4):
+ hex_data = infile.read(1).hex()
+ outfile.write(f'0x{hex_data},')
+ if i != 3:
+ outfile.write(' ')
+ outfile.write("\n")
+
+ outfile.write(" // compressed data length (32-bit)\n",)
+ outfile.write(" ")
+ for i in range(4):
+ hex_data = infile.read(1).hex()
+ outfile.write(f'0x{hex_data},')
+ if i != 3:
+ outfile.write(' ')
+ outfile.write("\n")
+
+ outfile.write(" // compressed data\n",)
+ outfile.write(" ")
+ hex_data = infile.read(1).hex()
+ first = True
+ while hex_data:
+ if not first:
+ outfile.write(' ')
+ first = False
+ outfile.write(f'0x{hex_data},')
+ hex_data = infile.read(1).hex()
+ outfile.write("\n};\n")
+
+ byte = infile.read(1)
+
+
+def reformat_c_icon(path):
+ name = header_to_toif(path)
+ with open("tmp.toif", "rb") as f:
+ toi = toif.from_bytes(f.read())
+ im = toi.to_image()
+ with open("tmp_c.toif", "wb") as f:
+ toi = toif.from_image(im)
+ f.write(toi.to_bytes())
+ toif_to_header(path, name)
+
+ os.remove("tmp.toif")
+ os.remove("tmp_c.toif")
+
+
+def reformat_c_icons(p):
+ files = os.listdir(p)
+ for file in files:
+ if file.startswith("icon_") and file.endswith(".h"):
+ reformat_c_icon(os.path.join(p, file))
+
+
+def reformat_toif_icon(p):
+ with open(p, "rb") as f:
+ toi = toif.from_bytes(f.read())
+ im = toi.to_image()
+ with open(p, "wb") as f:
+ toi = toif.from_image(im)
+ f.write(toi.to_bytes())
+
+
+def reformat_toif_icons(p):
+ files = os.listdir(p)
+ for file in files:
+ if file.endswith(".toif"):
+ reformat_toif_icon(os.path.join(p, file))
+
+
+def change_icon_format():
+ # bootloader icons
+ reformat_c_icons("../../embed/bootloader")
+
+ # bootloader_ci icons
+ reformat_c_icons("../../embed/bootloader_ci")
+
+ # rust icons
+ reformat_toif_icons("../../embed/rust/src/ui/model_tr/res")
+ reformat_toif_icons("../../embed/rust/src/ui/model_tt/res")
+
+ # python icons
+ reformat_toif_icons("../../src/trezor/res")
+ reformat_toif_icons("../../src/trezor/res/header_icons")
+
+ # vendor header icons
+ reformat_toif_icon("../../embed/vendorheader/vendor_satoshilabs.toif")
+ reformat_toif_icon("../../embed/vendorheader/vendor_unsafe.toif")
+
+ # additional python icons
+ # reformat_toif_icon("../src/apps/homescreen/res/bg.toif") - unchanged - using as avatar
+ reformat_toif_icon("../../src/apps/management/res/small-arrow.toif")
+ reformat_toif_icon("../../src/apps/webauthn/res/icon_webauthn.toif")
+
+
+if __name__ == "__main__":
+ change_icon_format()
diff --git a/docs/misc/toif.md b/docs/misc/toif.md
index 468f1db5da..fbe0d9894a 100644
--- a/docs/misc/toif.md
+++ b/docs/misc/toif.md
@@ -15,10 +15,12 @@ All multibyte integer values are little endian!
## Format
-TOI currently supports 2 variants:
+TOI currently supports 4 variants:
-* `f`: full-color
-* `g`: gray-scale
+* `f`: full-color big endian
+* `F`: full-color little endian
+* `g`: gray-scale odd high
+* `G`: gray-scale even high
### Full-color
@@ -30,15 +32,25 @@ final 5 bits are blue:
|----|----|----|----|----|----|---|---|---|---|---|---|---|---|---|---|
| R | R | R | R | R | G | G | G | G | G | G | B | B | B | B | B |
+The data is stored according to endianness.
+
### Gray-scale
Each pixel is encoded using a 4-bit value.
Each byte contains color of two pixels:
+#### Odd high:
+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|
| Po | Po | Po | Po | Pe | Pe | Pe | Pe |
+#### Even high:
+
+| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|-----|-----|-----|-----|-----|-----|-----|-----|
+| Pe | Pe | Pe | Pe | Po | Po | Po | Po |
+
Where Po is odd pixel and Pe is even pixel.
## Compression
diff --git a/python/.changelog.d/2414.added b/python/.changelog.d/2414.added
new file mode 100644
index 0000000000..68cb1d57e5
--- /dev/null
+++ b/python/.changelog.d/2414.added
@@ -0,0 +1 @@
+Added new TOI formats - little endian full-color and even-high grayscale
diff --git a/python/src/trezorlib/firmware.py b/python/src/trezorlib/firmware.py
index 54408082cd..eadad475c4 100644
--- a/python/src/trezorlib/firmware.py
+++ b/python/src/trezorlib/firmware.py
@@ -99,8 +99,10 @@ class Unsigned(FirmwareIntegrityError):
class ToifMode(Enum):
- full_color = b"f"
- grayscale = b"g"
+ full_color = b"f" # big endian
+ grayscale = b"g" # odd hi
+ full_color_le = b"F" # little endian
+ grayscale_eh = b"G" # even hi
class HeaderType(Enum):
diff --git a/python/src/trezorlib/toif.py b/python/src/trezorlib/toif.py
index 699d6e298f..db976c6a70 100644
--- a/python/src/trezorlib/toif.py
+++ b/python/src/trezorlib/toif.py
@@ -45,50 +45,68 @@ def _decompress(data: bytes) -> bytes:
return zlib.decompress(data, wbits=-10)
-def _from_pil_rgb(pixels: Sequence[RGBPixel]) -> bytes:
+def _from_pil_rgb(pixels: Sequence[RGBPixel], little_endian: bool) -> bytes:
data = bytearray()
for r, g, b in pixels:
c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
- data += struct.pack(">H", c)
+ if little_endian:
+ data += struct.pack("H", c)
return bytes(data)
-def _to_rgb(data: bytes) -> bytes:
+def _to_rgb(data: bytes, little_endian: bool) -> bytes:
res = bytearray()
for i in range(0, len(data), 2):
- (c,) = struct.unpack(">H", data[i : i + 2])
+ if little_endian:
+ (c,) = struct.unpack("H", data[i : i + 2])
r = (c & 0xF800) >> 8
- g = (c & 0x07C0) >> 3
+ g = (c & 0x07E0) >> 3
b = (c & 0x001F) << 3
res += bytes((r, g, b))
return bytes(res)
-def _from_pil_grayscale(pixels: Sequence[int]) -> bytes:
+def _from_pil_grayscale(pixels: Sequence[int], right_hi: bool) -> bytes:
data = bytearray()
for i in range(0, len(pixels), 2):
left, right = pixels[i], pixels[i + 1]
- c = (left & 0xF0) | ((right & 0xF0) >> 4)
+ if right_hi:
+ c = (right & 0xF0) | ((left & 0xF0) >> 4)
+ else:
+ c = (left & 0xF0) | ((right & 0xF0) >> 4)
data += struct.pack(">B", c)
return bytes(data)
-def _from_pil_grayscale_alpha(pixels: Sequence[Tuple[int, int]]) -> bytes:
+def _from_pil_grayscale_alpha(
+ pixels: Sequence[Tuple[int, int]], right_hi: bool
+) -> bytes:
data = bytearray()
for i in range(0, len(pixels), 2):
left_w_alpha, right_w_alpha = pixels[i], pixels[i + 1]
left = int((left_w_alpha[0] * left_w_alpha[1]) / 255)
right = int((right_w_alpha[0] * right_w_alpha[1]) / 255)
- c = (left & 0xF0) | ((right & 0xF0) >> 4)
+ if right_hi:
+ c = (right & 0xF0) | ((left & 0xF0) >> 4)
+ else:
+ c = (left & 0xF0) | ((right & 0xF0) >> 4)
data += struct.pack(">B", c)
return bytes(data)
-def _to_grayscale(data: bytes) -> bytes:
+def _to_grayscale(data: bytes, right_hi: bool) -> bytes:
res = bytearray()
for pixel in data:
- left = pixel & 0xF0
- right = (pixel & 0x0F) << 4
+ if right_hi:
+ right = pixel & 0xF0
+ left = (pixel & 0x0F) << 4
+ else:
+ left = pixel & 0xF0
+ right = (pixel & 0x0F) << 4
res += bytes((left, right))
return bytes(res)
@@ -102,7 +120,10 @@ class Toif:
def __post_init__(self) -> None:
# checking the data size
width, height = self.size
- if self.mode is firmware.ToifMode.grayscale:
+ if (
+ self.mode is firmware.ToifMode.grayscale
+ or self.mode is firmware.ToifMode.grayscale_eh
+ ):
expected_size = width * height // 2
else:
expected_size = width * height * 2
@@ -123,10 +144,16 @@ class Toif:
pil_mode: Literal["L", "RGB"]
if self.mode is firmware.ToifMode.grayscale:
pil_mode = "L"
- raw_data = _to_grayscale(uncompressed)
- else:
+ raw_data = _to_grayscale(uncompressed, right_hi=False)
+ elif self.mode is firmware.ToifMode.grayscale_eh:
+ pil_mode = "L"
+ raw_data = _to_grayscale(uncompressed, right_hi=True)
+ elif self.mode is firmware.ToifMode.full_color:
pil_mode = "RGB"
- raw_data = _to_rgb(uncompressed)
+ raw_data = _to_rgb(uncompressed, little_endian=False)
+ else: # self.mode is firmware.ToifMode.full_color_le:
+ pil_mode = "RGB"
+ raw_data = _to_rgb(uncompressed, little_endian=True)
return Image.frombuffer(pil_mode, self.size, raw_data, "raw", pil_mode, 0, 1)
@@ -152,7 +179,9 @@ def load(filename: str) -> Toif:
def from_image(
- image: "Image.Image", background: Tuple[int, int, int, int] = (0, 0, 0, 255)
+ image: "Image.Image",
+ background: Tuple[int, int, int, int] = (0, 0, 0, 255),
+ legacy_format: bool = False,
) -> Toif:
if not PIL_AVAILABLE:
raise RuntimeError(
@@ -168,18 +197,31 @@ def from_image(
image = image.convert("L")
if image.mode == "L":
- toif_mode = firmware.ToifMode.grayscale
if image.size[0] % 2 != 0:
raise ValueError("Only even-width grayscale images are supported")
- toif_data = _from_pil_grayscale(image.getdata())
+ if not legacy_format:
+ toif_mode = firmware.ToifMode.grayscale_eh
+ toif_data = _from_pil_grayscale(image.getdata(), right_hi=True)
+ else:
+ toif_mode = firmware.ToifMode.grayscale
+ toif_data = _from_pil_grayscale(image.getdata(), right_hi=False)
elif image.mode == "LA":
toif_mode = firmware.ToifMode.grayscale
if image.size[0] % 2 != 0:
raise ValueError("Only even-width grayscale images are supported")
- toif_data = _from_pil_grayscale_alpha(image.getdata())
+ if not legacy_format:
+ toif_mode = firmware.ToifMode.grayscale_eh
+ toif_data = _from_pil_grayscale_alpha(image.getdata(), right_hi=True)
+ else:
+ toif_mode = firmware.ToifMode.grayscale
+ toif_data = _from_pil_grayscale_alpha(image.getdata(), right_hi=False)
elif image.mode == "RGB":
- toif_mode = firmware.ToifMode.full_color
- toif_data = _from_pil_rgb(image.getdata())
+ if not legacy_format:
+ toif_mode = firmware.ToifMode.full_color_le
+ toif_data = _from_pil_rgb(image.getdata(), little_endian=True)
+ else:
+ toif_mode = firmware.ToifMode.full_color
+ toif_data = _from_pil_rgb(image.getdata(), little_endian=False)
else:
raise ValueError(f"Unsupported image mode: {image.mode}")