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:
parent
223d1b20fb
commit
f7b9bb4ef8
1
core/.changelog.d/2414.added
Normal file
1
core/.changelog.d/2414.added
Normal file
@ -0,0 +1 @@
|
||||
Using hardware acceleration (dma2d) for rendering
|
@ -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',
|
||||
]
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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}',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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)}"'
|
||||
|
||||
|
1
core/embed/boardloader/.changelog.d/2414.added
Normal file
1
core/embed/boardloader/.changelog.d/2414.added
Normal file
@ -0,0 +1 @@
|
||||
Using hardware acceleration (dma2d) for rendering
|
@ -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;
|
||||
|
1
core/embed/bootloader/.changelog.d/2414.added
Normal file
1
core/embed/bootloader/.changelog.d/2414.added
Normal file
@ -0,0 +1 @@
|
||||
Using hardware acceleration (dma2d) for rendering
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
1
core/embed/bootloader_ci/.changelog.d/2414.added
Normal file
1
core/embed/bootloader_ci/.changelog.d/2414.added
Normal file
@ -0,0 +1 @@
|
||||
Using hardware acceleration (dma2d) for rendering
|
74
core/embed/extmod/modtrezorui/buffers.c
Normal file
74
core/embed/extmod/modtrezorui/buffers.c
Normal 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
|
59
core/embed/extmod/modtrezorui/buffers.h
Normal file
59
core/embed/extmod/modtrezorui/buffers.h
Normal 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
|
41
core/embed/extmod/modtrezorui/colors.c
Normal file
41
core/embed/extmod/modtrezorui/colors.c
Normal 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;
|
||||
}
|
57
core/embed/extmod/modtrezorui/colors.h
Normal file
57
core/embed/extmod/modtrezorui/colors.h
Normal 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
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {}
|
@ -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) {
|
@ -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);
|
||||
|
@ -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
|
||||
|
52
core/embed/extmod/modtrezorui/display_defs.h
Normal file
52
core/embed/extmod/modtrezorui/display_defs.h
Normal 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
|
75
core/embed/extmod/modtrezorui/display_interface.h
Normal file
75
core/embed/extmod/modtrezorui/display_interface.h
Normal 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
|
@ -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);
|
||||
|
@ -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");
|
||||
|
39
core/embed/rust/Cargo.lock
generated
39
core/embed/rust/Cargo.lock
generated
@ -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]]
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
6
core/embed/rust/rust_ui.h
Normal file
6
core/embed/rust/rust_ui.h
Normal 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);
|
@ -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]
|
||||
|
45
core/embed/rust/src/trezorhal/buffers.rs
Normal file
45
core/embed/rust/src/trezorhal/buffers.rs
Normal 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())
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
26
core/embed/rust/src/trezorhal/dma2d.rs
Normal file
26
core/embed/rust/src/trezorhal/dma2d.rs
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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::*;
|
||||
|
396
core/embed/rust/src/ui/display/loader.rs
Normal file
396
core/embed/rust/src/ui/display/loader.rs
Normal 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);
|
||||
}
|
@ -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)
|
||||
}
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
148
core/embed/trezorhal/dma2d.c
Normal file
148
core/embed/trezorhal/dma2d.c
Normal 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)
|
||||
;
|
||||
}
|
41
core/embed/trezorhal/dma2d.h
Normal file
41
core/embed/trezorhal/dma2d.h
Normal 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
|
@ -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
145
core/embed/unix/dma2d.c
Normal 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
43
core/embed/unix/dma2d.h
Normal 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
|
@ -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:
|
||||
|
@ -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")
|
||||
|
||||
|
||||
|
161
core/tools/snippets/change_icon_format.py
Normal file
161
core/tools/snippets/change_icon_format.py
Normal 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()
|
@ -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
|
||||
|
1
python/.changelog.d/2414.added
Normal file
1
python/.changelog.d/2414.added
Normal file
@ -0,0 +1 @@
|
||||
Added new TOI formats - little endian full-color and even-high grayscale
|
@ -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):
|
||||
|
@ -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}")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user