1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-20 01:56:15 +00:00

feat(core/rust): use dma2d to improve rendering performance, implement text over image and icon over icon functions

This commit is contained in:
tychovrahe 2022-08-16 16:51:10 +02:00 committed by TychoVrahe
parent 223d1b20fb
commit f7b9bb4ef8
58 changed files with 2535 additions and 341 deletions

View File

@ -0,0 +1 @@
Using hardware acceleration (dma2d) for rendering

View File

@ -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',
]

View File

@ -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',

View File

@ -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',

View File

@ -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}',

View File

@ -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',

View File

@ -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',

View File

@ -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)}"'

View File

@ -0,0 +1 @@
Using hardware acceleration (dma2d) for rendering

View File

@ -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;

View File

@ -0,0 +1 @@
Using hardware acceleration (dma2d) for rendering

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1 @@
Using hardware acceleration (dma2d) for rendering

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _BUFFERS_H
#define _BUFFERS_H
#include <stdbool.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -17,6 +17,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdint.h>
#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();
}

View File

@ -17,6 +17,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#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();
}

View File

@ -17,13 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#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) {}

View File

@ -16,11 +16,21 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <SDL.h>
#include <SDL_image.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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) {

View File

@ -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 <stdarg.h>
@ -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);

View File

@ -24,35 +24,11 @@
#include <stddef.h>
#include <stdint.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _DISPLAY_INTERFACE_H
#define _DISPLAY_INTERFACE_H
#include <stdint.h>
#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

View File

@ -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);

View File

@ -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");

View File

@ -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]]

View File

@ -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

View File

@ -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()

View File

@ -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);

View File

@ -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]

View File

@ -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())
}
}

View File

@ -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<ToifInfo, ()> {
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);
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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::*;

View File

@ -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);
}

View File

@ -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<Rect>,
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)
}

View File

@ -128,6 +128,12 @@ impl Sub<Offset> for Offset {
}
}
impl From<Point> 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<Offset> 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,

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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"

View File

@ -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
}

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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)
;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 */

145
core/embed/unix/dma2d.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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
}

43
core/embed/unix/dma2d.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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:

View File

@ -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")

View File

@ -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()

View File

@ -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

View File

@ -0,0 +1 @@
Added new TOI formats - little endian full-color and even-high grayscale

View File

@ -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):

View File

@ -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)
else:
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])
else:
(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}")