WIP - New drawing library

[no changelog]
cepetr 4 months ago
parent 7ce1242b37
commit 5bec2abb59

@ -187,6 +187,13 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/extmod/modtrezorui/modtrezorui.c',
'embed/gdc/gdc_color.c',
'embed/gdc/gdc_core.c',
'embed/gdc/gdc_heap.c',
'embed/gdc/gdc_rgb565.c',
'embed/gdc/gdc_rgba8888.c',
'embed/gdc/gdc_text.c',
'embed/gdc/gdc_wnd565.c',
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_draw.c',

@ -188,6 +188,13 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/extmod/modtrezorui/modtrezorui.c',
'embed/gdc/gdc_color.c',
'embed/gdc/gdc_core.c',
'embed/gdc/gdc_heap.c',
'embed/gdc/gdc_rgb565.c',
'embed/gdc/gdc_rgba8888.c',
'embed/gdc/gdc_text.c',
'embed/gdc/gdc_wnd565.c',
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_draw.c',
@ -783,8 +790,10 @@ if ARGUMENTS.get('TREZOR_EMULATOR_DEBUGGABLE', '0') == '1':
RUST_PROFILE = 'dev'
RUST_LIBDIR = f'build/unix/rust/{TARGET}/debug'
else:
RUST_PROFILE = 'release'
RUST_LIBDIR = f'build/unix/rust/{TARGET}/release'
RUST_PROFILE = 'dev'
RUST_LIBDIR = f'build/unix/rust/{TARGET}/debug'
# RUST_PROFILE = 'release'
# RUST_LIBDIR = f'build/unix/rust/{TARGET}/release'
RUST_LIB = 'trezor_lib'
RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'

@ -189,6 +189,12 @@ int main(void) {
ensure(sectrue * (zkp_context_init() == 0), NULL);
#endif
while (1) { // TODO:just for testing, remove later !!!
new_drawing_poc();
display_refresh();
}
printf("CORE: Preparing stack\n");
// Stack limit should be less than real stack size, so we have a chance
// to recover from limit hit.

@ -0,0 +1,30 @@
/*
* 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 GDC_H
#define GDC_H
#include "gdc_bitmap.h"
#include "gdc_color.h"
#include "gdc_core.h"
#include "gdc_geom.h"
#include "gdc_heap.h"
#include "gdc_text.h"
#endif // GDC_H

@ -0,0 +1,107 @@
/*
* 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 GDC_BITMAP_H
#define GDC_BITMAP_H
#include "gdc_color.h"
#include "gdc_geom.h"
#include <stddef.h>
#include <stdint.h>
// forward declaration
typedef struct gdc_vmt gdc_vmt_t;
// ------------------------------------------------------------------------
// GDC Bitmap pixel format
//
typedef enum {
GDC_FORMAT_UNKNOWN, //
GDC_FORMAT_MONO1, // 1-bpp per pixel
GDC_FORMAT_MONO4, // 4-bpp per pixel
GDC_FORMAT_RGB565, // 16-bpp per pixel
GDC_FORMAT_RGBA8888, // 32-bpp
} gdc_format_t;
// ------------------------------------------------------------------------
// GDC Bitmap Attributes
//
#define GDC_BITMAP_READ_ONLY 0x01 // Read-only data
#define GDC_BITMAP_NO_DMA 0x02 // Not accessible by DMA2D
// #define GDC_BITMAP_DMA_READ 0x04 // DMA read pending
// #define GDC_BITMAP_DMA_WRITE 0x08 // DMA write pending
// ------------------------------------------------------------------------
// GDC Bitmap
//
// Structure holding pointer to the bitmap data, its format and sizes
//
// Note: gdc_bitmap_t itself can be used as GDC as long as it contains
// valid gdc virtual table pointer.
typedef struct gdc_bitmap {
// GDC virtual method table
// (must be the first field of the structure)
const gdc_vmt_t* vmt;
// pointer to top-left pixel
void* ptr;
// stride in bytes
size_t stride;
// size in pixels
gdc_size_t size;
// format of pixels
gdc_format_t format;
// attributes, GDC_BITMAP_xxx
uint8_t attrs;
} gdc_bitmap_t;
// Initializes RGB565 bitmap structure
// GDC and format fields and filled automatically.
gdc_bitmap_t gdc_bitmap_rgb565(void* data_ptr, size_t stride, gdc_size_t size,
uint8_t attrs);
// Initializes RGBA8888 bitmap structure
// GDC and format fields and filled automatically.
gdc_bitmap_t gdc_bitmap_rgba8888(void* data_ptr, size_t stride, gdc_size_t size,
uint8_t attrs);
// ------------------------------------------------------------------------
// GDC Bitmap reference
//
// Structure is used when bitmap is beeing drawed to supply
// additional parameters
typedef struct {
// soruce bitmap
const gdc_bitmap_t* bitmap;
// offset used when bitmap is drawed on gdc
gdc_offset_t offset;
// foreground color (used with MONOx formats)
gdc_color_t fg_color;
// background color (used with MONOx formats)
gdc_color_t bg_color;
} gdc_bitmap_ref_t;
#endif // GDC_BITMAP_H

@ -0,0 +1,128 @@
/*
* 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 GDC_CLIP_H
#define GDC_CLIP_H
#include <common.h>
#include "gdc.h"
typedef struct {
int16_t dst_x;
int16_t dst_y;
int16_t fg_x;
int16_t fg_y;
int16_t bg_x;
int16_t bg_y;
int16_t width;
int16_t height;
} gdc_clip_t;
static inline gdc_clip_t gdc_clip(gdc_rect_t dst, gdc_size_t size,
const gdc_bitmap_ref_t* fg,
const gdc_bitmap_ref_t* bg) {
int16_t dst_x = dst.x0;
int16_t dst_y = dst.y0;
int16_t fg_x = 0;
int16_t fg_y = 0;
int16_t bg_x = 0;
int16_t bg_y = 0;
if (fg != NULL) {
fg_x += fg->offset.x;
fg_y += fg->offset.y;
// Normalize negative x-offset of fg bitmap
if (fg_x < 0) {
dst_x -= fg_x;
bg_x -= fg_x;
fg_x = 0;
}
// Normalize negative y-offset of fg bitmap
if (fg_y < 0) {
dst_y -= fg_y;
bg_y -= fg_y;
fg_y = 0;
}
}
if (bg != NULL) {
bg_x += bg->offset.x;
bg_y += bg->offset.y;
// Normalize negative x-offset of bg bitmap
if (bg_x < 0) {
dst_x -= bg_x;
fg_x -= bg_x;
bg_x = 0;
}
// Normalize negative y-offset of bg bitmap
if (bg_y < 0) {
dst_y -= bg_y;
fg_y -= bg_y;
bg_y = 0;
}
}
// Normalize negative top-left of destination rectangle
if (dst_x < 0) {
fg_x -= dst_x;
bg_x -= dst_x;
dst_x = 0;
}
if (dst_y < 0) {
fg_y -= dst_y;
bg_y -= dst_y;
dst_y = 0;
}
// Calculate dimension of effective rectangle
int16_t width = MIN(size.x, dst.x1) - dst_x;
int16_t height = MIN(size.y, dst.y1) - dst_y;
if (fg != NULL) {
width = MIN(width, fg->bitmap->size.x - fg_x);
height = MIN(height, fg->bitmap->size.y - fg_y);
}
if (bg != NULL) {
width = MIN(width, bg->bitmap->size.x - bg_x);
height = MIN(height, bg->bitmap->size.y - bg_y);
}
gdc_clip_t clip = {
.dst_x = dst_x,
.dst_y = dst_y,
.fg_x = fg_x,
.fg_y = fg_y,
.bg_x = bg_x,
.bg_y = bg_y,
.width = width,
.height = height,
};
return clip;
}
#endif // GDC_CLIP_H

@ -0,0 +1,49 @@
/*
* 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 "gdc_color.h"
#include "colors.h"
const gdc_color16_t* gdc_color16_gradient_a4(gdc_color_t fg_color,
gdc_color_t bg_color) {
static gdc_color16_t cache[16] = {0};
if (gdc_color_to_color16(bg_color) != cache[0] ||
gdc_color_to_color16(fg_color) != cache[15]) {
for (int alpha = 0; alpha < 16; alpha++) {
cache[alpha] = gdc_color16_blend_a4(fg_color, bg_color, alpha);
}
}
return (const gdc_color16_t*)&cache[0];
}
const gdc_color32_t* gdc_color32_gradient_a4(gdc_color_t fg_color,
gdc_color_t bg_color) {
static gdc_color32_t cache[16] = {0};
if (bg_color != gdc_color32_to_color(cache[0]) ||
fg_color != gdc_color32_to_color(cache[15])) {
for (int alpha = 0; alpha < 16; alpha++) {
cache[alpha] = gdc_color32_blend_a4(fg_color, bg_color, alpha);
}
}
return (const gdc_color32_t*)&cache[0];
}

@ -0,0 +1,221 @@
/*
* 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 GDC_COLOR_H
#define GDC_COLOR_H
#include <stdint.h>
#define GDC_COLOR_16BIT
// #define GDC_COLOR_32BIT
// Color in RGB565 format
//
// |15 8 | 7 0|
// |---------------------------------|
// |r r r r r g g g | g g g b b b b b|
// |---------------------------------|
typedef uint16_t gdc_color16_t;
// Color in RGBA8888 format
//
// |31 24 |23 16 |15 8 | 7 0 |
// |----------------------------------------------------------------------|
// |a a a a a a a a | r r r r r r r r | g g g g g g g g | b b b b b b b b |
// |----------------------------------------------------------------------|
//
typedef uint32_t gdc_color32_t;
#ifdef GDC_COLOR_16BIT
#define gdc_color_t gdc_color16_t
#define gdc_color_to_color16(c) (c)
#define gdc_color16_to_color(c) (c)
#define gdc_color_to_color32(c) (gdc_color16_to_color32(c))
#define gdc_color32_to_color(c) (gdc_color32_to_color16(c))
#elif GDC_COLOR_32BIT
#define gdc_color_t gdc_color32_t
#define gdc_color_to_color16(c) (gdc_color32_to_color16(c))
#define gdc_color16_to_color(c) (gdc_color16_to_color32(c))
#define gdc_color_to_color32(c) (c)
#define gdc_color32_to_color(c) (c)
#else
#error "GDC_COLOR_16BIT/32BIT not specified"
#endif
// Constructs a 16-bit color from the given red (r),
// green (g), and blue (b) values in the range 0..255
static inline gdc_color16_t gdc_color16_rgb(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | ((b & 0xF8U) >> 3);
}
// Constructs a 32-bit color from the given red (r),
// green (g), and blue (b) values in the range 0..255.
// Alpha is set to 255.
static inline gdc_color32_t gdc_color32_rgb(uint8_t r, uint8_t g, uint8_t b) {
return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
// Converts a 16-bit color to a 32-bit color; alpha is set to 255
static inline gdc_color32_t gdc_color16_to_color32(gdc_color16_t color) {
uint32_t r = (color & 0xF800) >> 8;
uint32_t g = (color & 0x07E0) >> 3;
uint32_t b = (color & 0x001F) << 3;
r |= (r >> 5);
g |= (g >> 6);
b |= (b >> 5);
return (0xFFU << 24) | (r << 16) | (g << 8) | b;
}
// Converts 32-bit color to 16-bit color, alpha is ignored
static inline gdc_color16_t gdc_color32_to_color16(gdc_color32_t color) {
uint16_t r = (color & 0x00F80000) >> 8;
uint16_t g = (color & 0x0000FC00) >> 5;
uint16_t b = (color & 0x000000F8) >> 3;
return r | g | b;
}
#ifdef GDC_COLOR_16BIT
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 16-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gdc_color16_t gdc_color16_blend_a4(gdc_color16_t fg,
gdc_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0xF800) >> 11;
uint16_t bg_r = (bg & 0xF800) >> 11;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x07E0) >> 5;
uint16_t bg_g = (bg & 0x07E0) >> 5;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x001F) >> 0;
uint16_t bg_b = (bg & 0x001F) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return (r << 11) | (g << 5) | b;
}
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 32-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gdc_color32_t gdc_color32_blend_a4(gdc_color16_t fg,
gdc_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0xF800) >> 8;
fg_r |= fg_r >> 5;
uint16_t bg_r = (bg & 0xF800) >> 8;
bg_r |= bg_r >> 5;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x07E0) >> 2;
fg_g |= fg_g >> 6;
uint16_t bg_g = (bg & 0x07E0) >> 2;
bg_g |= bg_g >> 6;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x001F) << 3;
fg_b |= fg_b >> 5;
uint16_t bg_b = (bg & 0x001F) << 3;
bg_b |= bg_b >> 5;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
#elif GDC_COLOR_32BIT
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 16-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gdc_color16_t gdc_color16_blend_a4(gdc_color32_t fg,
gdc_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0x00FF0000) >> 16;
uint16_t bg_r = (bg & 0x00FF0000) >> 16;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x0000FF00) >> 8;
uint16_t bg_g = (bg & 0x0000FF00) >> 8;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x000000FF) >> 0;
uint16_t bg_b = (bg & 0x000000FF) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return gdc_color16_rgb(r, g, b)
}
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 32-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gdc_color32_t gdc_color32_blend_a4(gdc_color32_t fg,
gdc_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0x00FF0000) >> 16;
uint16_t bg_r = (bg & 0x00FF0000) >> 16;
uint16_t r = (fg_r * alpha + (bg_r * (15 - alpha))) / 15;
uint16_t fg_g = (fg & 0x0000FF00) >> 8;
uint16_t bg_g = (bg & 0x0000FF00) >> 8;
uint16_t g = (fg_g * alpha + (bg_g * (15 - alpha))) / 15;
uint16_t fg_b = (fg & 0x000000FF) >> 0;
uint16_t bg_b = (bg & 0x000000FF) >> 0;
uint16_t b = (fg_b * alpha + (bg_b * (15 - alpha))) / 15;
return gdc_color32_rgb(r, g, b);
}
#else
#error "GDC_COLOR_16BIT/32BIT not specified"
#endif
// Returns a gradient as an array of 16 consecutive 16-bit colors
//
// Each element in the array represents a color, with retval[0] being
// the background (bg) color and retval[15] the foreground (fg) color
const gdc_color16_t* gdc_color16_gradient_a4(gdc_color_t fg, gdc_color_t bg);
// Returns a gradient as an array of 16 consecutive 32-bit colors
//
// Each element in the array represents a color, with retval[0] being
// the background (bg) color and retval[15] the foreground (fg) color
const gdc_color32_t* gdc_color32_gradient_a4(gdc_color_t fg, gdc_color_t bg);
#endif // TREZORHAL_GDC_COLOR_H

@ -0,0 +1,237 @@
/*
* 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 "gdc_core.h"
#include "gdc_clip.h"
#include "dma2d.h"
void gdc_release(gdc_t* gdc) {
if (gdc != NULL) {
gdc_wait_for_pending_ops(gdc);
gdc_vmt_t* vmt = *(gdc_vmt_t**)gdc;
if (vmt != NULL && vmt->release != NULL) {
vmt->release(gdc);
}
}
}
gdc_size_t gdc_get_size(const gdc_t* gdc) {
if (gdc != NULL) {
gdc_vmt_t* vmt = *(gdc_vmt_t**)gdc;
if (vmt != NULL && vmt->get_bitmap != NULL) {
return vmt->get_bitmap((gdc_t*)gdc)->size;
}
}
return gdc_size(0, 0);
}
gdc_format_t gdc_get_format(const gdc_t* gdc) {
if (gdc != NULL) {
gdc_vmt_t* vmt = *(gdc_vmt_t**)gdc;
if (vmt != NULL && vmt->get_bitmap != NULL) {
return vmt->get_bitmap((gdc_t*)gdc)->format;
}
}
return GDC_FORMAT_UNKNOWN;
}
void gdc_wait_for_pending_ops(gdc_t* gdc) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (gdc != NULL) {
dma2d_wait(gdc);
}
#endif
}
void gdc_set_window_hint(gdc_t* gdc, gdc_rect_t rect) {
if (gdc != NULL) {
gdc_vmt_t* vmt = *(gdc_vmt_t**)gdc;
gdc_bitmap_t* bitmap = (gdc_bitmap_t*)gdc;
gdc_clip_t clip = gdc_clip(rect, bitmap->size, NULL, NULL);
dma2d_params_t dp = {
// Destination rectangle
.height = clip.height,
.width = clip.width,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
};
// xgdc_wait_for_dma();
return vmt->set_window_hint(gdc, &dp);
}
}
bool gdc_fill_rect(gdc_t* gdc, gdc_rect_t rect, gdc_color_t color) {
if (gdc != NULL) {
gdc_vmt_t* vmt = *(gdc_vmt_t**)gdc;
gdc_bitmap_t* bitmap = (gdc_bitmap_t*)gdc;
gdc_clip_t clip = gdc_clip(rect, bitmap->size, NULL, NULL);
if (clip.width <= 0 || clip.height <= 0) {
return true;
}
dma2d_params_t dp = {
// Destination rectangle
.height = clip.height,
.width = clip.width,
.dst_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.dst_y,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
.dst_stride = bitmap->stride,
// Source A (foreground)
.srca_fg = color,
.cpu_only = (bitmap->attrs & GDC_BITMAP_NO_DMA) != 0,
};
gdc_wait_for_pending_ops(gdc);
if (vmt->fill != NULL) {
return vmt->fill(gdc, &dp);
}
}
return false;
}
bool gdc_draw_bitmap(gdc_t* gdc, gdc_rect_t rect, const gdc_bitmap_ref_t* fg) {
if (gdc != NULL) {
gdc_vmt_t* vmt = *(gdc_vmt_t**)gdc;
gdc_bitmap_t* bitmap = vmt->get_bitmap(gdc);
gdc_clip_t clip = gdc_clip(rect, bitmap->size, fg, NULL);
if (clip.width <= 0 || clip.height <= 0) {
return true;
}
dma2d_params_t dp = {
// Destination rectangle
.height = clip.height,
.width = clip.width,
.dst_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.dst_y,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
.dst_stride = bitmap->stride,
// Source A (foreground)
.srca_row = (uint8_t*)fg->bitmap->ptr + fg->bitmap->stride * clip.fg_y,
.srca_x = clip.fg_x,
.srca_stride = fg->bitmap->stride,
.srca_fg = fg->fg_color,
.srca_bg = fg->bg_color,
.cpu_only = (bitmap->attrs & GDC_BITMAP_NO_DMA) ||
(fg->bitmap->attrs & GDC_BITMAP_NO_DMA),
};
gdc_wait_for_pending_ops(gdc);
if (fg->bitmap->format == GDC_FORMAT_MONO4) {
if (vmt->copy_mono4 != NULL) {
return vmt->copy_mono4(gdc, &dp);
}
} else if (fg->bitmap->format == GDC_FORMAT_RGB565) {
if (vmt->copy_rgb565 != NULL) {
return vmt->copy_rgb565(gdc, &dp);
}
} else if (fg->bitmap->format == GDC_FORMAT_RGBA8888) {
if (vmt->copy_rgba8888 != NULL) {
return vmt->copy_rgba8888(gdc, &dp);
}
}
}
return false;
}
bool gdc_draw_blended(gdc_t* gdc, gdc_rect_t rect, const gdc_bitmap_ref_t* fg,
const gdc_bitmap_ref_t* bg) {
if (gdc != NULL) {
gdc_vmt_t* vmt = *(gdc_vmt_t**)gdc;
gdc_bitmap_t* bitmap = vmt->get_bitmap(gdc);
gdc_clip_t clip = gdc_clip(rect, bitmap->size, fg, bg);
if (clip.width <= 0 || clip.height <= 0) {
return true;
}
dma2d_params_t dp = {
// Destination rectangle
.height = clip.height,
.width = clip.width,
.dst_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.dst_y,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
.dst_stride = bitmap->stride,
// Source A (foreground)
.srca_row = (uint8_t*)fg->bitmap->ptr + fg->bitmap->stride * clip.fg_y,
.srca_x = clip.fg_x,
.srca_stride = fg->bitmap->stride,
.srca_fg = fg->fg_color,
// Source B (background)
.srcb_row = (uint8_t*)bg->bitmap->ptr + bg->bitmap->stride * clip.bg_y,
.srcb_x = clip.bg_x,
.srcb_stride = bg->bitmap->stride,
.srcb_fg = bg->fg_color,
.srcb_bg = bg->bg_color,
.cpu_only = (bitmap->attrs & GDC_BITMAP_NO_DMA) ||
(fg->bitmap->attrs & GDC_BITMAP_NO_DMA) ||
(fg->bitmap->attrs & GDC_BITMAP_NO_DMA),
};
gdc_wait_for_pending_ops(gdc);
if (fg->bitmap->format == GDC_FORMAT_MONO4 &&
bg->bitmap->format == GDC_FORMAT_RGB565) {
if (vmt->blend_mono4_rgb565 != NULL) {
return vmt->blend_mono4_rgb565(gdc, &dp);
}
} else if (fg->bitmap->format == GDC_FORMAT_MONO4 &&
bg->bitmap->format == GDC_FORMAT_RGBA8888) {
if (vmt->blend_mono4_rgba8888 != NULL) {
return vmt->blend_mono4_rgba8888(gdc, &dp);
}
} else if (fg->bitmap->format == GDC_FORMAT_MONO4 &&
bg->bitmap->format == GDC_FORMAT_MONO4) {
if (vmt->blend_mono4_mono4 != NULL) {
return vmt->blend_mono4_mono4(gdc, &dp);
}
}
}
return false;
}

@ -0,0 +1,122 @@
/*
* 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 GDC_CORE_H
#define GDC_CORE_H
#include "gdc_bitmap.h"
#include "gdc_color.h"
#include "gdc_dma2d.h"
#include "gdc_geom.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// ------------------------------------------------------------------------
// GDC - Graphics Device Context
//
typedef void gdc_t;
// ------------------------------------------------------------------------
// GDC (Graphic Device Context) Virtual Method Table
//
// GDC structure is implementation specific. Only requirement is that
// it starts with a field of type gdc_vmt_t* vmt.
//
// typedef struct
// {
// gdc_vmt_t* vmt;
//
// // GDC specific data
//
// } gdc_impl_specific_t;
//
typedef void (*gdc_release_t)(gdc_t* gdc);
typedef gdc_bitmap_t* (*gdc_get_bitmap_t)(gdc_t* gdc);
typedef void (*gdc_set_window_hint_t)(gdc_t* gdc, dma2d_params_t* params);
typedef bool (*gdc_fill_t)(gdc_t* gdc, dma2d_params_t* params);
typedef bool (*gdc_copy_mono4_t)(gdc_t* gdc, dma2d_params_t* params);
typedef bool (*gdc_copy_rgb565_t)(gdc_t* gdc, dma2d_params_t* params);
typedef bool (*gdc_copy_rgba8888_t)(gdc_t* gdc, dma2d_params_t* params);
typedef bool (*gdc_blend_mono4_mono4_t)(gdc_t* gdc, dma2d_params_t* params);
typedef bool (*gdc_blend_mono4_rgb565_t)(gdc_t* gdc, dma2d_params_t* params);
typedef bool (*gdc_blend_mono4_rgba8888_t)(gdc_t* gdc, dma2d_params_t* params);
// GDC virtual methods
struct gdc_vmt {
gdc_release_t release;
gdc_get_bitmap_t get_bitmap;
gdc_set_window_hint_t set_window_hint;
gdc_fill_t fill;
gdc_copy_mono4_t copy_mono4;
gdc_copy_rgb565_t copy_rgb565;
gdc_copy_rgba8888_t copy_rgba8888;
gdc_blend_mono4_mono4_t blend_mono4_mono4;
gdc_blend_mono4_rgb565_t blend_mono4_rgb565;
gdc_blend_mono4_rgba8888_t blend_mono4_rgba8888;
};
// ------------------------------------------------------------------------
// GDC (Graphic Device Context) Public API
// Releases reference to GDC
void gdc_release(gdc_t* gdc);
// Gets size of GDC bounding rectangle
gdc_size_t gdc_get_size(const gdc_t* gdc);
// Returns the bitmap format associated with the specified GDC
gdc_format_t gdc_get_format(const gdc_t* gdc);
// Wait for pending DMA operation applied on this GDC
// (used by high level code before accessing GDC's framebuffer/bitmap)
void gdc_wait_for_pending_ops(gdc_t* gdc);
// Sets the hint for the driver regarding the target windows beeing filled
// Some drivers may utilize this hint to optimize access to the
// display framebuffer
void gdc_set_window_hint(gdc_t* gdc, gdc_rect_t rect);
// Fills a rectangle with a specified color
bool gdc_fill_rect(gdc_t* gdc, gdc_rect_t rect, gdc_color_t color);
// Draws a bitmap into the specified rectangle. The destination rectangle
// may not be fully filled if the source bitmap is smaller then destination
// rectangle or if the bitmap is translated by an offset partially or completely
// outside the destination rectangle.
bool gdc_draw_bitmap(gdc_t* gdc, gdc_rect_t rect, const gdc_bitmap_ref_t* src);
// Draw a combination of two bitmaps into the specified rectangle
// Copies blended pixels of foreground(fg) over the background(bg)
// to the GDC window. If fg and bg bitmaps have different dimension or
// are translated by different offset, only the intersection of them is drawn.
bool gdc_draw_blended(gdc_t* gdc, gdc_rect_t rect, const gdc_bitmap_ref_t* fg,
const gdc_bitmap_ref_t* bg);
// ------------------------------------------------------------------------
// this will be defined elsewhere::
// Gets GDC for the hardware display
// Returns NULL if display gdc was already acquired and not released
gdc_t* display_acquire_gdc(void);
#endif // GDC_CORE_H

@ -0,0 +1,57 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GDC_DMA2D_H
#define GDC_DMA2D_H
#include <stdbool.h>
#include <stdint.h>
#include "gdc_color.h"
typedef struct {
// Destination rectangle
// (this fill are used always)
uint16_t height;
uint16_t width;
void* dst_row;
uint16_t dst_x;
uint16_t dst_y;
uint16_t dst_stride;
// Source A (foreground) - only for copying
// (srca_fg color for used for filling)
void* srca_row;
uint16_t srca_x;
uint16_t srca_stride;
gdc_color_t srca_fg;
gdc_color_t srca_bg;
// Source B (background) - only for blending
void* srcb_row;
uint16_t srcb_x;
uint16_t srcb_stride;
gdc_color_t srcb_fg;
gdc_color_t srcb_bg;
// Manipulate data by CPU only (memory is not accessible by DMA)
bool cpu_only;
} dma2d_params_t;
#endif // GDC_DMA2D_H

@ -0,0 +1,88 @@
/*
* 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 GDC_GEOM_H
#define GDC_GEOM_H
// ------------------------------------------------------------------------
// GDC Rectangle
//
// used for simplified manipulation with rectangle coordinates
typedef struct {
int16_t x0;
int16_t y0;
int16_t x1;
int16_t y1;
} gdc_rect_t;
// Creates a rectangle from top-left coordinates and dimensions
static inline gdc_rect_t gdc_rect_wh(int16_t x, int16_t y, int16_t w,
int16_t h) {
gdc_rect_t rect = {
.x0 = x,
.y0 = y,
.x1 = x + w,
.y1 = y + h,
};
return rect;
}
// Creates a rectangle from top-left and bottom-right coordinates
static inline gdc_rect_t gdc_rect(int16_t x0, int16_t y0, int16_t x1,
int16_t y1) {
gdc_rect_t rect = {
.x0 = x0,
.y0 = y0,
.x1 = x1,
.y1 = y1,
};
return rect;
}
// ------------------------------------------------------------------------
// GDC Size
//
// used for simplified manipulation with size of objects
typedef struct {
int16_t x;
int16_t y;
} gdc_size_t;
// Creates a rectangle from top-left and bottom-right coordinates
static inline gdc_size_t gdc_size(int16_t x, int16_t y) {
gdc_size_t size = {
.x = x,
.y = y,
};
return size;
}
// ------------------------------------------------------------------------
// GDC Offset
//
// used for simplified manipulation with size of objects
typedef gdc_size_t gdc_offset_t;
#endif // GDC_GEOM_H

@ -0,0 +1,112 @@
/*
* 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 "gdc_heap.h"
#include <stdbool.h>
#include <stdint.h>
typedef struct gdc_heap_free_item gdc_heap_free_item_t;
typedef struct heap_block {
size_t size;
struct heap_block* next;
} heap_block_t;
typedef struct {
heap_block_t data[(24 * 1024) / sizeof(heap_block_t)];
heap_block_t* free;
bool initialized;
} heap_t;
heap_t gdc_heap;
void* gdc_heap_alloc(size_t size) {
heap_t* heap = &gdc_heap;
if (!heap->initialized) {
heap->data[0].size = sizeof(heap->data);
heap->data[0].next = NULL;
heap->free = &heap->data[0];
heap->initialized = true;
}
heap_block_t* block = heap->free;
heap_block_t** prev = &heap->free;
size = (size + 7) & ~0x7;
while (block != NULL) {
if (block->size == size) {
// perfect fit
*prev = block->next;
return block;
} else if (block->size > size) {
// split block
heap_block_t* new_block = (heap_block_t*)((uintptr_t)block + size);
new_block->size = block->size - size;
new_block->next = block->next;
*prev = new_block;
return block;
}
prev = &block->next;
block = block->next;
}
return NULL;
}
void gdc_heap_free(void* ptr, size_t size) {
heap_t* heap = &gdc_heap;
size = (size + 7) & ~0x7;
heap_block_t* next = heap->free;
heap_block_t* prev = NULL;
while (next != NULL && (uintptr_t)ptr >= (uintptr_t)next) {
prev = next;
next = next->next;
}
heap_block_t* block = (heap_block_t*)ptr;
block->size = size;
block->next = next;
if (prev != NULL) {
if ((uintptr_t)prev + prev->size == (uintptr_t)ptr) {
// colaesce with the previous block
prev->size += size;
block = prev;
} else {
prev->next = block;
}
} else {
heap->free = block;
}
if (block->next != NULL) {
if ((uintptr_t)block + block->size == (uintptr_t)block->next) {
// colaesce with the next block
block->size += block->next->size;
block->next = block->next->next;
}
}
}

@ -0,0 +1,29 @@
/*
* 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 GDC_HEAP_H
#define GDC_HEAP_H
#include <stdio.h>
void* gdc_heap_alloc(size_t size);
void gdc_heap_free(void* ptr, size_t size);
#endif // GDC_CORE_H

@ -0,0 +1,183 @@
/*
* 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 "gdc_core.h"
#include "gdc_dma2d.h"
#include <string.h>
#if USE_DMA2D
#include "dma2d.h"
#endif
static void gdc_rgb565_release(gdc_t* gdc) {
/* gdc_bitmap_t* bitmap = (gdc_bitmap_t*) gdc;
if (bitmap->release != NULL) {
bitmap->release(bitmap->context);
}*/
}
static gdc_bitmap_t* gdc_rgb565_get_bitmap(gdc_t* gdc) {
return (gdc_bitmap_t*)gdc;
}
/*static*/ bool gdc_rgb565_fill(gdc_t* gdc, dma2d_params_t* params) {
dma2d_params_t p = *params;
uint16_t* dst_ptr = (uint16_t*)p.dst_row + p.dst_x;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
dst_ptr[x] = p.srca_fg;
}
dst_ptr += p.dst_stride / sizeof(*dst_ptr);
}
return true;
}
/*static*/ bool gdc_rgb565_copy_mono4(gdc_t* gdc, dma2d_params_t* params) {
dma2d_params_t p = *params;
const gdc_color16_t* gradient = gdc_color16_gradient_a4(p.srca_fg, p.srca_bg);
uint16_t* dst_ptr = (uint16_t*)p.dst_row + p.dst_x;
uint8_t* srca_row = (uint8_t*)p.srca_row;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
uint8_t fg_data = srca_row[(x + p.srca_x) / 2];
uint8_t fg_lum = (x + p.srca_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
dst_ptr[x] = gradient[fg_lum];
}
dst_ptr += p.dst_stride / sizeof(*dst_ptr);
srca_row += p.srca_stride / sizeof(*srca_row);
}
return true;
}
/*static*/ bool gdc_rgb565_copy_rgb565(gdc_t* gdc, dma2d_params_t* params) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!params->cpu_only) {
return dma2d_rgb565_copy_rgb565(gdc, params);
} else
#endif
{
dma2d_params_t p = *params;
uint16_t* dst_ptr = (uint16_t*)p.dst_row + p.dst_x;
uint16_t* srca_ptr = (uint16_t*)p.srca_row + p.srca_x;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
dst_ptr[x] = srca_ptr[x];
}
dst_ptr += p.dst_stride / sizeof(*dst_ptr);
srca_ptr += p.srca_stride / sizeof(*srca_ptr);
}
return true;
}
}
static bool gdc_rgb565_blend_mono4_mono4(gdc_t* gdc, dma2d_params_t* params) {
dma2d_params_t p = *params;
const gdc_color16_t* gradient = gdc_color16_gradient_a4(p.srcb_fg, p.srcb_bg);
uint16_t* dst_ptr = (uint16_t*)p.dst_row + p.dst_x;
uint8_t* srca_row = (uint8_t*)p.srca_row;
uint8_t* srcb_row = (uint8_t*)p.srcb_row;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
uint8_t fg_data = srca_row[(x + p.srca_x) / 2];
uint8_t fg_alpha = (x + p.srca_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
uint8_t bg_data = srcb_row[(x + p.srcb_x) / 2];
uint8_t bg_lum = (x + p.srcb_x) & 1 ? bg_data >> 4 : bg_data & 0x0F;
dst_ptr[x] = gdc_color16_blend_a4(
p.srca_fg, gdc_color16_to_color(gradient[bg_lum]), fg_alpha);
}
dst_ptr += p.dst_stride / sizeof(*dst_ptr);
srca_row += p.srca_stride / sizeof(*srca_row);
srcb_row += p.srcb_stride / sizeof(*srcb_row);
}
return true;
}
/*static*/ bool gdc_rgb565_blend_mono4_rgb565(gdc_t* gdc,
dma2d_params_t* params) {
dma2d_params_t p = *params;
uint16_t* dst_ptr = (uint16_t*)p.dst_row + p.dst_x;
uint8_t* srca_row = (uint8_t*)p.srca_row;
uint16_t* srcb_ptr = (uint16_t*)p.srcb_row + p.srcb_x;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
uint8_t fg_data = srca_row[(x + p.srca_x) / 2];
uint8_t fg_alpha = (x + p.srca_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
dst_ptr[x] = gdc_color16_blend_a4(
p.srca_fg, gdc_color16_to_color(srcb_ptr[x]), fg_alpha);
}
dst_ptr += p.dst_stride / sizeof(*dst_ptr);
srca_row += p.srca_stride / sizeof(*srca_row);
srcb_ptr += p.srcb_stride / sizeof(*srcb_ptr);
}
return true;
}
gdc_bitmap_t gdc_bitmap_rgb565(void* data_ptr, size_t stride, gdc_size_t size,
uint8_t attrs) {
static const gdc_vmt_t gdc_rgb565 = {
.release = gdc_rgb565_release,
.set_window_hint = NULL,
.get_bitmap = gdc_rgb565_get_bitmap,
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
.fill = dma2d_rgb565_fill,
.copy_mono4 = dma2d_rgb565_copy_mono4,
.copy_rgb565 = gdc_rgb565_copy_rgb565, // dma2d_rgb565_copy_rgb565,
.copy_rgba8888 = NULL,
.blend_mono4_mono4 = gdc_rgb565_blend_mono4_mono4,
.blend_mono4_rgb565 = dma2d_rgb565_blend_mono4_rgb565,
.blend_mono4_rgba8888 = NULL,
#else
.fill = gdc_rgb565_fill,
.copy_mono4 = gdc_rgb565_copy_mono4,
.copy_rgb565 = gdc_rgb565_copy_rgb565,
.copy_rgba8888 = NULL,
.blend_mono4_mono4 = gdc_rgb565_blend_mono4_mono4,
.blend_mono4_rgb565 = gdc_rgb565_blend_mono4_rgb565,
.blend_mono4_rgba8888 = NULL,
#endif
};
gdc_bitmap_t bitmap = {.vmt = &gdc_rgb565,
.ptr = data_ptr,
.stride = stride,
.size = size,
.format = GDC_FORMAT_RGB565,
.attrs = attrs};
return bitmap;
}

@ -0,0 +1,61 @@
/*
* 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 "gdc_core.h"
#include <string.h>
static void gdc_rgba8888_release(gdc_t* gdc) {
/* gdc_bitmap_t* bitmap = (gdc_bitmap_t*) gdc;
if (bitmap->release != NULL) {
bitmap->release(bitmap->context);
}*/
}
static gdc_bitmap_t* gdc_rgba8888_get_bitmap(gdc_t* gdc) {
return (gdc_bitmap_t*)gdc;
}
gdc_bitmap_t gdc_bitmap_rgba8888(void* data_ptr, size_t stride, gdc_size_t size,
uint8_t attrs) {
static const gdc_vmt_t gdc_rgba8888 = {
.release = gdc_rgba8888_release,
.set_window_hint = NULL,
.get_bitmap = gdc_rgba8888_get_bitmap,
.fill = NULL, // dma2d_rgba8888_fill,
.copy_mono4 = NULL, // dma2d_rgba8888_copy_mono4,
.copy_rgb565 = NULL, // dma2d_rgba8888_copy_rgb565,
.copy_rgba8888 = NULL, // dma2d_rgba8888_copy_rgba8888,
.blend_mono4_mono4 = NULL, // dma2d_rgba8888_blend_mono4_mono4,
.blend_mono4_rgb565 = NULL,
.blend_mono4_rgba8888 = NULL, // dma2d_rgba8888_blend_mono4_rgba8888,
};
gdc_bitmap_t bitmap = {
.vmt = &gdc_rgba8888,
.ptr = data_ptr,
.stride = stride,
.size = size,
.format = GDC_FORMAT_RGBA8888,
.attrs = attrs,
};
return bitmap;
}

@ -0,0 +1,178 @@
/*
* 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 "gdc_text.h"
#include "fonts/fonts.h"
#if TREZOR_FONT_BPP == 1
#define GLYPH_FORMAT GDC_FORMAT_MONO1
#define GLYPH_STRIDE(w) (((w) + 7) / 8)
#elif TREZOR_FONT_BPP == 2
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GDC_FORMAT_MONO2
#define GLYPH_STRIDE(w) (((w) + 3) / 4)
#elif TREZOR_FONT_BPP == 4
#define GLYPH_FORMAT GDC_FORMAT_MONO4
#define GLYPH_STRIDE(w) (((w) + 1) / 2)
#elif TREZOR_FONT_BPP == 8
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GDC_FORMAT_MONO8
#define GLYPH_STRIDE(w) (w)
#else
#error Unsupported TREZOR_FONT_BPP value
#endif
#define GLYPH_WIDTH(g) ((g)[0])
#define GLYPH_HEIGHT(g) ((g)[1])
#define GLYPH_ADVANCE(g) ((g)[2])
#define GLYPH_BEARING_X(g) ((g)[3])
#define GLYPH_BEARING_Y(g) ((g)[4])
#define GLYPH_DATA(g) ((void*)&(g)[5])
bool gdc_draw_opaque_text(gdc_t* gdc, gdc_rect_t rect, const char* text,
size_t maxlen, const gdc_text_attr_t* attr) {
if (text == NULL) {
return false;
}
gdc_bitmap_t glyph_bitmap;
glyph_bitmap.vmt = NULL;
glyph_bitmap.format = GLYPH_FORMAT;
gdc_bitmap_ref_t glyph_ref;
glyph_ref.bitmap = &glyph_bitmap;
glyph_ref.fg_color = attr->fg_color;
glyph_ref.bg_color = attr->bg_color;
int max_height = font_max_height(attr->font);
int baseline = font_baseline(attr->font);
int offset_x = attr->offset.x;
if (offset_x < 0) {
rect.x0 -= attr->offset.x;
offset_x = 0;
}
for (int i = 0; i < maxlen; i++) {
uint8_t ch = (uint8_t)text[i];
if (ch == 0 || rect.x0 >= rect.x1) {
break;
}
const uint8_t* glyph = font_get_glyph(attr->font, ch);
if (glyph == NULL) {
continue;
}
if (offset_x >= GLYPH_ADVANCE(glyph)) {
offset_x -= GLYPH_ADVANCE(glyph);
continue;
}
glyph_bitmap.ptr = GLYPH_DATA(glyph);
glyph_bitmap.stride = GLYPH_STRIDE(GLYPH_WIDTH(glyph));
glyph_bitmap.size.x = GLYPH_WIDTH(glyph);
glyph_bitmap.size.y = GLYPH_HEIGHT(glyph);
glyph_ref.offset.x = attr->offset.x - GLYPH_BEARING_X(glyph);
glyph_ref.offset.y =
attr->offset.y - (max_height - baseline - GLYPH_BEARING_Y(glyph));
if (!gdc_draw_bitmap(gdc, rect, &glyph_ref)) {
return false;
}
rect.x0 += GLYPH_ADVANCE(glyph) - offset_x;
offset_x = 0;
}
return true;
}
bool gdc_draw_blended_text(gdc_t* gdc, gdc_rect_t rect, const char* text,
size_t maxlen, const gdc_text_attr_t* attr,
const gdc_bitmap_ref_t* bg) {
if (text == NULL) {
return false;
}
gdc_bitmap_t glyph_bitmap;
glyph_bitmap.vmt = NULL;
glyph_bitmap.format = GLYPH_FORMAT;
gdc_bitmap_ref_t glyph_ref;
glyph_ref.bitmap = &glyph_bitmap;
glyph_ref.fg_color = attr->fg_color;
glyph_ref.bg_color = attr->bg_color;
gdc_bitmap_ref_t bg_ref = *bg;
int max_height = font_max_height(attr->font);
int baseline = font_baseline(attr->font);
int offset_x = attr->offset.x;
if (offset_x < 0) {
rect.x0 -= attr->offset.x;
offset_x = 0;
}
for (int i = 0; i < maxlen; i++) {
uint8_t ch = (uint8_t)text[i];
if (ch == 0 || rect.x0 >= rect.x1) {
break;
}
const uint8_t* glyph = font_get_glyph(attr->font, ch);
if (glyph == NULL) {
continue;
}
if (offset_x >= GLYPH_ADVANCE(glyph)) {
offset_x -= GLYPH_ADVANCE(glyph);
continue;
} else {
}
glyph_bitmap.ptr = GLYPH_DATA(glyph);
glyph_bitmap.stride = GLYPH_STRIDE(GLYPH_WIDTH(glyph));
glyph_bitmap.size.x = GLYPH_WIDTH(glyph);
glyph_bitmap.size.y = GLYPH_HEIGHT(glyph);
glyph_ref.offset.x = offset_x - GLYPH_BEARING_X(glyph);
glyph_ref.offset.y =
attr->offset.y - (max_height - baseline - GLYPH_BEARING_Y(glyph));
if (!gdc_draw_blended(gdc, rect, &glyph_ref, &bg_ref)) {
return false;
}
rect.x0 += GLYPH_ADVANCE(glyph) - offset_x;
bg_ref.offset.x += GLYPH_ADVANCE(glyph) - offset_x;
offset_x = 0;
}
return true;
}

@ -0,0 +1,49 @@
/*
* 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 GDC_TEXT_H
#define GDC_TEXT_H
#include "gdc_color.h"
#include "gdc_core.h"
#include "gdc_geom.h"
#include <stddef.h>
#include <stdint.h>
typedef struct {
int font;
gdc_color_t fg_color;
gdc_color_t bg_color;
// TODO: horizontal/vertical alignment??
// TODO: extra offset???
gdc_offset_t offset;
} gdc_text_attr_t;
bool gdc_draw_opaque_text(gdc_t* gdc, gdc_rect_t rect, const char* text,
size_t maxlen, const gdc_text_attr_t* attr);
bool gdc_draw_blended_text(gdc_t* gdc, gdc_rect_t rect, const char* text,
size_t maxlen, const gdc_text_attr_t* attr,
const gdc_bitmap_ref_t* bg);
#endif // GDC_TEXT_H

@ -0,0 +1,257 @@
/*
* 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 "gdc_wnd565.h"
#include <string.h>
#include "display.h"
static void gdc_wnd565_release(gdc_t* gdc) {
// gdc_wnd565_t* wnd = (gdc_wnd565_t*)gdc;
// if (wnd->config.release != NULL) {
// wnd->config.release(wnd->config.context);
// }
}
static gdc_bitmap_t* gdc_wnd565_get_bitmap(gdc_t* gdc) {
return &((gdc_wnd565_t*)gdc)->bitmap;
}
static bool window_update_needed(gdc_wnd565_t* wnd, const dma2d_params_t* dp) {
if (wnd->cursor_x != dp->dst_x || wnd->cursor_y == dp->dst_y) {
// the cursor is not at top-left corner of the clip
return true;
}
if (wnd->cursor_x < wnd->rect.x0 || wnd->cursor_y >= wnd->rect.x0) {
// cursor_x is out of the window
return true;
}
if (wnd->cursor_y < wnd->rect.y0 || wnd->cursor_y >= wnd->rect.y0) {
// cursor_y is out of the window
return true;
}
if (dp->height == 1) {
// one-line operation
if (dp->dst_x + dp->width > wnd->rect.x1) {
// clip extends right side of the window
return true;
}
} else {
// multi-line operation
if (dp->dst_x != wnd->rect.x1 || dp->dst_x + dp->width != wnd->rect.x1) {
// the clip x coordinates does not match the window
return true;
}
if (dp->dst_y + dp->height > wnd->rect.x1) {
// clip is too tall to fit in the window
return true;
}
}
return false;
}
static void ensure_window(gdc_wnd565_t* wnd, dma2d_params_t* dp) {
if (window_update_needed(wnd, dp)) {
display_set_window(dp->dst_x, dp->dst_y, dp->dst_x + dp->width - 1,
dp->dst_y + dp->height + 1);
}
}
static void update_cursor(gdc_wnd565_t* wnd, dma2d_params_t* params) {
wnd->cursor_x += params->width;
if (wnd->cursor_x == wnd->rect.x1) {
wnd->cursor_x = wnd->rect.x0;
}
wnd->cursor_y += params->height;
}
static void gdc_wnd565_set_window_hint(gdc_t* gdc, dma2d_params_t* dp) {
gdc_wnd565_t* wnd = (gdc_wnd565_t*)gdc;
if (window_update_needed(wnd, dp)) {
if (dp->width > 0 && dp->height > 0) { // TODO: why this condition???
display_set_window(dp->dst_x, dp->dst_y, dp->dst_x + dp->width - 1,
dp->dst_y + dp->height + 1);
}
}
}
static bool gdc_wnd565_fill(gdc_t* gdc, dma2d_params_t* params) {
gdc_wnd565_t* wnd = (gdc_wnd565_t*)gdc;
ensure_window(wnd, params);
dma2d_params_t p = *params;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
PIXELDATA(p.srca_fg);
}
}
update_cursor(wnd, params);
return true;
}
static bool gdc_wnd565_copy_mono4(gdc_t* gdc, dma2d_params_t* params) {
gdc_wnd565_t* wnd = (gdc_wnd565_t*)gdc;
ensure_window(wnd, params);
dma2d_params_t p = *params;
const gdc_color16_t* gradient = gdc_color16_gradient_a4(p.srca_fg, p.srca_bg);
uint8_t* srca_row = (uint8_t*)p.srca_row;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
uint8_t fg_data = srca_row[(x + p.srca_x) / 2];
uint8_t fg_lum = (x + p.srca_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
PIXELDATA(gradient[fg_lum]);
}
srca_row += p.srca_stride / sizeof(*srca_row);
}
update_cursor(wnd, params);
return true;
}
static bool gdc_wnd565_copy_rgb565(gdc_t* gdc, dma2d_params_t* params) {
gdc_wnd565_t* wnd = (gdc_wnd565_t*)gdc;
ensure_window(wnd, params);
dma2d_params_t p = *params;
uint16_t* srca_ptr = (uint16_t*)p.srca_row + p.srca_x;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
PIXELDATA(srca_ptr[x]);
}
srca_ptr += p.srca_stride / sizeof(*srca_ptr);
}
update_cursor(wnd, params);
return true;
}
static bool gdc_wnd565_blend_mono4_mono4(gdc_t* gdc, dma2d_params_t* params) {
gdc_wnd565_t* wnd = (gdc_wnd565_t*)gdc;
ensure_window(wnd, params);
dma2d_params_t p = *params;
const gdc_color16_t* gradient = gdc_color16_gradient_a4(p.srcb_fg, p.srcb_bg);
uint8_t* srca_row = (uint8_t*)p.srca_row;
uint8_t* srcb_row = (uint8_t*)p.srcb_row;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
uint8_t fg_data = srca_row[(x + p.srca_x) / 2];
uint8_t fg_alpha = (x + p.srca_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
uint8_t bg_data = srcb_row[(x + p.srcb_x) / 2];
uint8_t bg_lum = (x + p.srcb_x) & 1 ? bg_data >> 4 : bg_data & 0x0F;
PIXELDATA(gdc_color16_blend_a4(
p.srca_fg, gdc_color16_to_color(gradient[bg_lum]), fg_alpha));
}
srca_row += p.srca_stride / sizeof(*srca_row);
srcb_row += p.srcb_stride / sizeof(*srcb_row);
}
update_cursor(wnd, params);
return true;
}
static bool gdc_wnd565_blend_mono4_rgb565(void* gdc, dma2d_params_t* params) {
gdc_wnd565_t* wnd = (gdc_wnd565_t*)gdc;
ensure_window(wnd, params);
dma2d_params_t p = *params;
uint8_t* srca_row = (uint8_t*)p.srca_row;
uint16_t* srcb_ptr = (uint16_t*)p.srcb_row + p.srcb_x;
while (p.height-- > 0) {
for (int x = 0; x < p.width; x++) {
uint8_t fg_data = srca_row[(x + p.srca_x) / 2];
uint8_t fg_alpha = (x + p.srca_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
PIXELDATA(gdc_color16_blend_a4(
p.srca_fg, gdc_color16_to_color(srcb_ptr[x]), fg_alpha));
}
srca_row += p.srca_stride / sizeof(*srca_row);
srcb_ptr += p.srcb_stride / sizeof(*srcb_ptr);
}
update_cursor(wnd, params);
return true;
}
gdc_t* gdc_wnd565_init(gdc_wnd565_t* gdc, gdc_wnd565_config_t* config) {
static const gdc_vmt_t gdc_wnd565 = {
.release = gdc_wnd565_release,
.set_window_hint = gdc_wnd565_set_window_hint,
.get_bitmap = gdc_wnd565_get_bitmap,
.fill = gdc_wnd565_fill,
.copy_mono4 = gdc_wnd565_copy_mono4,
.copy_rgb565 = gdc_wnd565_copy_rgb565,
.copy_rgba8888 = NULL,
.blend_mono4_mono4 = gdc_wnd565_blend_mono4_mono4,
.blend_mono4_rgb565 = gdc_wnd565_blend_mono4_rgb565,
.blend_mono4_rgba8888 = NULL,
};
memset(gdc, 0, sizeof(gdc_wnd565_t));
gdc->vmt = &gdc_wnd565;
gdc->bitmap.format = GDC_FORMAT_RGB565;
gdc->bitmap.size = config->size;
gdc->bitmap.ptr = (void*)config->reg_address;
return (gdc_t*)&gdc->vmt;
}
gdc_t* display_acquire_gdc(void) {
static gdc_wnd565_t wnd = {};
if (wnd.vmt == NULL) {
gdc_wnd565_config_t config = {
.size.x = 240,
.size.y = 240,
};
gdc_wnd565_init(&wnd, &config);
}
return (gdc_t*)&wnd.vmt;
}

@ -0,0 +1,73 @@
/*
* 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 GDC_WND565_H
#define GDC_WND565_H
#include "gdc_core.h"
// ------------------------------------------------------------------------
// GDC for displays RGB565 with register/window API like ST7789
//
// This module serves as a driver for specific types of displays with
// internal memory acting as a frame buffer and a specific interface
// for writing to this framebuffer by putting pixels into the
// specified rectangle window.
typedef void (*gdc_release_cb_t)(void* context);
// Driver configuration
typedef struct {
// TODO
uintptr_t reg_address;
// GDC size in pixels
gdc_size_t size;
// Callback for settings the window
// gdc_set_window_cb_t set_window;
// Release callback invoked when gdc_release() is called
gdc_release_cb_t release;
// Context for release callback
void* context;
} gdc_wnd565_config_t;
// Driver-specific GDC structure
typedef struct {
// GDC virtual method table
// (Must be the first field of the structure)
const gdc_vmt_t* vmt;
// Fake bitmap structure
gdc_bitmap_t bitmap;
// Current drawing window/rectangle
gdc_rect_t rect;
// Cursor position in the window
int cursor_x;
int cursor_y;
} gdc_wnd565_t;
// Initializes GDC context
gdc_t* gdc_wnd565_init(gdc_wnd565_t* gdc, gdc_wnd565_config_t* config);
#endif // GDC_WND565_H

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "alloc-traits"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -297,6 +303,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static-alloc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "570b7e840addf99f80c5b26abba410e21537002316fc82f2747fd87c171e9d7e"
dependencies = [
"alloc-traits",
]
[[package]]
name = "syn"
version = "1.0.80"
@ -326,7 +341,10 @@ dependencies = [
"num-traits",
"qrcodegen",
"serde_json",
"static-alloc",
"trezor-tjpgdec",
"unsize",
"without-alloc",
]
[[package]]
@ -335,6 +353,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unsize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939"
dependencies = [
"autocfg",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -356,3 +383,13 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "without-alloc"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375db0478b203b950ef10d1cce23cdbe5f30c2454fd9e7673ff56656df23adbb"
dependencies = [
"alloc-traits",
"unsize",
]

@ -94,6 +94,16 @@ version = "0.2.6"
default-features = false
features = ["nightly"]
[dependencies.static-alloc]
version = "0.2.4"
[dependencies.without-alloc]
version = "0.2.2"
[dependencies.unsize]
version = "1.1.0"
# Build dependencies
[build-dependencies.bindgen]

@ -309,6 +309,29 @@ fn generate_trezorhal_bindings() {
.allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_Y")
.allowlist_var("DISPLAY_RESX")
.allowlist_var("DISPLAY_RESY")
// gdc
.allowlist_type("gdc_t")
.allowlist_type("gdc_bitmap_t")
.allowlist_var("GDC_BITMAP_READ_ONLY")
.allowlist_var("GDC_BITMAP_NO_DMA")
.allowlist_type("gdc_bitmap_ref_t")
.allowlist_type("gdc_color_t")
.allowlist_type("gdc_format_t")
.allowlist_type("gdc_rect_t")
.allowlist_type("gdc_text_attr_t")
.allowlist_function("gdc_release")
.allowlist_function("gdc_get_size")
.allowlist_function("gdc_set_window_hint")
.allowlist_function("gdc_fill_rect")
.allowlist_function("gdc_draw_bitmap")
.allowlist_function("gdc_draw_blended")
.allowlist_function("gdc_draw_opaque_text")
.allowlist_function("gdc_draw_blended_text")
.allowlist_function("gdc_bitmap_rgb565")
.allowlist_function("gdc_bitmap_rgba8888")
.allowlist_function("gdc_heap_alloc")
.allowlist_function("gdc_heap_free")
.allowlist_function("display_acquire_gdc")
// fonts
.allowlist_function("font_height")
.allowlist_function("font_max_height")

@ -32,3 +32,5 @@ void display_image(int16_t x, int16_t y, const uint8_t* data, uint32_t datalen);
void display_icon(int16_t x, int16_t y, const uint8_t* data, uint32_t datalen,
uint16_t fg_color, uint16_t bg_color);
void bld_continue_label(uint16_t bg_color);
void new_drawing_poc();

@ -0,0 +1,370 @@
use super::ffi;
use crate::ui::{
display::{Color, Font},
geometry::{Offset, Rect},
};
use core::{mem, slice};
#[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)]
pub enum GdcFormat {
MONO1 = ffi::gdc_format_t_GDC_FORMAT_MONO1 as _,
MONO4 = ffi::gdc_format_t_GDC_FORMAT_MONO4 as _,
RGB565 = ffi::gdc_format_t_GDC_FORMAT_RGB565 as _,
RGBA8888 = ffi::gdc_format_t_GDC_FORMAT_RGBA8888 as _,
}
impl Into<Rect> for ffi::gdc_rect_t {
fn into(self) -> Rect {
Rect {
x0: self.x0,
y0: self.y0,
x1: self.x1,
y1: self.y1,
}
}
}
impl Into<ffi::gdc_rect_t> for Rect {
fn into(self) -> ffi::gdc_rect_t {
ffi::gdc_rect_t {
x0: self.x0,
y0: self.y0,
x1: self.x1,
y1: self.y1,
}
}
}
impl Into<Offset> for ffi::gdc_size_t {
fn into(self) -> Offset {
Offset {
x: self.x,
y: self.y,
}
}
}
impl Into<ffi::gdc_size_t> for Offset {
fn into(self) -> ffi::gdc_size_t {
ffi::gdc_size_t {
x: self.x,
y: self.y,
}
}
}
pub struct GdcBuffer<'a, T> {
pub data: &'a mut [T],
allocated: bool,
}
impl<'a, T> GdcBuffer<'a, T> {
pub fn alloc(len: usize) -> Option<Self> {
unsafe {
let ptr = ffi::gdc_heap_alloc(len * mem::size_of::<T>());
if !ptr.is_null() {
let data = slice::from_raw_parts_mut(ptr as *mut T, len);
Some(Self {
data,
allocated: true,
})
} else {
None
}
}
}
}
impl<'a, T> Drop for GdcBuffer<'a, T> {
fn drop(&mut self) {
if self.allocated {
unsafe {
ffi::gdc_heap_free(
self.data.as_mut_ptr() as *mut cty::c_void,
self.data.len() * mem::size_of::<T>(),
);
}
}
}
}
pub type GdcBitmap<'a> = ffi::gdc_bitmap_t;
impl<'a> GdcBitmap<'a> {
pub fn new_rgb565<'b>(
buff: &'b mut GdcBuffer<u16>,
stride: usize,
size: Offset,
) -> GdcBitmap<'b> {
unsafe {
ffi::gdc_bitmap_rgb565(
buff.data.as_mut_ptr() as *mut cty::c_void,
stride,
size.into(),
0,
)
}
}
pub fn from_rgb565_slice<'b>(buff: &'b [u16], stride: usize, size: Offset) -> GdcBitmap<'b> {
unsafe {
ffi::gdc_bitmap_rgb565(
buff.as_ptr() as *mut cty::c_void,
stride,
size.into(),
(ffi::GDC_BITMAP_READ_ONLY | ffi::GDC_BITMAP_NO_DMA) as u8,
)
}
}
pub fn new_rgba8888<'b>(
buff: &'b mut GdcBuffer<u32>,
stride: usize,
size: Offset,
) -> GdcBitmap<'b> {
unsafe {
ffi::gdc_bitmap_rgba8888(
buff.data.as_mut_ptr() as *mut cty::c_void,
stride,
size.into(),
0,
)
}
}
pub fn new_mono1<'b>(
buff: &'b mut GdcBuffer<u8>,
stride: usize,
size: Offset,
) -> GdcBitmap<'b> {
ffi::gdc_bitmap_t {
ptr: buff.data.as_mut_ptr() as *mut cty::c_void,
stride: stride,
size: size.into(),
format: ffi::gdc_format_t_GDC_FORMAT_MONO1,
vmt: core::ptr::null_mut(),
attrs: 0,
}
}
pub fn new_mono4<'b>(
buff: &'b mut GdcBuffer<u8>,
stride: usize,
size: Offset,
) -> GdcBitmap<'b> {
ffi::gdc_bitmap_t {
ptr: buff.data.as_mut_ptr() as *mut cty::c_void,
stride: stride,
size: size.into(),
format: ffi::gdc_format_t_GDC_FORMAT_MONO4,
vmt: core::ptr::null_mut(),
attrs: 0,
}
}
pub fn width(&self) -> i16 {
self.size.x
}
pub fn height(&self) -> i16 {
self.size.y
}
pub fn size(&self) -> Offset {
self.size.into()
}
pub fn stride(&self) -> usize {
self.stride
}
pub fn gdc(&'a mut self) -> Gdc<'a> {
unsafe {
// let handle = core::mem::transmute::<&*mut ffi::gdc_vmt, &mut
// cty::c_void>(&self.vmt);
let handle = core::mem::transmute::<&mut ffi::gdc_bitmap_t, &mut cty::c_void>(self);
Gdc { handle }
}
}
}
pub type GdcBitmapRef<'a> = ffi::gdc_bitmap_ref_t;
impl<'a> GdcBitmapRef<'a> {
pub fn new<'b>(bitmap: &'b GdcBitmap) -> GdcBitmapRef<'b> {
GdcBitmapRef {
bitmap: &*bitmap,
offset: Offset::zero().into(),
fg_color: 0,
bg_color: 0,
}
}
pub fn with_offset(self, offset: Offset) -> Self {
Self {
offset: (offset + self.offset.into()).into(),
..self
}
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self {
fg_color: fg_color.into(),
..self
}
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: bg_color.into(),
..self
}
}
pub fn size(&self) -> Offset {
unsafe { (&*self.bitmap).size() }
}
pub fn width(&self) -> i16 {
unsafe { (&*self.bitmap).width() }
}
pub fn height(&self) -> i16 {
unsafe { (&*self.bitmap).height() }
}
}
pub type GdcTextAttr = ffi::gdc_text_attr_t;
impl GdcTextAttr {
pub fn new() -> Self {
Self {
font: Font::NORMAL.into(),
fg_color: Color::white().into(),
bg_color: Color::black().into(),
offset: Offset::zero().into(),
}
}
pub fn with_font(self, font: Font) -> Self {
Self {
font: font.into(),
..self
}
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self {
fg_color: fg_color.into(),
..self
}
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: bg_color.into(),
..self
}
}
pub fn with_offset(self, offset: Offset) -> Self {
Self {
offset: (offset + self.offset.into()).into(),
..self
}
}
}
pub struct Gdc<'a> {
handle: &'a mut ffi::gdc_t,
}
impl<'a> Gdc<'a> {
pub fn size(&self) -> Offset {
unsafe { ffi::gdc_get_size(self.handle).into() }
}
pub fn set_window_hint(&mut self, rect: Rect) {
unsafe { ffi::gdc_set_window_hint(self.handle, rect.into()) }
}
// Draw filled rectangle
pub fn fill_rect(&mut self, rect: Rect, color: Color) {
unsafe {
let ok = ffi::gdc_fill_rect(self.handle, rect.into(), color.into());
assert!(ok);
}
}
pub fn draw_bitmap(&mut self, rect: Rect, src: &GdcBitmapRef) {
unsafe {
let ok = ffi::gdc_draw_bitmap(self.handle, rect.into(), src);
assert!(ok);
}
}
pub fn draw_blended(&mut self, rect: Rect, bg: &GdcBitmapRef, fg: &GdcBitmapRef) {
unsafe {
let ok = ffi::gdc_draw_blended(self.handle, rect.into(), bg, fg);
assert!(ok);
}
}
pub fn draw_opaque_text(&mut self, rect: Rect, text: &str, attr: &GdcTextAttr) {
unsafe {
let ok = ffi::gdc_draw_opaque_text(
self.handle,
rect.into(),
text.as_ptr() as _,
text.len(),
attr,
);
assert!(ok);
}
}
pub fn draw_blended_text(
&mut self,
rect: Rect,
text: &str,
attr: &GdcTextAttr,
bg: &GdcBitmapRef,
) {
unsafe {
let ok = ffi::gdc_draw_blended_text(
self.handle,
rect.into(),
text.as_ptr() as _,
text.len(),
attr,
bg,
);
assert!(ok);
}
}
}
impl<'a> Drop for Gdc<'a> {
fn drop(&mut self) {
unsafe {
ffi::gdc_release(self.handle);
}
}
}
pub struct Display {}
impl Display {
pub fn acquire_gdc<'a>() -> Option<Gdc<'a>> {
unsafe {
let handle = ffi::display_acquire_gdc();
if !handle.is_null() {
Some(Gdc {
handle: &mut *handle,
})
} else {
None
}
}
}
}

@ -2,11 +2,12 @@ pub mod bip39;
#[macro_use]
#[allow(unused_macros)]
pub mod fatal_error;
#[cfg(feature = "ui")]
pub mod display;
#[cfg(feature = "dma2d")]
pub mod dma2d;
mod ffi;
#[cfg(feature = "ui")]
pub mod gdc;
pub mod io;
pub mod model;
pub mod random;

@ -10,6 +10,7 @@ impl Default for ffi::uzlib_uncomp {
}
}
#[derive(Debug, Copy, Clone)]
pub struct UzlibContext<'a> {
uncomp: ffi::uzlib_uncomp,
src_data: PhantomData<&'a [u8]>,

@ -0,0 +1,292 @@
/// This is a simple and fast blurring algorithm that uses a box filter -
/// a square kernel with all coefficients set to 1.
///
/// The `BlurFilter` structure holds the context of a simple 2D window averaging
/// filter - a sliding window and the sum of all rows in the sliding window.
///
/// The `BlurFilter` implements only three public functions - `new`, `push`, and
/// `pop`.
///
/// The `new()` function creates a blur filter context.
/// - The `width` argument specifies the width of the blurred area.
/// - The `radius` argument specifies the length of the kernel side.
///
/// ```rust
/// let blur = BlurFilter::new(width, radius);
/// ```
///
/// The `push()` function pushes source row data into the sliding window and
/// performs all necessary calculations.
///
/// ```rust
/// blur.push(&canvas.row(ya)[x0..x1]);
/// ```
///
/// The `pop()` function pops the blurred row from the sliding window.
///
/// ```rust
/// blur.pop(&mut canvas.row(yb)[x0..x1]);
/// ```
use crate::trezorhal::gdc::GdcBuffer;
const MAX_RADIUS: usize = 4;
const MAX_SIDE: usize = 1 + MAX_RADIUS * 2;
const MAX_WIDTH: usize = 240;
type PixelColor = u16;
#[derive(Default, Copy, Clone)]
struct Rgb<T> {
pub r: T,
pub g: T,
pub b: T,
}
impl Rgb<u16> {
#[inline(always)]
fn mulshift(&self, multiplier: u32, shift: u8) -> Rgb<u8> {
Rgb::<u8> {
r: ((self.r as u32 * multiplier) >> shift) as u8,
g: ((self.g as u32 * multiplier) >> shift) as u8,
b: ((self.b as u32 * multiplier) >> shift) as u8,
}
}
}
impl From<u16> for Rgb<u16> {
#[inline(always)]
fn from(value: u16) -> Rgb<u16> {
Rgb::<u16> {
r: (value >> 8) & 0xF8,
g: (value >> 3) & 0xFC,
b: (value << 3) & 0xF8,
}
}
}
impl core::ops::AddAssign<u16> for Rgb<u16> {
#[inline(always)]
fn add_assign(&mut self, rhs: u16) {
let rgb: Rgb<u16> = rhs.into();
*self += rgb;
}
}
impl core::ops::SubAssign<u16> for Rgb<u16> {
#[inline(always)]
fn sub_assign(&mut self, rhs: u16) {
let rgb: Rgb<u16> = rhs.into();
*self -= rgb;
}
}
impl core::ops::AddAssign for Rgb<u16> {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.r += rhs.r;
self.g += rhs.g;
self.b += rhs.b;
}
}
impl core::ops::SubAssign for Rgb<u16> {
#[inline(always)]
fn sub_assign(&mut self, rhs: Rgb<u16>) {
self.r -= rhs.r;
self.g -= rhs.g;
self.b -= rhs.b;
}
}
impl From<Rgb<u8>> for u16 {
#[inline(always)]
fn from(value: Rgb<u8>) -> u16 {
let r = (value.r as u16 & 0xF8) << 8;
let g = (value.g as u16 & 0xFC) << 3;
let b = (value.b as u16 & 0xF8) >> 3;
r | g | b
}
}
impl From<Rgb<u16>> for Rgb<u8> {
#[inline(always)]
fn from(value: Rgb<u16>) -> Rgb<u8> {
Rgb::<u8> {
r: value.r as u8,
g: value.g as u8,
b: value.b as u8,
}
}
}
impl core::ops::AddAssign<Rgb<u8>> for Rgb<u16> {
#[inline(always)]
fn add_assign(&mut self, rhs: Rgb<u8>) {
self.r += rhs.r as u16;
self.g += rhs.g as u16;
self.b += rhs.b as u16;
}
}
impl core::ops::SubAssign<Rgb<u8>> for Rgb<u16> {
#[inline(always)]
fn sub_assign(&mut self, rhs: Rgb<u8>) {
self.r -= rhs.r as u16;
self.g -= rhs.g as u16;
self.b -= rhs.b as u16;
}
}
#[link_section = ".no_dma_buffers"]
static mut WINDOW: [Rgb<u8>; MAX_WIDTH * MAX_SIDE] =
[Rgb::<u8> { r: 0, g: 0, b: 0 }; MAX_WIDTH * MAX_SIDE];
fn alloc_window(size: usize) -> &'static mut [Rgb<u8>] {
unsafe {
let result = &mut WINDOW[..size];
result.iter_mut().for_each(|it| {
*it = Rgb::<u8>::default();
});
result
}
}
pub struct BlurAlgorithm<'a> {
width: usize,
radius: usize,
row: usize,
//window: GdcBuffer::<'a, RGB>,
totals: GdcBuffer<'a, Rgb<u16>>,
window: &'a mut [Rgb<u8>],
// totals: [RGB; MAX_WIDTH],
}
impl<'a> BlurAlgorithm<'a> {
// Constraints:
// width <= MAX_WIDTH
// radius <= MAX_RADIUS
// width >= radius
pub fn new(width: usize, radius: usize) -> Option<Self> {
assert!(width <= MAX_WIDTH);
assert!(radius <= MAX_RADIUS);
assert!(width > 2 * radius - 1);
let side = 1 + radius * 2;
// let window = GdcBuffer::<RGB>::alloc(width * side)?;
// self.window.iter_mut().for_each(|it| {*it = RGB::default()});
let window = alloc_window(width * side);
let totals = GdcBuffer::<Rgb<u16>>::alloc(width)?;
totals
.data
.iter_mut()
.for_each(|it| *it = Rgb::<u16>::default());
Some(Self {
width,
radius,
row: 0,
window,
totals,
// window: [RGB::default(); MAX_WIDTH * MAX_SIDE],
// totals: [RGB::default(); MAX_WIDTH],
})
}
// Returns the length of the box filter side
fn box_side(&self) -> usize {
1 + self.radius * 2
}
// Takes an input row and calculates the same-sized vector
// as the floating average of n subsequent elements where n = 2 * radius + 1.
// Finally, it stores it into the specifed row in the sliding window.
fn average_to_row(&mut self, inp: &[PixelColor], row: usize) {
let radius = self.radius;
let offset = self.width * row;
let row = &mut self.window[offset..offset + self.width];
let mut sum = Rgb::<u16>::default();
let divisor = (radius * 2 + 1) as u16;
let shift = 10;
let multiplier = (1 << shift) as u32 / divisor as u32;
// Prepare before averaging
for i in 0..radius {
sum += inp[0]; // Duplicate pixels on the left
sum += inp[i]; // Add first radius pixels
}
// Process the first few pixels of the row
for i in 0..radius {
sum += inp[i + radius];
row[i] = sum.mulshift(multiplier, shift);
sum -= inp[0];
}
// Process the inner part of the row
for i in radius..row.len() - radius {
sum += inp[i + radius];
row[i] = sum.mulshift(multiplier, shift);
sum -= inp[i - radius];
}
// Process the last few pixels of the row
for i in (row.len() - radius)..row.len() {
sum += inp[inp.len() - 1];
row[i] = sum.mulshift(multiplier, shift);
sum -= inp[i - radius]; // Duplicate pixels on the right
}
}
// Subtracts the specified row of sliding window from totals[]
fn subtract_row(&mut self, row: usize) {
let offset = row * self.width;
let row = &self.window[offset..offset + self.width];
for i in 0..row.len() {
self.totals.data[i] -= row[i];
}
}
// Adds the specified row of sliding window to totals[]
fn add_row(&mut self, row: usize) {
let offset = row * self.width;
let row = &self.window[offset..offset + self.width];
for i in 0..row.len() {
self.totals.data[i] += row[i];
}
}
// Takes the source row and pushes it into the sliding window
pub fn push(&mut self, input: &[PixelColor]) {
let row = self.row;
self.subtract_row(row);
self.average_to_row(input, row);
self.add_row(row);
self.row = if row < self.box_side() - 1 {
row + 1
} else {
0
};
}
// Copies the current content of totals[] to the output buffer
pub fn pop(&self, output: &mut [PixelColor]) {
let divisor = self.box_side() as u16;
let shift = 10;
let multiplier = (1 << shift) as u32 / divisor as u32;
for i in 0..output.len() {
output[i] = self.totals.data[i].mulshift(multiplier, shift).into();
}
}
}

@ -0,0 +1,94 @@
use crate::ui::{
canvas::{rgb, Viewport},
display::Color,
geometry::{Offset, Rect},
};
use crate::trezorhal::gdc::{Display, Gdc};
// ==========================================================================
// Display canvas
// ==========================================================================
pub struct Canvas<'a> {
gdc: Gdc<'a>,
viewport: Viewport,
}
impl<'a> Canvas<'a> {
pub fn acquire() -> Option<Self> {
let gdc = Display::acquire_gdc()?;
let viewport = Viewport::from_size(gdc.size());
Some(Self { gdc, viewport })
}
}
impl<'a> rgb::RgbCanvas for Canvas<'a> {
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.bounds());
}
fn size(&self) -> Offset {
self.gdc.size()
}
fn fill_rect(&mut self, r: Rect, color: Color) {
let new_r = r
.translate(self.viewport.origin)
.intersect(self.viewport.clip);
self.gdc.fill_rect(new_r, color);
}
fn draw_bitmap(&mut self, r: Rect, bitmap: &rgb::BitmapRef) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_b = bitmap
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc.draw_bitmap(r_clipped, &new_b);
}
fn draw_blended(&mut self, r: Rect, fg: &rgb::BitmapRef, bg: &rgb::BitmapRef) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_fg = fg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
let new_bg = bg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc.draw_blended(r_clipped, &new_fg, &new_bg);
}
fn draw_opaque_text(&mut self, r: Rect, text: &str, attr: &rgb::TextAttr) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_attr = attr
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc.draw_opaque_text(r_clipped, text, &new_attr);
}
fn draw_blended_text(
&mut self,
r: Rect,
text: &str,
attr: &rgb::TextAttr,
bg: &rgb::BitmapRef,
) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_attr = attr
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
let new_bg = bg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc
.draw_blended_text(r_clipped, text, &new_attr, &new_bg);
}
}

@ -0,0 +1,116 @@
pub mod bluralgo;
pub mod display;
pub mod mono;
pub mod mono1;
pub mod mono4;
mod octant;
pub mod rgb;
pub mod rgb565;
pub mod rgba8888;
use crate::ui::geometry::{Offset, Rect};
/// The Viewport concept is foundation for clipping and translating
/// during drawing on the general canvas.
///
/// The Viewport structure comprises a rectangle representing the
/// clipping area and a drawing origin (or offset), which is applied
/// to all coordinates passed to the drawing functions.
///
/// Two coordination systems exist - "absolute" and "relative."
///
/// In the "absolute" coordinate system, (0, 0) is at the left-top of
/// a referenced canvas (device or bitmap).
///
/// Relative coordinates are with respect to the viewport origin.
/// The relative coordinate (0, 0) is located at (viewport.origin.x,
/// viewport.origin.y).
///
/// Conversion between "absolute" and "relative" coordinates is straightforward:
///
/// pt_absolute = pt_relative.translate(viewport.origin)
///
/// pt_relative = pt_absolute.translate(-viewport.origin)
///
/// The Viewport's clipping area and origin are always in "absolute"
/// coordinates. Canvas objects utilize the viewport to translate "relative"
/// coordinates passed to drawing functions into "absolute" coordinates that
/// correspond to the target device or bitmap.
#[derive(Copy, Clone)]
pub struct Viewport {
/// Clipping rectangle relative to the canvas top-left corner
pub clip: Rect,
/// Offset applied to all coordinates before clipping
pub origin: Offset,
}
impl Viewport {
// Creates a new viewport with specified size and origin at (0,0)
pub fn from_size(size: Offset) -> Viewport {
Self {
clip: Rect::from_size(size),
origin: Offset::zero(),
}
}
/// Returns the width of the viewport
pub fn width(&self) -> i16 {
// TODO: candidate to remove
self.clip.width()
}
/// Returns the height of the viewport
pub fn height(&self) -> i16 {
// TODO: candidate to remove
self.clip.width()
}
/// Checks if the viewport intersects with the specified rectangle
/// given in relative coordinates.
pub fn contains(&self, r: Rect) -> bool {
r.translate(self.origin).has_intersection(self.clip)
}
pub fn translate(self, offset: Offset) -> Viewport {
Self {
clip: self.clip.translate(offset),
origin: self.origin + offset,
}
}
// Creates a new viewport with the new origin given in
// absolute coordinates
pub fn with_origin(self, origin: Offset) -> Viewport {
Self { origin, ..self }
}
/// Creates a clip of the viewport containing only the specified rectangle
/// given in absolute coordinates. The origin of the new viewport
/// remains unchanged.
pub fn absolute_clip(self, r: Rect) -> Viewport {
Self {
clip: r.intersect(self.clip),
..self
}
}
/// Creates a clip of the viewport containing only the specified rectangle
/// given in relative coordinates. The origin of the new viewport
/// remains unchanged.
pub fn relative_clip(self, r: Rect) -> Viewport {
Self {
clip: r.translate(self.origin).intersect(self.clip),
..self
}
}
/// Creates a clip of the viewport containing only the specified rectangle
/// given in relative coordinates. The origin of the new viewport
/// is set to the top-left corner of the rectangle.
pub fn relative_window(&self, r: Rect) -> Viewport {
let clip = r.translate(self.origin).intersect(self.clip);
let origin = self.origin + (clip.top_left() - self.clip.top_left());
Self { clip, origin }
}
}

@ -0,0 +1,41 @@
use crate::ui::geometry::{Offset, Rect};
use crate::trezorhal::gdc;
pub type BitmapRef<'a> = gdc::GdcBitmapRef<'a>;
/*pub struct MonoTextAttr {
// luminance
lum: u8,
offset: Offset,
// TODO: horizontal alignment
// TODO: vertical alignment
}*/
/*pub enum MonoBitmapRef<'a> {
// (&bitmap, offset, luminance)
Mono1(&'a mono1::Canvas, Offset, u8),
Mono4(&'a mono4::Canvas, Offset, u8),
}*/
pub trait MonoCanvas {
/// Returns dimensions of the canvas in pixels
fn size(&self) -> Offset;
/// Returns a non-mutable reference to the underlying bitmap
fn bitmap<'a>(&'a self) -> BitmapRef<'a>;
/// Returns the dimensions of the canvas as a rectangle with the top-left at
/// (0,0)
fn bounds(&self) -> Rect {
Rect::from_size(self.size())
}
fn width(&self) -> i16 {
self.size().x
}
fn height(&self) -> i16 {
self.size().y
}
}

@ -0,0 +1,60 @@
use crate::ui::{canvas::mono, geometry::Offset};
use crate::trezorhal::gdc::{GdcBitmap, GdcBuffer};
// ==========================================================================
// 1-bpp Monochromatic Canvas
// ==========================================================================
pub struct Canvas<'a> {
buff: GdcBuffer<'a, u8>,
bitmap: GdcBitmap<'a>,
}
impl<'a> Canvas<'a> {
pub fn new(size: Offset) -> Option<Self> {
if size.x > 0 && size.y > 0 {
let width = size.x as usize;
let height = size.y as usize;
let stride = (width + 7) / 8;
let mut buff = GdcBuffer::<u8>::alloc(height * width)?;
let bitmap = GdcBitmap::new_mono1(&mut buff, stride, size);
Some(Self { buff, bitmap })
} else {
panic!();
}
}
pub fn row(&mut self, row: i16) -> Option<&mut [u8]> {
if row >= 0 && row < self.bitmap.height() {
let offset = self.bitmap.stride() * row as usize;
Some(&mut self.buff.data[offset..offset + ((self.bitmap.width() + 7) / 8) as usize])
} else {
None
}
}
pub fn row_bytes(&mut self, row: i16, height: i16) -> Option<&mut [u8]> {
if row >= 0
&& height > 0
&& row < self.bitmap.height()
&& row + height <= self.bitmap.height()
{
let offset = self.bitmap.stride() * row as usize;
let len = self.bitmap.stride() * height as usize;
Some(&mut self.buff.data[offset..offset + len])
} else {
None
}
}
}
impl<'a> mono::MonoCanvas for Canvas<'a> {
fn bitmap<'b>(&'b self) -> mono::BitmapRef<'b> {
mono::BitmapRef::new(&self.bitmap)
}
fn size(&self) -> Offset {
self.bitmap.size()
}
}

@ -0,0 +1,60 @@
use crate::ui::{canvas::mono, geometry::Offset};
use crate::trezorhal::gdc::{GdcBitmap, GdcBuffer};
// ==========================================================================
// 4-bpp Monochromatic Canvas
// ==========================================================================
pub struct MonoCanvas<'a> {
buff: GdcBuffer<'a, u8>,
bitmap: GdcBitmap<'a>,
}
impl<'a> MonoCanvas<'a> {
pub fn new(size: Offset) -> Option<Self> {
if size.x > 0 && size.y > 0 {
let width = size.x as usize;
let height = size.y as usize;
let stride = (width + 1) / 2;
let mut buff = GdcBuffer::<u8>::alloc(height * width)?;
let bitmap = GdcBitmap::new_mono4(&mut buff, stride, size);
Some(Self { buff, bitmap })
} else {
panic!();
}
}
pub fn row(&mut self, row: i16) -> Option<&mut [u8]> {
if row >= 0 && row < self.bitmap.height() {
let offset = self.bitmap.stride() * row as usize;
Some(&mut self.buff.data[offset..offset + ((self.bitmap.width() + 1) / 2) as usize])
} else {
None
}
}
pub fn row_bytes(&mut self, row: i16, height: i16) -> Option<&mut [u8]> {
if row >= 0
&& height > 0
&& row < self.bitmap.height()
&& row + height <= self.bitmap.height()
{
let offset = self.bitmap.stride() * row as usize;
let len = self.bitmap.stride() * height as usize;
Some(&mut self.buff.data[offset..offset + len])
} else {
None
}
}
}
impl<'a> mono::MonoCanvas for MonoCanvas<'a> {
fn bitmap<'b>(&'b self) -> mono::BitmapRef<'b> {
mono::BitmapRef::new(&self.bitmap)
}
fn size(&self) -> Offset {
self.bitmap.size()
}
}

@ -0,0 +1,61 @@
/// Iterator providing points for 1/8th of a circle (single octant)
///
/// The iterator supplies coordinates of pixels relative to the
/// circle's center point, along with an alpha value in
/// the range (0..255), indicating the proportion of the pixel
/// that lies inside the circle.
/// for (x, y, alpha) in octant_points(radius) {
/// ...
/// }
pub fn octant_points(radius: i16) -> OctantPoints {
OctantPoints {
radius,
x: radius,
y: 0,
t1: radius / 16,
first: true,
}
}
pub struct OctantPoints {
radius: i16,
x: i16,
y: i16,
t1: i16,
first: bool,
}
fn alpha(t1: i16, r1: i16) -> u8 {
255 - ((t1 as i32 * 255) / r1 as i32) as u8
}
impl Iterator for OctantPoints {
type Item = (i16, i16, u8, bool, bool);
fn next(&mut self) -> Option<Self::Item> {
if self.x >= self.y {
let cur_x = self.x;
let cur_y = self.y;
let cur_t1 = self.t1;
let first = self.first;
self.first = false;
self.y += 1;
self.t1 = self.t1 + self.y;
let t2 = self.t1 - self.x;
if t2 >= 0 {
self.t1 = t2;
self.x -= 1;
self.first = true;
}
let last = cur_x != self.x;
Some((cur_x, cur_y, alpha(cur_t1, self.radius), first, last))
} else {
None
}
}
}

@ -0,0 +1,661 @@
use crate::ui::{
canvas::Viewport,
display::Color,
geometry::{Offset, Point, Rect},
};
use super::octant::octant_points;
use crate::trezorhal::gdc;
// A reference to a bitmap with additional parameters,
// such as foreground/background color and drawing offset
pub type BitmapRef<'a> = gdc::GdcBitmapRef<'a>;
/// Text attributes, including font, foreground/background color,
/// and drawing offset
pub type TextAttr = gdc::GdcTextAttr;
pub trait RgbCanvas {
/// Sets the active viewport valid for all subsequent drawing operations
fn set_viewport(&mut self, vp: Viewport);
/// Gets the current drawing viewport previously set by set_viewport()
/// function
fn viewport(&self) -> Viewport;
/// Returns dimensions of the canvas in pixels
fn size(&self) -> Offset;
/// Draws a filled rectangle with the specified color
fn fill_rect(&mut self, r: Rect, color: Color);
/// Draws a bitmap of bitmap into to the rectangle
fn draw_bitmap(&mut self, r: Rect, bitmap: &BitmapRef);
/// Blends two bitmaps and draws them into the specified rectangle
fn draw_blended(&mut self, r: Rect, fg: &BitmapRef, bg: &BitmapRef);
/// Draws text to the specified rectangle
fn draw_opaque_text(&mut self, r: Rect, text: &str, attr: &TextAttr);
/// Blends text with a bitmap and draws it into the specified rectangle
/// (ignores attr.bg_color attribute)
fn draw_blended_text(&mut self, r: Rect, text: &str, attr: &TextAttr, bg: &BitmapRef);
fn set_window_hint(&mut self, _r: Rect) {
// Empty default implementation
}
/// Returns the dimensions of the canvas as a rectangle with the top-left at
/// (0,0)
fn bounds(&self) -> Rect {
Rect::from_size(self.size())
}
fn witdh(&self) -> i16 {
self.size().x
}
fn height(&self) -> i16 {
self.size().y
}
fn set_window(&mut self, window: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_window(window));
viewport
}
fn set_clip(&mut self, clip: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_clip(clip));
viewport
}
// Draw a pixel at specified coordinates
// (this default implementation is highly inefficient)
fn draw_pixel(&mut self, pt: Point, color: Color) {
self.fill_rect(Rect::from_top_left_and_size(pt, Offset::new(1, 1)), color);
}
fn draw_round_rect(&mut self, r: Rect, radius: i16, color: Color) {
let (split, _, _, _, _) = octant_points(radius).last().unwrap();
let b = Rect {
y1: r.y0 + radius - split + 1,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, last) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - y, r.y0 + radius - x);
let pt_r = Point::new(r.x1 - radius + y - 1, r.y0 + radius - x);
if x == radius && last {
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
} else {
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
}
let b = Rect {
y0: r.y0 + radius - split + 1,
y1: r.y0 + radius + 1,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - x, r.y0 + radius - y);
let pt_r = Point::new(r.x1 - radius + x - 1, r.y0 + radius - y);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
self.fill_rect(
Rect {
x0: r.x0,
y0: r.y0 + radius + 1,
x1: r.x0 + 1,
y1: r.y1 - radius - 1,
},
color,
);
self.fill_rect(
Rect {
x0: r.x1 - 1,
y0: r.y0 + radius + 1,
x1: r.x1,
y1: r.y1 - radius - 1,
},
color,
);
let b = Rect {
y0: r.y1 - radius - 1,
y1: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - x, r.y1 - radius - 1 + y);
let pt_r = Point::new(r.x1 - radius + x - 1, r.y1 - radius - 1 + y);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let b = Rect {
y0: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, last) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - y, r.y1 - radius - 1 + x);
let pt_r = Point::new(r.x1 - radius + y - 1, r.y1 - radius - 1 + x);
if x == radius && last {
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
} else {
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
}
}
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color) {
let (split, _, _, _, _) = octant_points(radius).last().unwrap();
let b = Rect {
y1: r.y0 + radius - split + 1,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, last) in octant_points(radius) {
if last {
let pt_l = Point::new(r.x0 + radius - y, r.y0 + radius - x);
let pt_r = Point::new(r.x1 - radius + y - 1, r.y0 + radius - x);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
}
let b = Rect {
y0: r.y0 + radius - split + 1,
y1: r.y0 + radius + 1,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - x, r.y0 + radius - y);
let pt_r = Point::new(r.x1 - radius + x - 1, r.y0 + radius - y);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
self.fill_rect(
Rect {
x0: r.x0,
y0: r.y0 + radius + 1,
x1: r.x1,
y1: r.y1 - radius - 1,
},
color,
);
let b = Rect {
y0: r.y1 - radius - 1,
y1: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - x, r.y1 - radius - 1 + y);
let pt_r = Point::new(r.x1 - radius + x - 1, r.y1 - radius - 1 + y);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
let b = Rect {
y0: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for (x, y, _, _, last) in octant_points(radius) {
if last {
let pt_l = Point::new(r.x0 + radius - y, r.y1 - radius - 1 + x);
let pt_r = Point::new(r.x1 - radius + y - 1, r.y1 - radius - 1 + x);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
}
}
// Draws filled circle with the specified center and the radius
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
let (split, _, _, _, _) = octant_points(radius).last().unwrap();
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, last) in octant_points(radius) {
if last {
let pt_l = Point::new(center.x - y, center.y - x);
let pt_r = Point::new(center.x + y, center.y - x);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y - y);
let pt_r = Point::new(center.x + x, center.y - y);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y + y);
let pt_r = Point::new(center.x + x, center.y + y);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, last) in octant_points(radius) {
if last {
let pt_l = Point::new(center.x - y, center.y + x);
let pt_r = Point::new(center.x + y, center.y + x);
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color);
}
}
}
}
// Draws circle with the specified center and the radius
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
let (split, _, _, _, _) = octant_points(radius).last().unwrap();
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - y, center.y - x);
let pt_r = Point::new(center.x + y, center.y - x);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y - y);
let pt_r = Point::new(center.x + x, center.y - y);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y + y);
let pt_r = Point::new(center.x + x, center.y + y);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for (x, y, _, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - y, center.y + x);
let pt_r = Point::new(center.x + y, center.y + x);
self.draw_pixel(pt_l, color);
self.draw_pixel(pt_r, color);
}
}
}
/// Fills the canvas background with the specified color
fn fill_background(&mut self, color: Color) {
self.fill_rect(self.viewport().clip, color);
}
}
pub trait RgbCanvasEx: RgbCanvas {
/// Returns a non-mutable reference to the underlying bitmap
fn bitmap<'a>(&'a self) -> BitmapRef<'a>;
/// Draws a single pixel and blends its color with the background
/// If alpha == 255, the (foreground) pixel color is used
/// If 0 < alpha << 255, pixel and backround colors are blended
/// If alpha == 0, the background color is used
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8);
/// Draws text (with a transparent backround) to the specified rectangle
fn draw_text(&mut self, r: Rect, text: &str, attr: &TextAttr) {
let offset = r.top_left() + self.viewport().origin;
self.draw_blended_text(r, text, attr, &self.bitmap().with_offset(offset.into()));
}
// Blends a bitmap with the canvas background
// (TODO: Explain better)
fn blend_bitmap(&mut self, r: Rect, bitmap: &BitmapRef) {
let offset = r.top_left() + self.viewport().origin;
self.draw_blended(r, bitmap, &self.bitmap().with_offset(offset.into()));
}
fn blur_rect(&mut self, r: Rect, radius: usize);
// Draws antialiased filled circle with the specified center and the radius
fn fill_circle_aa(&mut self, center: Point, radius: i16, color: Color) {
let (split, _, _, _, _) = octant_points(radius).last().unwrap();
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, first, _) in octant_points(radius) {
let pt_l = Point::new(center.x - y, center.y - x);
let pt_r = Point::new(center.x + y, center.y - x);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_r, color, alpha);
if first {
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color);
}
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y - y);
let pt_r = Point::new(center.x + x, center.y - y);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_r, color, alpha);
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y + y);
let pt_r = Point::new(center.x + x, center.y + y);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_r, color, alpha);
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, first, _) in octant_points(radius) {
let pt_l = Point::new(center.x - y, center.y + x);
let pt_r = Point::new(center.x + y, center.y + x);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_r, color, alpha);
if first {
let r = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(r, color);
}
}
}
}
// Draws antialiased circle with the specified center and the radius
fn draw_circle_aa(&mut self, center: Point, radius: i16, color: Color) {
let (split, _, _, _, _) = octant_points(radius).last().unwrap();
let r = Rect::new(
Point::new(center.x - radius, center.y - radius),
Point::new(center.x + radius + 1, center.y - split + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - y, center.y - x);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_l.under(), color, 255 - alpha);
let pt_r = Point::new(center.x + y, center.y - x);
self.blend_pixel(pt_r, color, alpha);
self.blend_pixel(pt_r.under(), color, 255 - alpha);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y - split),
Point::new(center.x + radius + 1, center.y + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y - y);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_l.onright(), color, 255 - alpha);
let pt_r = Point::new(center.x + x, center.y - y);
self.blend_pixel(pt_r, color, alpha);
self.blend_pixel(pt_r.onleft(), color, 255 - alpha);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y),
Point::new(center.x + radius + 1, center.y + split + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - x, center.y + y);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_l.onright(), color, 255 - alpha);
let pt_r = Point::new(center.x + x, center.y + y);
self.blend_pixel(pt_r, color, alpha);
self.blend_pixel(pt_r.onleft(), color, 255 - alpha);
}
}
let r = Rect::new(
Point::new(center.x - radius, center.y + split),
Point::new(center.x + radius + 1, center.y + radius + 1),
);
if self.viewport().contains(r) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(center.x - y, center.y + x);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_l.above(), color, 255 - alpha);
let pt_r = Point::new(center.x + y, center.y + x);
self.blend_pixel(pt_r, color, alpha);
self.blend_pixel(pt_r.above(), color, 255 - alpha);
}
}
}
fn fill_round_rect_aa(&mut self, r: Rect, radius: i16, color: Color) {
let (split, _, _, _, _) = octant_points(radius).last().unwrap();
let b = Rect {
y1: r.y0 + radius - split + 1,
..r
};
if self.viewport().contains(b) {
for (x, y, alpha, first, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - y, r.y0 + radius - x);
let pt_r = Point::new(r.x1 - radius + y - 1, r.y0 + radius - x);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_r, color, alpha);
if first {
let inner = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(inner, color);
}
}
}
let b = Rect {
y0: r.y0 + radius - split + 1,
y1: r.y0 + radius + 1,
..r
};
if self.viewport().contains(b) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - x, r.y0 + radius - y);
let pt_r = Point::new(r.x1 - radius + x - 1, r.y0 + radius - y);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_r, color, alpha);
let inner = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(inner, color);
}
}
self.fill_rect(
Rect {
x0: r.x0,
y0: r.y0 + radius + 1,
x1: r.x1,
y1: r.y1 - radius - 1,
},
color,
);
let b = Rect {
y0: r.y1 - radius - 1,
y1: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for (x, y, alpha, _, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - x, r.y1 - radius - 1 + y);
let pt_r = Point::new(r.x1 - radius + x - 1, r.y1 - radius - 1 + y);
self.blend_pixel(pt_l, color, alpha);
self.blend_pixel(pt_r, color, alpha);
let b = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(b, color);
}
}
let b = Rect {
y0: r.y1 - radius - 1 + split,
..r
};
if self.viewport().contains(b) {
for (x, y, alpha, first, _) in octant_points(radius) {
let pt_l = Point::new(r.x0 + radius - y, r.y1 - radius - 1 + x);
self.blend_pixel(pt_l, color, alpha);
let pt_r = Point::new(r.x1 - radius + y - 1, r.y1 - radius - 1 + x);
self.blend_pixel(pt_r, color, alpha);
if first {
let b = Rect::new(pt_l.onright(), pt_r.under());
self.fill_rect(b, color);
}
}
}
}
}
impl Point {
fn onleft(self) -> Self {
Self {
x: self.x - 1,
..self
}
}
fn onright(self) -> Self {
Self {
x: self.x + 1,
..self
}
}
fn above(self) -> Self {
Self {
y: self.y - 1,
..self
}
}
fn under(self) -> Self {
Self {
y: self.y + 1,
..self
}
}
}

@ -0,0 +1,217 @@
use crate::ui::{
canvas::{bluralgo::BlurAlgorithm, rgb, Viewport},
display::Color,
geometry::{Offset, Point, Rect},
};
use crate::trezorhal::gdc::{Gdc, GdcBitmap, GdcBuffer};
// ==========================================================================
// RGB565 Canvas
// ==========================================================================
pub struct Canvas<'a> {
buff: GdcBuffer<'a, u16>,
bitmap: GdcBitmap<'a>,
viewport: Viewport,
}
impl<'a> Canvas<'a> {
pub fn new(size: Offset) -> Option<Self> {
if size.x > 0 && size.y > 0 {
let width = size.x as usize;
let height = size.y as usize;
let mut buff = GdcBuffer::<u16>::alloc(height * width)?;
let stride = width * 2;
let bitmap = GdcBitmap::new_rgb565(&mut buff, stride, size);
Some(Self {
buff,
bitmap,
viewport: Viewport::from_size(size),
})
} else {
panic!();
}
}
pub fn row(&mut self, row: i16) -> Option<&mut [u16]> {
if row >= 0 && row < self.bitmap.height() {
let offset = (self.bitmap.stride()) / 2 * row as usize;
Some(&mut self.buff.data[offset..offset + self.bitmap.width() as usize])
} else {
None
}
}
pub fn row_bytes(&mut self, row: i16, height: i16) -> Option<&mut [u8]> {
if row >= 0
&& height > 0
&& row < self.bitmap.height()
&& row + height <= self.bitmap.height()
{
let offset = self.bitmap.stride() * row as usize;
let len = self.bitmap.stride() * height as usize;
unsafe {
Some(core::slice::from_raw_parts_mut(
(self.buff.data.as_mut_ptr() as *mut u8).add(offset),
len,
))
}
} else {
None
}
}
fn gdc<'b>(&'b mut self) -> Gdc<'b> {
self.bitmap.gdc()
}
}
impl<'a> rgb::RgbCanvas for Canvas<'a> {
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.bounds());
}
fn size(&self) -> Offset {
self.bitmap.size()
}
fn fill_rect(&mut self, r: Rect, color: Color) {
let r = r
.translate(self.viewport.origin)
.intersect(self.viewport.clip);
self.gdc().fill_rect(r, color);
}
fn draw_bitmap(&mut self, r: Rect, bitmap: &rgb::BitmapRef) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_b = bitmap
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc().draw_bitmap(r_clipped, &new_b);
}
fn draw_blended(&mut self, r: Rect, fg: &rgb::BitmapRef, bg: &rgb::BitmapRef) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_fg = fg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
let new_bg = bg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc().draw_blended(r_clipped, &new_fg, &new_bg);
}
fn draw_opaque_text(&mut self, r: Rect, text: &str, attr: &rgb::TextAttr) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_attr = attr
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc().draw_opaque_text(r_clipped, text, &new_attr);
}
fn draw_blended_text(
&mut self,
r: Rect,
text: &str,
attr: &rgb::TextAttr,
bg: &rgb::BitmapRef,
) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_attr = attr
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
let new_bg = bg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc()
.draw_blended_text(r_clipped, text, &new_attr, &new_bg);
}
fn draw_pixel(&mut self, pt: Point, color: Color) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
self.row(pt.y).unwrap()[pt.x as usize] = color.into();
}
}
}
impl<'a> rgb::RgbCanvasEx for Canvas<'a> {
fn bitmap<'b>(&'b self) -> rgb::BitmapRef<'b> {
rgb::BitmapRef::new(&self.bitmap)
}
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
let bg_color: Color = self.row(pt.y).unwrap()[pt.x as usize].into();
self.row(pt.y).unwrap()[pt.x as usize] = bg_color.blend(color, alpha).into();
}
}
/// This function applies a blur effect to the specified rectangle.
//
/// The blur effect works properly only when the rectangle is not clipped,
/// which is a strong constraint that's hard to be met. The function uses a
/// simple box filter, where the 'radius' argument represents the length
/// of the sides of this filter.
///
/// It's important to be aware that strong artifacts may appear on images
/// with horizontal/vertical lines
fn blur_rect(&mut self, r: Rect, radius: usize) {
let clip = r
.translate(self.viewport.origin)
.intersect(self.viewport.clip);
let ofs = radius as i16;
if clip.width() > 2 * ofs - 1 && clip.height() > 2 * ofs - 1 {
let mut blur = BlurAlgorithm::new(clip.width() as usize, radius).unwrap();
for y in (clip.y0 - ofs)..(clip.y0 + ofs) {
let rd_y = core::cmp::max(y, clip.y0);
let row = self.row(rd_y).unwrap();
blur.push(&row[clip.x0 as usize..clip.x1 as usize]);
}
for y in clip.y0..clip.y1 {
let rd_y = core::cmp::min(y + ofs, clip.y1 - 1);
let row = self.row(rd_y).unwrap();
blur.push(&row[clip.x0 as usize..clip.x1 as usize]);
let row = self.row(y).unwrap();
blur.pop(&mut row[clip.x0 as usize..clip.x1 as usize]);
}
}
}
}
impl Color {
pub fn blend(self, fg: Color, alpha: u8) -> Color {
let alpha = alpha as u16;
let fg_r: u16 = (fg.to_u16() & 0xF800) >> 11;
let bg_r: u16 = (self.to_u16() & 0xF800) >> 11;
let r = (fg_r * alpha + (bg_r * (255 - alpha))) / 255;
let fg_g: u16 = (fg.to_u16() & 0x07E0) >> 5;
let bg_g: u16 = (self.to_u16() & 0x07E0) >> 5;
let g = (fg_g * alpha + (bg_g * (255 - alpha))) / 255;
let fg_b: u16 = (fg.to_u16() & 0x001F) >> 0;
let bg_b: u16 = (self.to_u16() & 0x001F) >> 0;
let b = (fg_b * alpha + (bg_b * (255 - alpha))) / 255;
return ((r << 11) | (g << 5) | b).into();
}
}

@ -0,0 +1,140 @@
use crate::ui::{
canvas::{rgb, Viewport},
display::Color,
geometry::{Offset, Point, Rect},
};
use crate::trezorhal::gdc::{Gdc, GdcBitmap, GdcBuffer};
// ==========================================================================
// RGB565 Canvas
// ==========================================================================
pub struct Canvas<'a> {
buff: GdcBuffer<'a, u32>,
bitmap: GdcBitmap<'a>,
viewport: Viewport,
}
impl<'a> Canvas<'a> {
pub fn new(size: Offset) -> Option<Self> {
if size.x > 0 && size.y > 0 {
let width = size.x as usize;
let height = size.y as usize;
let mut buff = GdcBuffer::<u32>::alloc(height * width)?;
let stride = width * 4;
let bitmap = GdcBitmap::new_rgba8888(&mut buff, stride, size);
Some(Self {
buff,
bitmap,
viewport: Viewport::from_size(size),
})
} else {
panic!();
}
}
pub fn row(&mut self, row: i16) -> Option<&mut [u32]> {
if row >= 0 && row < self.bitmap.height() {
let offset = self.bitmap.stride() / 4 * row as usize;
Some(&mut self.buff.data[offset..offset + self.bitmap.width() as usize])
} else {
None
}
}
fn gdc<'b>(&'b mut self) -> Gdc<'b> {
self.bitmap.gdc()
}
}
impl<'a> rgb::RgbCanvas for Canvas<'a> {
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.bounds());
}
fn size(&self) -> Offset {
self.bitmap.size()
}
fn fill_rect(&mut self, r: Rect, color: Color) {
let r = r
.translate(self.viewport.origin)
.intersect(self.viewport.clip);
self.gdc().fill_rect(r, color);
}
fn draw_bitmap(&mut self, r: Rect, bitmap: &rgb::BitmapRef) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_b = bitmap
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc().draw_bitmap(r_clipped, &new_b);
}
fn draw_blended(&mut self, r: Rect, fg: &rgb::BitmapRef, bg: &rgb::BitmapRef) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_fg = fg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
let new_bg = bg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc().draw_blended(r_clipped, &new_fg, &new_bg);
}
fn draw_opaque_text(&mut self, r: Rect, text: &str, attr: &rgb::TextAttr) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_attr = attr
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc().draw_opaque_text(r_clipped, text, &new_attr);
}
fn draw_blended_text(
&mut self,
r: Rect,
text: &str,
attr: &rgb::TextAttr,
bg: &rgb::BitmapRef,
) {
let r_moved = r.translate(self.viewport.origin);
let r_clipped = r_moved.intersect(self.viewport.clip);
let new_attr = attr
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
let new_bg = bg
.clone()
.with_offset(r_clipped.top_left() - r_moved.top_left());
self.gdc()
.draw_blended_text(r_clipped, text, &new_attr, &new_bg);
}
fn draw_pixel(&mut self, pt: Point, color: Color) {
let pt = pt + self.viewport.origin;
if self.viewport.clip.contains(pt) {
self.row(pt.y).unwrap()[pt.x as usize] = color.into();
}
}
}
impl<'a> rgb::RgbCanvasEx for Canvas<'a> {
fn bitmap(&self) -> rgb::BitmapRef {
rgb::BitmapRef::new(&self.bitmap)
}
fn blend_pixel(&mut self, _pt: Point, _color: Color, _alpha: u8) {
// TODO: not implemented yet, requires 32-bit color blending routines
}
fn blur_rect(&mut self, _r: Rect, _radius: usize) {
// TODO
}
}

@ -0,0 +1,266 @@
use crate::ui::{
canvas::{display, rgb::RgbCanvas, Viewport},
display::{toif::Toif, Color, Font},
geometry::{Insets, Offset, Point, Rect},
shape,
shape::{ProgressiveRenderer, Renderer},
};
use crate::time;
use core::fmt::Write;
use heapless::String;
use static_alloc::Bump;
use crate::{
trezorhal::io::io_touch_read,
ui::{
event::TouchEvent,
model_tt::theme::bootloader::{FIRE40, REFRESH24, WARNING40},
},
};
const ICON_GOOGLE: &[u8] = include_res!("model_tt/res/fido/icon_google.toif");
fn draw_component1<'a>(target: &mut impl Renderer<'a>) {
let r = Rect::from_top_left_and_size(Point::new(30, 120), Offset::new(180, 60));
shape::Bar::new(r)
.with_radius(16)
.with_bg(Color::rgb(96, 128, 128))
.render(target);
let r = Rect::from_top_left_and_size(Point::new(50, 50), Offset::new(50, 50));
shape::Bar::new(r)
.with_fg(Color::rgb(128, 0, 192))
.with_bg(Color::rgb(192, 0, 0))
.with_thickness(4)
.render(target);
let r = Rect::new(Point::zero(), Point::new(16, 160));
shape::Bar::new(r.translate(Offset::new(140, 40)))
.with_bg(Color::rgb(0, 160, 0))
.render(target);
let r = Rect::new(Point::new(0, 0), Point::new(240, 240));
shape::Text::new(r, "TREZOR!!!")
.with_fg(Color::rgb(255, 0, 0))
.render(target);
let r = Rect::new(Point::new(80, 0), Point::new(240, 240));
shape::Text::new(r, "TREZOR!!!")
.with_fg(Color::rgb(0, 255, 0))
.render(target);
let r = Rect::new(Point::new(160, 0), Point::new(240, 240));
shape::Text::new(r, "TREZOR!!!")
.with_fg(Color::rgb(0, 0, 255))
.render(target);
let r = Rect::new(Point::new(80, 80), Point::new(240, 240));
shape::Text::new(r, "BITCOIN!")
.with_font(Font::BOLD)
.render(target);
let r = Rect::new(Point::new(80, 140), Point::new(240, 240));
let s = "SatoshiLabs";
shape::Text::new(r, s)
.with_fg(Color::rgb(0, 255, 255))
.render(target);
shape::Text::new(r.translate(Offset::new(1, 1)), s)
.with_fg(Color::rgb(255, 0, 0))
.render(target);
let pt = Point::new(-1, 40);
let toif = Toif::new(REFRESH24).unwrap();
shape::ToifImage::new(pt, toif)
.with_fg(Color::black())
.with_bg(Color::white())
.render(target);
let pt = Point::new(80, 40);
let toif = Toif::new(FIRE40).unwrap();
shape::ToifImage::new(pt, toif).render(target);
let pt = Point::new(95, 50);
let toif = Toif::new(WARNING40).unwrap();
shape::ToifImage::new(pt, toif)
.with_fg(Color::rgb(64, 192, 200))
.render(target);
let pt = Point::new(0, 70);
let toif = Toif::new(ICON_GOOGLE).unwrap();
shape::ToifImage::new(pt, toif).render(target);
let pt = Point::new(120, 120);
shape::Circle::new(pt, 20)
.with_bg(Color::white())
.render(target);
}
fn draw_component2<'a>(target: &mut impl Renderer<'a>) {
let pt = Point::new(120, 110);
shape::Circle::new(pt, 60)
.with_bg(Color::rgb(80, 80, 80))
.render(target);
shape::Circle::new(pt, 42)
.with_bg(Color::rgb(0, 0, 0))
.with_fg(Color::white())
.with_thickness(2)
.render(target);
let toif = Toif::new(FIRE40).unwrap();
let icon_tl = Point::new(pt.x - toif.width() / 2, pt.y - toif.height() / 2);
shape::ToifImage::new(icon_tl, toif).render(target);
let r = Rect::new(Point::new(35, 190), Point::new(240, 240));
shape::Text::new(r, "Installing firmware")
.with_fg(Color::white())
.render(target);
}
const IMAGE_HOMESCREEN: &[u8] = include_res!("minion.jpg");
fn draw_component3<'a>(target: &mut impl Renderer<'a>) {
shape::JpegImage::new(Point::new(0, 0), IMAGE_HOMESCREEN).render(target);
}
#[link_section = ".no_dma_buffers"]
static mut POOL: Bump<[u8; 32 * 1024]> = Bump::uninit();
fn draw_screen(split: Point) -> time::Duration {
let start_time = time::Instant::now();
let bump = unsafe { &mut *core::ptr::addr_of_mut!(POOL) };
{
let mut canvas = display::Canvas::acquire().unwrap();
let vp = canvas.set_window(canvas.bounds().inset(Insets::new(20, 0, 0, 0)));
for _ in 1..=1 {
let mut target =
ProgressiveRenderer::new(&mut canvas, Some(Color::rgb(0, 0, 48)), bump, 30);
target.set_viewport(vp.with_origin(Offset::new(split.x, split.y)));
draw_component3(&mut target);
target.set_viewport(vp.with_origin(Offset::new(split.x - 240, split.y)));
draw_component2(&mut target);
target.set_viewport(vp);
/*let r = Rect::new(Point::new(60, 60), Point::new(180, 180));
//let r = Rect::new(Point::new(0, 0), Point::new(240, 240));
//Blurring::new(r, 1).render(&mut target);
shape::Blurring::new(r, 2).render(&mut target);
//Blurring::new(r, 3).render(&mut target);
//Blurring::new(r, 4).render(&mut target);
shape::Bar::new(r)
.with_fg(Color::white())
.render(&mut target);*/
target.render(16);
}
}
bump.reset();
time::Instant::now()
.checked_duration_since(start_time)
.unwrap()
}
fn draw_info(duration: time::Duration) {
let bump = unsafe { &mut *core::ptr::addr_of_mut!(POOL) };
{
let mut canvas = display::Canvas::acquire().unwrap();
canvas.set_viewport(Viewport::from_size(Offset::new(240, 20)));
let blue = Color::rgb(0, 0, 255);
let yellow = Color::rgb(255, 255, 0);
let mut target = ProgressiveRenderer::new(&mut canvas, Some(blue), bump, 10);
let mut info = String::<128>::new();
write!(info, "time={}ms", duration.to_millis() as f32 / 1.0).unwrap();
let text = info.as_str();
let r = Rect::new(Point::new(0, 0), Point::new(240, 240));
shape::Text::new(r, text)
.with_fg(yellow)
.render(&mut target);
target.render(20);
}
bump.reset();
}
#[derive(Copy, Clone)]
struct PocContext {
split: Point,
origin: Point,
delta: Offset,
pressed: bool,
}
static mut POC_CONTEXT: PocContext = PocContext {
split: Point::zero(),
origin: Point::zero(),
delta: Offset::zero(),
pressed: false,
};
fn touch_event() -> Option<TouchEvent> {
let event = io_touch_read();
if event == 0 {
return None;
}
let event_type = event >> 24;
let ex = ((event >> 12) & 0xFFF) as i16;
let ey = (event & 0xFFF) as i16;
TouchEvent::new(event_type, ex as _, ey as _).ok()
}
#[no_mangle]
extern "C" fn new_drawing_poc() {
let mut ctx = unsafe { POC_CONTEXT };
match touch_event() {
Some(TouchEvent::TouchStart(pt)) => {
ctx.origin = pt;
}
Some(TouchEvent::TouchMove(pt)) => {
let delta = pt - ctx.origin;
let k = 2;
ctx.delta.x = (ctx.delta.x * k + delta.x * (10 - k)) / 10;
ctx.delta.y = (ctx.delta.y * k + delta.y * (10 - k)) / 10;
}
Some(TouchEvent::TouchEnd(_pt)) => {
ctx.split = ctx.split + ctx.delta;
ctx.pressed = false;
ctx.delta = Offset::zero();
}
None => {
if ctx.split.x < 0 {
ctx.split.x -= ctx.split.x / 4;
} else if ctx.split.x > 240 {
ctx.split.x -= (ctx.split.x - 240) / 4;
}
if ctx.split.y < -120 {
ctx.split.y = -120;
} else if ctx.split.y > 120 {
ctx.split.y = 120;
}
}
}
let duration = draw_screen(ctx.split + ctx.delta);
draw_info(duration);
unsafe {
POC_CONTEXT = ctx;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -2,6 +2,7 @@
pub mod macros;
pub mod animation;
pub mod canvas;
pub mod component;
pub mod constant;
pub mod display;
@ -9,6 +10,7 @@ pub mod event;
pub mod geometry;
pub mod lerp;
pub mod screens;
pub mod shape;
#[macro_use]
pub mod util;
@ -19,3 +21,5 @@ pub mod layout;
pub mod model_tr;
#[cfg(feature = "model_tt")]
pub mod model_tt;
pub mod gdc_poc; // !!! REMOVE

@ -0,0 +1,113 @@
/// This module implements `Bar` shape for rendering various
/// types of rectangles. These rectangle might have optional
/// rounded corners and outline.
use crate::ui::{
canvas::rgb::RgbCanvasEx,
display::Color,
geometry::{Insets, Rect},
};
use super::{DrawingContext, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
pub struct Bar {
area: Rect,
fg_color: Option<Color>,
bg_color: Option<Color>,
thickness: i16,
radius: i16,
}
impl Bar {
pub fn new(area: Rect) -> Self {
Self {
area,
fg_color: None,
bg_color: None,
thickness: 1,
radius: 0,
}
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self {
fg_color: Some(fg_color),
..self
}
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: Some(bg_color),
..self
}
}
pub fn with_radius(self, radius: i16) -> Self {
Self { radius, ..self }
}
pub fn with_thickness(self, thickness: i16) -> Self {
Self { thickness, ..self }
}
pub fn render<'a>(self, renderer: &mut impl Renderer<'a>) {
renderer.render_shape(self);
}
}
impl Shape for Bar {
fn bounds(&self, _context: &mut DrawingContext) -> Rect {
self.area
}
fn cleanup(&self, _context: &mut DrawingContext) {}
fn draw(&self, canvas: &mut dyn RgbCanvasEx, _context: &mut DrawingContext) {
// NOTE: drawing of rounded bars without a background
// is not supported. If we needed it, we would have to
// introduce a new function in RgbCanvas.
// TODO: panic! in unsupported scenarious
let th = match self.fg_color {
Some(_) => self.thickness,
None => 0,
};
if self.radius == 0 {
if let Some(fg_color) = self.fg_color {
// outline
let r = self.area;
canvas.fill_rect(Rect { y1: r.y0 + th, ..r }, fg_color);
canvas.fill_rect(Rect { x1: r.x0 + th, ..r }, fg_color);
canvas.fill_rect(Rect { x0: r.x1 - th, ..r }, fg_color);
canvas.fill_rect(Rect { y0: r.y1 - th, ..r }, fg_color);
}
if let Some(bg_color) = self.bg_color {
// background
let bg_r = self.area.inset(Insets::uniform(th));
canvas.fill_rect(bg_r, bg_color);
}
} else {
if let Some(fg_color) = self.fg_color {
canvas.fill_round_rect(self.area, self.radius, fg_color);
}
if let Some(bg_color) = self.bg_color {
let bg_r = self.area.inset(Insets::uniform(th));
canvas.fill_round_rect_aa(bg_r, self.radius, bg_color);
}
}
}
}
impl ShapeClone for Bar {
fn clone_at_pool<'alloc, T>(self, pool: &'alloc T) -> Option<&'alloc mut dyn Shape>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = pool.alloc_t::<Bar>()?;
Some(clone.uninit.init(Bar { ..self }))
}
}

@ -0,0 +1,42 @@
use crate::ui::{canvas::rgb::RgbCanvasEx, geometry::Rect};
use super::DrawingContext;
use without_alloc::alloc::LocalAllocLeakExt;
// ==========================================================================
// trait Shape
// ==========================================================================
/// All shapes (like `Bar`, `Text`, `Icon`, ...) that can be rendered
/// must implement Shape. This trait is used internally
/// by so-called Renderers - `DirectRenderer` & `ProgressiveRederer`. Shape
/// objects may use DrawingContext as a scratch-pad memory or for caching
/// expensive calculations results.
pub trait Shape {
/// Returns the smallest bounding rectangle containing whole parts of the
/// shape This method is used by renderer for optimization if the shape
/// must be renderer or not
fn bounds(&self, context: &mut DrawingContext) -> Rect;
/// Draws shape on the canvas
fn draw(&self, canvas: &mut dyn RgbCanvasEx, context: &mut DrawingContext);
/// Releases data allocated in context memory
/// Is called by renderer if the shape draw() function won't be called
/// anymore
fn cleanup(&self, context: &mut DrawingContext);
}
// ==========================================================================
// trait ShapeClone
// ==========================================================================
/// All shapes (like `Bar`, `Text`, `Icon`, ...) that can be rendered
/// by `ProgressiveRender` must implement `ShapeClone`.
pub trait ShapeClone {
/// Clone a shape object at the specified memory pool
/// This method is used by renderers to store shape objects for deferred
/// drawing
fn clone_at_pool<'alloc, T>(self, pool: &'alloc T) -> Option<&'alloc mut dyn Shape>
where
T: LocalAllocLeakExt<'alloc>;
}

@ -0,0 +1,46 @@
use crate::ui::{canvas::rgb::RgbCanvasEx, geometry::Rect};
use super::{DrawingContext, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
// ==========================================================================
// struct Blur
// ==========================================================================
pub struct Blurring {
area: Rect,
radius: usize,
}
impl Blurring {
pub fn new(area: Rect, radius: usize) -> Self {
Self { area, radius }
}
pub fn render<'a>(self, renderer: &mut impl Renderer<'a>) {
renderer.render_shape(self);
}
}
impl Shape for Blurring {
fn bounds(&self, _context: &mut DrawingContext) -> Rect {
self.area
}
fn cleanup(&self, _context: &mut DrawingContext) {}
fn draw(&self, canvas: &mut dyn RgbCanvasEx, _context: &mut DrawingContext) {
canvas.blur_rect(self.area, self.radius);
}
}
impl ShapeClone for Blurring {
fn clone_at_pool<'alloc, T>(self, pool: &'alloc T) -> Option<&'alloc mut dyn Shape>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = pool.alloc_t::<Blurring>()?;
Some(clone.uninit.init(Blurring { ..self }))
}
}

@ -0,0 +1,109 @@
/// This module implements the `Circle` shape for rendering
/// filled circles, circles with outlines and specified thickness,
/// or just the outline of a circle.
use crate::ui::{
canvas::rgb::RgbCanvasEx,
display::Color,
geometry::{Point, Rect},
};
use super::{DrawingContext, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
pub struct Circle {
center: Point,
radius: i16,
fg_color: Option<Color>,
bg_color: Option<Color>,
thickness: i16,
}
impl Circle {
pub fn new(center: Point, radius: i16) -> Self {
Self {
center,
radius,
fg_color: None,
bg_color: None,
thickness: 1,
}
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self {
fg_color: Some(fg_color),
..self
}
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: Some(bg_color),
..self
}
}
pub fn with_thickness(self, thickness: i16) -> Self {
Self {
thickness: thickness,
..self
}
}
pub fn render<'a>(self, renderer: &mut impl Renderer<'a>) {
renderer.render_shape(self);
}
}
impl Shape for Circle {
fn bounds(&self, _context: &mut DrawingContext) -> Rect {
let c = self.center;
let r = self.radius;
Rect::new(
Point::new(c.x - r, c.y - r),
Point::new(c.x + r + 1, c.y + r + 1),
)
}
fn cleanup(&self, _context: &mut DrawingContext) {}
fn draw(&self, canvas: &mut dyn RgbCanvasEx, _context: &mut DrawingContext) {
// NOTE: drawing of circles without a background and with a thickness > 1
// is not supported. If we needed it, we would have to
// introduce RgbCanvas::draw_ring() function.
// TODO: panic! in unsupported scenarious
let th = match self.fg_color {
Some(_) => self.thickness,
None => 0,
};
if self.thickness == 1 {
if let Some(color) = self.bg_color {
canvas.fill_circle_aa(self.center, self.radius, color);
}
if let Some(color) = self.fg_color {
canvas.draw_circle_aa(self.center, self.radius, color);
}
} else {
if let Some(color) = self.fg_color {
canvas.fill_circle_aa(self.center, self.radius, color);
}
if let Some(color) = self.bg_color {
canvas.fill_circle_aa(self.center, self.radius - th, color);
}
}
}
}
impl ShapeClone for Circle {
fn clone_at_pool<'alloc, T>(self, pool: &'alloc T) -> Option<&'alloc mut dyn Shape>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = pool.alloc_t::<Circle>()?;
Some(clone.uninit.init(Circle { ..self }))
}
}

@ -0,0 +1,14 @@
use without_alloc::alloc::LocalAllocLeakExt;
pub struct BlurCache<'a> {
data: &'a [u8],
}
impl<'a> BlurCache<'a> {
pub fn new<'alloc: 'a, T>(_pool: &'alloc T) -> BlurCache<'alloc>
where
T: LocalAllocLeakExt<'alloc>,
{
BlurCache { data: &[] }
}
}

@ -0,0 +1,53 @@
use super::{blur_cache::BlurCache, jpeg_cache::JpegCache, zlib_cache::ZlibCache};
use crate::ui::{
canvas::rgb::BitmapRef,
display::{tjpgd, toif::Toif},
geometry::{Offset, Point, Rect},
};
use without_alloc::alloc::LocalAllocLeakExt;
pub struct DrawingContext<'a> {
zlib_cache: ZlibCache<'a>,
jpeg_cache: JpegCache<'a>,
blur_cache: BlurCache<'a>,
//bitmap_cache: BitmapCache<'a>
}
impl<'a> DrawingContext<'a> {
pub fn new<T>(pool: &'a T) -> Self
where
T: LocalAllocLeakExt<'a>,
{
Self {
zlib_cache: ZlibCache::new(pool, 4),
jpeg_cache: JpegCache::new(pool, 1),
blur_cache: BlurCache::new(pool),
//bitmap_cache: BitmapCache::new(pool_dma),
}
}
pub fn deflate_toif(&mut self, toif: Toif<'static>, from_row: i16, dest_buf: &mut [u8]) {
let from_offset = toif.stride() * from_row as usize;
self.zlib_cache
.uncompress(toif.zdata(), from_offset, dest_buf)
.unwrap();
}
pub fn get_jpeg_size<'i: 'a>(&mut self, jpeg: &'i [u8]) -> Result<Offset, tjpgd::Error> {
self.jpeg_cache.get_size(jpeg)
}
pub fn decompress_jpeg<'i: 'a, F>(
&mut self,
jpeg: &'i [u8],
offset: Point,
output: F,
) -> Result<(), tjpgd::Error>
where
F: FnMut(Rect, &BitmapRef) -> bool,
{
self.jpeg_cache.decompress(jpeg, offset, output)
}
}

@ -0,0 +1,226 @@
use crate::ui::{
canvas::rgb::BitmapRef,
display::tjpgd,
geometry::{Offset, Point, Rect},
};
use crate::trezorhal::gdc::{GdcBitmap, GdcBitmapRef};
use core::cell::UnsafeCell;
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
// JDEC work buffer size
//
// number of quantization tables (n_qtbl) = 2..4 (typical 2)
// number of huffman tables (n_htbl) = 2..4 (typical 2)
// mcu size = 1 * 1 .. 2 * 2 = 1..4 (typical 4)
//
// hufflut_ac & hufflut_dc are required only if JD_FASTDECODE == 2 (default)
//
// ---------------------------------------------------------------------
// table | size calculation | MIN..MAX | TYP
// ---------------------------------------------------------------------
// qttbl | n_qtbl * size_of(i32) * 64 | 512..1024 | 512
// huffbits | n_htbl * size_of(u8) * 16 | 32..64 | 32
// huffcode | n_htbl * size_of(u16) * 256 | 1024..2048 | 1024
// huffdata | n_htbl * size_of(u8) * 256 | 512..1024 | 512
// hufflut_ac | n_htbl * size_of(u16) * 1024 | 4096..8192 | 4096
// hufflut_dc | n_htbl * size_of(u8) * 1024 | 2048..4096 | 2048
// workbuf | mcu_size * 192 + 64 | 256..832 | 832
// mcubuf | (mcu_size + 2) * size_of(u16) * 64 | 384..768 | 768
// inbuff | JD_SZBUF constant | 512..512 | 512
// ---------------------------------------------------------------|------
// SUM | | 9376..18560 | 10336
// ---------------------------------------------------------------|------
const JPEG_DECODER_POOL_SIZE: usize = 10500; // the same const > 10336 as in original code
pub struct JpegCacheSlot<'a> {
// Reference to compressed data
jpeg: &'a [u8],
// Input buffer referencing compressed data
input: Option<tjpgd::BufferInput<'a>>,
// JPEG decoder instance
decoder: Option<tjpgd::JDEC<'a>>,
// Scratchpad memory used by the JPEG decoder
// (it's used just by our decoder and nobody else)
scratchpad: &'a UnsafeCell<[u8; JPEG_DECODER_POOL_SIZE]>,
}
impl<'a> JpegCacheSlot<'a> {
fn new<'alloc: 'a, T>(pool: &'alloc T) -> JpegCacheSlot<'a>
where
T: LocalAllocLeakExt<'alloc>,
{
let scratchpad = unwrap!(pool.alloc_t::<UnsafeCell<[u8; JPEG_DECODER_POOL_SIZE]>>())
.uninit
.init(UnsafeCell::new([0; JPEG_DECODER_POOL_SIZE]));
Self {
jpeg: &[],
input: None,
decoder: None,
scratchpad: scratchpad,
}
}
fn reset<'i: 'a>(&mut self, jpeg: &'i [u8]) -> Result<(), tjpgd::Error> {
// Drop the existing decoder holding
// a mutable reference to the scratchpad buffer
self.decoder = None;
if jpeg.len() > 0 {
// Now there's nobody else holding any reference to our window
// so we can get a mutable reference and pass it to a new
// instance of the JPEG decoder
let scratchpad = unsafe { &mut *self.scratchpad.get() };
// Prepare a input buffer
let mut input = tjpgd::BufferInput(jpeg);
// Initialize the decoder by reading headers from input
self.decoder = Some(tjpgd::JDEC::new(&mut input, scratchpad)?);
// Save modified input buffer
self.input = Some(input);
} else {
self.input = None;
}
self.jpeg = jpeg;
Ok(())
}
fn is_for<'i: 'a>(&self, jpeg: &'i [u8]) -> bool {
jpeg == self.jpeg && self.decoder.is_some()
}
pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8]) -> Result<Offset, tjpgd::Error> {
if !self.is_for(jpeg) {
self.reset(jpeg)?;
}
let decoder = unwrap!(self.decoder.as_mut());
Ok(Offset::new(decoder.width(), decoder.height()))
}
// left-top origin of output rectangle must be aligned to JPEG MCU size
pub fn decompress<'i: 'a, F>(
&mut self,
jpeg: &'i [u8],
offset: Point,
output: F,
) -> Result<(), tjpgd::Error>
where
F: FnMut(Rect, &BitmapRef) -> bool,
{
// Reset the slot if the JPEG image is different
if !self.is_for(jpeg) {
self.reset(jpeg)?;
}
// Get coordinates of the next coming MCU
let decoder = unwrap!(self.decoder.as_ref());
let next_mcu = Offset::new(decoder.next_mcu().0 as i16, decoder.next_mcu().1 as i16);
// Get height of the MCUs (8 or 16pixels)
let mcu_height = decoder.mcu_height() as i16;
// Reset the decoder if any part of src_clip was already decoded
if offset.y < next_mcu.y || (offset.x < next_mcu.x && offset.y < next_mcu.y + mcu_height) {
self.reset(self.jpeg)?;
}
let decoder = unwrap!(self.decoder.as_mut());
let input = unwrap!(self.input.as_mut());
let mut drawer = JpegFnOutput::new(output);
match decoder.decomp2(input, &mut drawer) {
Ok(_) | Err(tjpgd::Error::Interrupted) => Ok(()),
Err(e) => return Err(e),
}
}
}
struct JpegFnOutput<F>
where
F: FnMut(Rect, &BitmapRef) -> bool,
{
output: F,
}
impl<F> JpegFnOutput<F>
where
F: FnMut(Rect, &BitmapRef) -> bool,
{
fn new(output: F) -> Self {
Self { output }
}
}
impl<F> trezor_tjpgdec::JpegOutput for JpegFnOutput<F>
where
F: FnMut(Rect, &BitmapRef) -> bool,
{
fn write(
&mut self,
_jd: &tjpgd::JDEC,
rect_origin: (u32, u32),
rect_size: (u32, u32),
pixels: &[u16],
) -> bool {
// MCU coordinates in source image
let mcu_r = Rect::from_top_left_and_size(
Point::new(rect_origin.0 as i16, rect_origin.1 as i16),
Offset::new(rect_size.0 as i16, rect_size.1 as i16),
);
// Create readonly bitmap in the memory not accessible with DMA
let mcu_bitmap =
GdcBitmap::from_rgb565_slice(pixels, (mcu_r.width() * 2) as usize, mcu_r.size());
// Return true to continue decompression
(self.output)(mcu_r, &GdcBitmapRef::new(&mcu_bitmap))
}
}
pub struct JpegCache<'a> {
slots: FixedVec<'a, JpegCacheSlot<'a>>,
}
impl<'a> JpegCache<'a> {
pub fn new<'alloc: 'a, T>(pool: &'alloc T, slot_count: usize) -> JpegCache<'a>
where
T: LocalAllocLeakExt<'alloc>,
{
assert!(slot_count <= 1); // we support just 1 decoder
let mut cache = JpegCache {
slots: unwrap!(pool.fixed_vec(slot_count)),
};
for _ in 0..cache.slots.capacity() {
unwrap!(cache.slots.push(JpegCacheSlot::new(pool)));
}
cache
}
pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8]) -> Result<Offset, tjpgd::Error> {
if self.slots.capacity() > 0 {
self.slots[0].get_size(jpeg)
} else {
Err(tjpgd::Error::MemoryPool)
}
}
pub fn decompress<'i: 'a, F>(
&mut self,
jpeg: &'i [u8],
offset: Point,
output: F,
) -> Result<(), tjpgd::Error>
where
F: FnMut(Rect, &BitmapRef) -> bool,
{
if self.slots.capacity() > 0 {
self.slots[0].decompress(jpeg, offset, output)
} else {
Err(tjpgd::Error::MemoryPool)
}
}
}

@ -0,0 +1,6 @@
mod blur_cache;
mod context;
mod jpeg_cache;
mod zlib_cache;
pub use context::DrawingContext;

@ -0,0 +1,166 @@
use crate::trezorhal::uzlib::{UzlibContext, UZLIB_WINDOW_SIZE};
use core::cell::UnsafeCell;
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
struct ZlibCacheSlot<'a> {
// Reference to compressed data
zdata: &'a [u8],
// Current offset in docempressed data
offset: usize,
// Decompression context for the current zdata
dc: Option<UzlibContext<'a>>,
// Window used by current decompression context
// (it's used just by own dc and nobody else)
window: &'a UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>,
}
impl<'a> ZlibCacheSlot<'a> {
fn new<'alloc: 'a, T>(pool: &'alloc T) -> ZlibCacheSlot<'a>
where
T: LocalAllocLeakExt<'alloc>,
{
let window = unwrap!(pool.alloc_t::<UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>>())
.uninit
.init(UnsafeCell::new([0; UZLIB_WINDOW_SIZE]));
ZlibCacheSlot {
zdata: &[],
offset: 0,
dc: None,
window,
}
}
// May be called with zdata == &[] to make the slot free
fn reset(&mut self, zdata: &'static [u8]) {
// Drop the existing decompression context holding
// a mutable reference to window buffer
self.dc = None;
if zdata.len() > 0 {
// Now there's nobody else holding any reference to our window
// so we can get mutable reference and pass it to a new
// instance of the decompression context
let window = unsafe { &mut *self.window.get() };
self.dc = Some(UzlibContext::new(zdata, Some(window)));
}
self.offset = 0;
self.zdata = zdata;
}
fn uncompress(&mut self, dest_buf: &mut [u8]) -> Result<bool, ()> {
if let Some(dc) = self.dc.as_mut() {
match dc.uncompress(dest_buf) {
Ok(done) => {
if done {
self.reset(&[]);
} else {
self.offset += dest_buf.len();
}
Ok(done)
}
Err(e) => Err(e),
}
} else {
Err(())
}
}
fn skip(&mut self, nbytes: usize) -> Result<bool, ()> {
if let Some(dc) = self.dc.as_mut() {
match dc.skip(nbytes) {
Ok(done) => {
if done {
self.reset(&[]);
} else {
self.offset += nbytes;
}
Ok(done)
}
Err(e) => Err(e),
}
} else {
Err(())
}
}
fn is_for<'b>(&self, zdata: &'b [u8], offset: usize) -> bool {
self.zdata == zdata && self.offset == offset
}
}
pub struct ZlibCache<'a> {
slots: FixedVec<'a, ZlibCacheSlot<'a>>,
}
impl<'a> ZlibCache<'a> {
pub fn new<'alloc: 'a, T>(pool: &'alloc T, slot_count: usize) -> ZlibCache<'alloc>
where
T: LocalAllocLeakExt<'alloc>,
{
let mut cache = ZlibCache {
slots: unwrap!(pool.fixed_vec(slot_count)),
};
for _ in 0..cache.slots.capacity() {
unwrap!(cache.slots.push(ZlibCacheSlot::new(pool)));
}
cache
}
fn select_slot_for_reuse(&self) -> Result<usize, ()> {
if self.slots.capacity() > 0 {
let mut selected = 0;
for (i, slot) in self.slots.iter().enumerate() {
if slot.dc.is_none() {
selected = i;
break;
}
}
Ok(selected)
} else {
Err(())
}
}
pub fn uncompress(
&mut self,
zdata: &'static [u8],
offset: usize,
dest_buf: &mut [u8],
) -> Result<bool, ()> {
let slot = self
.slots
.iter_mut()
.find(|slot| slot.is_for(zdata, offset));
if let Some(slot) = slot {
slot.uncompress(dest_buf)
} else {
let selected = self.select_slot_for_reuse()?;
let slot = &mut self.slots[selected];
slot.reset(zdata);
slot.skip(offset)?;
slot.uncompress(dest_buf)
}
}
}
impl<'a> UzlibContext<'a> {
// TODO: move to trezorhal !!!
pub fn skip(&mut self, nbytes: usize) -> Result<bool, ()> {
let mut result = false; // false => OK, true => DONE
let mut sink = [0u8; 256];
for i in (0..nbytes).step_by(sink.len()) {
let chunk_len = core::cmp::min(sink.len(), nbytes - i);
let chunk = &mut sink[0..chunk_len];
result = self.uncompress(chunk)?;
}
Ok(result)
}
}

@ -0,0 +1,78 @@
use crate::ui::{
canvas::rgb::RgbCanvasEx,
geometry::{Point, Rect},
};
use super::{DrawingContext, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
pub struct JpegImage {
pos: Point,
blur_radius: usize,
jpeg: &'static [u8],
}
impl JpegImage {
pub fn new(pos: Point, jpeg: &'static [u8]) -> Self {
JpegImage {
pos,
blur_radius: 0,
jpeg,
}
}
pub fn with_blur(self, blur_radius: usize) -> Self {
Self {
blur_radius: blur_radius,
..self
}
}
pub fn render<'b>(self, renderer: &mut impl Renderer<'b>) {
renderer.render_shape(self);
}
}
impl Shape for JpegImage {
fn bounds(&self, context: &mut DrawingContext) -> Rect {
// TODO: consider caching size inside JpegImage structure
// (this unfortunately requires &mut self here, or adding another trait
// method init())
// TODO:: replace unwrap!
let size = unwrap!(context.get_jpeg_size(self.jpeg));
Rect::from_top_left_and_size(self.pos, size)
}
fn cleanup(&self, _context: &mut DrawingContext) {}
fn draw(&self, canvas: &mut dyn RgbCanvasEx, context: &mut DrawingContext) {
let clip = canvas.viewport().relative_clip(self.bounds(context)).clip;
// translate clip to JPEG relative coordinates
let clip = clip.translate(-canvas.viewport().origin);
let clip = clip.translate((-self.pos).into());
unwrap!(
context.decompress_jpeg(self.jpeg, clip.top_left(), |mcu_r, mcu_bitmap| {
// draw mcu (might be clipped if needed)
canvas.draw_bitmap(mcu_r.translate(self.pos.into()), mcu_bitmap);
// Return true if we are not done yet
mcu_r.x1 < clip.x1 || mcu_r.y1 < clip.y1
})
);
// TODO: add blurring variant
}
}
impl ShapeClone for JpegImage {
fn clone_at_pool<'alloc, T>(self, pool: &'alloc T) -> Option<&'alloc mut dyn Shape>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = pool.alloc_t::<JpegImage>()?;
Some(clone.uninit.init(JpegImage { ..self }))
}
}

@ -0,0 +1,58 @@
## Memory usage comparison
## Legacy solution
**Memory with DMA access**
```
buffer_line_16bpp @.buf 1440
buffer_line_4bpp @.buf 360
buffer_text @.buf 4320
-------------------------------------------------
6120
```
**Memory without DMA access**
```
buffer_jpeg @.no_dma 7680
buffer_jpeg_work @.no_dma 10500
buffer_blurring @.no_dma 14400
buffer_blurring_totals @.no_dma 1440
zlib context+window @.stack 2308
-------------------------------------------------
36328
```
## New drawing library
The memory usage is configurable, so the two options are considered.\
MIN variant is slower, but consumes less memory. OPT variant should
be sufficient for all purposes.
**Memory with DMA access**
```
MIN OPT
ProgressiveRenderer.slice @.buf 480 7680
ProgressiveRenderer.scratch @.buf 480 2048
---------------------------------------------------------
960 9728
```
**Memory without DMA access**
```
ProgressiveRenderer.list @.stack 512 2048
zlib decompression context @.no_dma 2308 6924
jpeg decompressor @.no_dma 10500 10500
partial jpeg image @.no_dma 7680 7680
blurring window/totals @.no_dma 7920 7920
------------------------------------------------------------------
28920 35072
```

@ -0,0 +1,19 @@
mod bar;
mod base;
mod blur;
mod circle;
mod context;
mod jpeg;
mod render;
mod text;
mod toif;
pub use bar::Bar;
pub use base::{Shape, ShapeClone};
pub use blur::Blurring;
pub use circle::Circle;
pub use context::DrawingContext;
pub use jpeg::JpegImage;
pub use render::{DirectRenderer, ProgressiveRenderer, Renderer};
pub use text::Text;
pub use toif::ToifImage;

@ -0,0 +1,271 @@
use crate::ui::{
canvas::{
rgb,
rgb::{RgbCanvas, RgbCanvasEx},
rgb565, Viewport,
},
display::{toif::Toif, Color},
geometry::{Offset, Point, Rect},
shape::{context::DrawingContext, Shape, ShapeClone},
};
use without_alloc::{alloc::LocalAllocLeakExt, FixedVec};
// ==========================================================================
// trait Renderer
// ==========================================================================
/// All renders must implement Renderer trait
/// Renderers can immediately use the draw() method of the passed shape or
/// may store it (using the boxed() method) and draw it later
pub trait Renderer<'a> {
fn viewport(&self) -> Viewport;
fn set_viewport(&mut self, viewport: Viewport);
fn set_window(&mut self, window: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_window(window));
viewport
}
fn set_clip(&mut self, clip: Rect) -> Viewport {
let viewport = self.viewport();
self.set_viewport(viewport.relative_clip(clip));
viewport
}
fn render_shape<S>(&mut self, shape: S)
where
S: Shape + ShapeClone;
}
// ==========================================================================
// struct DirectRenderer
// ==========================================================================
/// A simple implementation of a Renderer that draws directly onto the CanvasEx
pub struct DirectRenderer<'a> {
/// Target canvas
canvas: &'a mut dyn rgb::RgbCanvasEx,
/// Drawing context (decompression context, scratch-pad memory)
drawing_context: DrawingContext<'a>,
}
impl<'a> DirectRenderer<'a> {
/// Creates a new DirectRenderer instance with the given canvas
pub fn new<T>(
canvas: &'a mut dyn rgb::RgbCanvasEx,
bg_color: Option<Color>,
pool: &'a T,
) -> Self
where
T: LocalAllocLeakExt<'a>,
{
if let Some(color) = bg_color {
canvas.fill_background(color);
}
// TODO: consider storing original canvas.viewport
// and restoring it by drop() function
Self {
canvas,
drawing_context: DrawingContext::new(pool),
}
}
}
impl<'a> Renderer<'a> for DirectRenderer<'a> {
fn viewport(&self) -> Viewport {
self.canvas.viewport()
}
fn set_viewport(&mut self, viewport: Viewport) {
self.canvas.set_viewport(viewport);
}
fn render_shape<S>(&mut self, shape: S)
where
S: Shape + ShapeClone,
{
let context = &mut self.drawing_context;
if self.canvas.viewport().contains(shape.bounds(context)) {
shape.draw(self.canvas, context);
shape.cleanup(context);
}
}
}
// ==========================================================================
// struct ProgressiveRenderer
// ==========================================================================
struct ShapeHolder<'a> {
shape: &'a dyn Shape,
viewport: Viewport,
}
/// A more advanced Renderer implementation that supports deferred rendering.
pub struct ProgressiveRenderer<'a, T: LocalAllocLeakExt<'a>> {
/// Target canvas
canvas: &'a mut dyn rgb::RgbCanvas,
/// Pool for cloning shapes
pool: &'a T,
/// List of rendered shapes
shapes: FixedVec<'a, ShapeHolder<'a>>,
/// Current viewport
viewport: Viewport,
// Default background color
bg_color: Option<Color>,
/// Drawing context (decompression context, scratch-pad memory)
drawing_context: DrawingContext<'a>,
}
impl<'a, T> ProgressiveRenderer<'a, T>
where
T: LocalAllocLeakExt<'a>,
{
/// Creates a new ProgressiveRenderer instance
pub fn new(
canvas: &'a mut dyn rgb::RgbCanvas,
bg_color: Option<Color>,
pool: &'a T,
max_shapes: usize,
) -> Self {
let viewport = canvas.viewport();
Self {
canvas,
pool,
shapes: pool.fixed_vec(max_shapes).unwrap(),
viewport,
bg_color,
drawing_context: DrawingContext::new(pool),
}
}
/// Renders the stored shapes onto the specified canvas
pub fn render(&mut self, lines: usize) {
let canvas_clip = self.canvas.viewport().clip;
let canvas_origin = self.canvas.viewport().origin;
let mut slice =
rgb565::Canvas::new(Offset::new(canvas_clip.width(), lines as i16)).unwrap();
for y in (canvas_clip.y0..canvas_clip.y1).step_by(lines) {
// Calculate the coordinates of the slice we will draw into
let slice_r = Rect::new(
// slice_r is in absolute coordinates
Point::new(canvas_clip.x0, y),
Point::new(canvas_clip.x1, y + lines as i16),
)
.translate(-canvas_origin);
// Clear the slice background
if let Some(color) = self.bg_color {
slice.set_viewport(Viewport::from_size(slice_r.size()));
slice.fill_background(color);
}
// Draw all shapes that overlaps the slice
for holder in self.shapes.iter() {
let shape_viewport = holder.viewport.absolute_clip(slice_r);
let shape = holder.shape;
let context = &mut self.drawing_context;
let shape_bounds = shape.bounds(context);
// Is the shape overlapping the current slice?
if shape_viewport.contains(shape_bounds) {
slice.set_viewport(shape_viewport.translate((-slice_r.top_left()).into()));
shape.draw(&mut slice, context);
if shape_bounds.y1 + shape_viewport.origin.y <= shape_viewport.clip.y1 {
// The shape will never be drawn again
shape.cleanup(context);
}
}
}
self.canvas.draw_bitmap(slice_r, &slice.bitmap());
}
}
}
impl<'a, T> Renderer<'a> for ProgressiveRenderer<'a, T>
where
T: LocalAllocLeakExt<'a>,
{
fn viewport(&self) -> Viewport {
self.viewport
}
fn set_viewport(&mut self, viewport: Viewport) {
self.viewport = viewport.absolute_clip(self.canvas.bounds());
}
fn render_shape<S>(&mut self, shape: S)
where
S: Shape + ShapeClone,
{
// Is the shape visible?
if self
.viewport
.contains(shape.bounds(&mut self.drawing_context))
{
// Clone the shape & push it to the list
let holder = ShapeHolder {
shape: shape.clone_at_pool(self.pool).unwrap(),
viewport: self.viewport,
};
unwrap!(self.shapes.push(holder));
}
}
}
// ---------------------------------
// following code should be moved
impl<'i> Toif<'i> {
// TODO: move to display::toif !!!
pub fn stride(&self) -> usize {
if self.is_grayscale() {
self.width() as usize / 2
} else {
self.width() as usize * 2
}
}
}
impl Rect {
// TODO: move to geometry.rs !!!
pub fn from_size(size: Offset) -> Self {
Rect::from_top_left_and_size(Point::zero(), size)
}
pub fn has_intersection(&self, r: Rect) -> bool {
self.x0 < r.x1 && self.x1 > r.x0 && self.y0 < r.y1 && self.y1 > r.y0
}
pub fn intersect(&self, r: Rect) -> Rect {
Rect::new(
Point::new(core::cmp::max(self.x0, r.x0), core::cmp::max(self.y0, r.y0)),
Point::new(core::cmp::min(self.x1, r.x1), core::cmp::min(self.y1, r.y1)),
)
}
pub fn is_empty(&self) -> bool {
self.x0 >= self.x1 || self.y0 >= self.y1
}
}
impl core::ops::Neg for Point {
// TODO: move to geometry.rs !!!
type Output = Point;
fn neg(self) -> Self::Output {
Point {
x: -self.x,
y: -self.y,
}
}
}

@ -0,0 +1,69 @@
use crate::ui::{
canvas::rgb::{RgbCanvasEx, TextAttr},
display::{Color, Font},
geometry::Rect,
};
use super::{DrawingContext, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
// ==========================================================================
// struct Text
// ==========================================================================
pub struct Text<'a> {
area: Rect,
text: &'a str,
color: Color,
font: Font,
}
impl<'a> Text<'a> {
pub fn new(area: Rect, text: &'a str) -> Self {
Self {
area,
text,
color: Color::white(),
font: Font::NORMAL,
}
}
pub fn with_fg(self, color: Color) -> Self {
Self { color, ..self }
}
pub fn with_font(self, font: Font) -> Self {
Self { font, ..self }
}
pub fn render<'b>(self, renderer: &mut impl Renderer<'b>) {
renderer.render_shape(self);
}
}
impl<'a> Shape for Text<'a> {
fn bounds(&self, _context: &mut DrawingContext) -> Rect {
// TODO:: returned value could be possibly smaller
// (according to the text, font, offset and alignment)
self.area
}
fn cleanup(&self, _context: &mut DrawingContext) {}
fn draw(&self, canvas: &mut dyn RgbCanvasEx, _context: &mut DrawingContext) {
let attr = TextAttr::new().with_fg(self.color).with_font(self.font);
canvas.draw_text(self.area, &self.text, &attr);
}
}
impl<'a> ShapeClone for Text<'a> {
fn clone_at_pool<'alloc, T>(self, pool: &'alloc T) -> Option<&'alloc mut dyn Shape>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = pool.alloc_t::<Text>()?;
let text = pool.copy_str(self.text)?;
Some(clone.uninit.init(Text { text, ..self }))
}
}

@ -0,0 +1,141 @@
use crate::ui::{
canvas::{
mono::MonoCanvas,
mono4,
rgb::{RgbCanvas, RgbCanvasEx},
rgb565,
},
display::{toif::Toif, Color},
geometry::{Offset, Point, Rect},
};
use super::{DrawingContext, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
// ==========================================================================
// struct ToifImage
// ==========================================================================
// A rectangle filled with a solid color
pub struct ToifImage {
pos: Point,
toif: Toif<'static>,
fg_color: Color,
bg_color: Option<Color>,
}
impl ToifImage {
pub fn new(pos: Point, toif: Toif<'static>) -> Self {
Self {
pos,
toif,
fg_color: Color::white(),
bg_color: None,
}
}
pub fn with_fg(self, fg_color: Color) -> Self {
Self { fg_color, ..self }
}
pub fn with_bg(self, bg_color: Color) -> Self {
Self {
bg_color: Some(bg_color),
..self
}
}
pub fn render<'b>(self, renderer: &mut impl Renderer<'b>) {
renderer.render_shape(self);
}
fn draw_grayscale(&self, canvas: &mut dyn RgbCanvasEx, context: &mut DrawingContext) {
// TODO: introduce new viewport/shape function for this calculation
let viewport = canvas.viewport();
let mut clip = self
.bounds(context)
.intersect(viewport.clip.translate(-viewport.origin))
.translate((-self.pos).into());
// TODO: calculate optimal height of the slice
let mut slice = mono4::MonoCanvas::new(Offset::new(self.toif.width(), 32)).unwrap();
while !clip.is_empty() {
let height = core::cmp::min(slice.height(), clip.height());
context.deflate_toif(self.toif, clip.y0, slice.row_bytes(0, height).unwrap());
let r = clip.translate(self.pos.into());
// TODO: a bit strange here..
let slice_ref = slice
.bitmap()
.with_fg(self.fg_color)
.with_offset(Offset::new(r.x0 - self.pos.x, 0));
match self.bg_color {
Some(bg_color) => canvas.draw_bitmap(r, &slice_ref.with_bg(bg_color)),
None => canvas.blend_bitmap(r, &slice_ref),
}
clip.y0 += height;
}
}
fn draw_rgb(&self, canvas: &mut dyn RgbCanvasEx, context: &mut DrawingContext) {
// TODO: introduce new viewport/shape function for this calculation
let viewport = canvas.viewport();
let mut clip = self
.bounds(context)
.intersect(viewport.clip.translate(-viewport.origin))
.translate((-self.pos).into());
// TODO: calculate optimal height of the slice
let mut slice = rgb565::Canvas::new(Offset::new(self.toif.width(), 8)).unwrap();
while !clip.is_empty() {
let height = core::cmp::min(slice.height(), clip.height());
context.deflate_toif(self.toif, clip.y0, slice.row_bytes(0, height).unwrap());
let r = clip.translate(self.pos.into());
// TODO: a bit strange here..
let slice_ref = slice
.bitmap()
.with_offset(Offset::new(r.x0 - self.pos.x, 0));
canvas.draw_bitmap(r, &slice_ref);
clip.y0 += height;
}
}
}
impl Shape for ToifImage {
fn bounds(&self, _context: &mut DrawingContext) -> Rect {
let toif_size = Offset::new(self.toif.width(), self.toif.height());
Rect::from_top_left_and_size(self.pos, toif_size)
}
fn cleanup(&self, _context: &mut DrawingContext) {
// TODO: inform the context that we won't use the zlib slot anymore
}
fn draw(&self, canvas: &mut dyn RgbCanvasEx, context: &mut DrawingContext) {
if self.toif.is_grayscale() {
self.draw_grayscale(canvas, context);
} else {
self.draw_rgb(canvas, context);
}
}
}
impl ShapeClone for ToifImage {
fn clone_at_pool<'alloc, T>(self, pool: &'alloc T) -> Option<&'alloc mut dyn Shape>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = pool.alloc_t::<ToifImage>()?;
Some(clone.uninit.init(ToifImage { ..self }))
}
}

@ -1,4 +1,6 @@
#include TREZOR_BOARD
#include "../gdc/gdc.h"
#include "buffers.h"
#include "button.h"
#include "common.h"

@ -20,6 +20,8 @@
#ifndef TREZORHAL_DMA2D_H
#define TREZORHAL_DMA2D_H
#include "../gdc/gdc_core.h"
#include "common.h"
void dma2d_init(void);
@ -40,4 +42,10 @@ void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr,
void dma2d_wait_for_transfer(void);
void dma2d_wait(gdc_t* gdc);
bool dma2d_rgb565_fill(gdc_t* gdc, dma2d_params_t* dp);
bool dma2d_rgb565_copy_mono4(gdc_t* gdc, dma2d_params_t* dp);
bool dma2d_rgb565_copy_rgb565(gdc_t* gdc, dma2d_params_t* dp);
bool dma2d_rgb565_blend_mono4_rgb565(gdc_t* gdc, dma2d_params_t* dp);
#endif // TREZORHAL_DMA2D_H

@ -0,0 +1,311 @@
/*
* 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 STM32_HAL_H
#include <stddef.h>
#include "dma2d.h"
#include "../gdc/gdc_color.h"
static DMA2D_HandleTypeDef dma2d_handle = {
.Instance = (DMA2D_TypeDef*)DMA2D_BASE,
};
void dma2d_wait(gdc_t* gdc) {
while (HAL_DMA2D_PollForTransfer(&dma2d_handle, 10) != HAL_OK)
;
}
bool dma2d_rgb565_fill(gdc_t* gdc, dma2d_params_t* dp) {
dma2d_wait(gdc);
dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
dma2d_handle.Init.Mode = DMA2D_R2M;
dma2d_handle.Init.OutputOffset =
dp->dst_stride / sizeof(uint16_t) - dp->width;
HAL_DMA2D_Init(&dma2d_handle);
HAL_DMA2D_Start(&dma2d_handle, gdc_color_to_color32(dp->srca_fg),
(uint32_t)dp->dst_row + dp->dst_x * sizeof(uint16_t),
dp->width, dp->height);
return true;
}
/*
static void dma2d_config_clut(uint32_t layer, gdc_color_t fg, gdc_color_t bg) {
#define LAYER_COUNT 2
#define GRADIENT_STEPS 16
static struct {
gdc_color32_t gradient[GRADIENT_STEPS];
} cache[LAYER_COUNT] = { 0 };
if (layer >= LAYER_COUNT) {
return;
}
uint32_t c_fg = gdc_color_to_color32(fg);
uint32_t c_bg = gdc_color_to_color32(bg);
uint32_t* gradient = cache[layer].gradient;
if (c_bg != gradient[0] || c_fg != gradient[GRADIENT_STEPS - 1]) {
for (int step = 0; step < GRADIENT_STEPS; step++) {
gradient[step] = gdc_color32_blend_a4(fg, bg, step);
}
DMA2D_CLUTCfgTypeDef clut;
clut.CLUTColorMode = DMA2D_CCM_ARGB8888;
clut.Size = GRADIENT_STEPS - 1;
clut.pCLUT = gradient;
HAL_DMA2D_ConfigCLUT(&dma2d_handle, clut, layer);
while (HAL_DMA2D_PollForTransfer(&dma2d_handle, 10) != HAL_OK)
;
}
}*/
static void dma2d_config_clut(uint32_t layer, gdc_color_t fg, gdc_color_t bg) {
#define LAYER_COUNT 2
#define GRADIENT_STEPS 16
static struct {
gdc_color_t c_fg;
gdc_color_t c_bg;
} cache[LAYER_COUNT] = {0};
if (layer >= LAYER_COUNT) {
return;
}
volatile uint32_t* clut =
layer ? dma2d_handle.Instance->FGCLUT : dma2d_handle.Instance->BGCLUT;
if (fg != cache[layer].c_fg || bg != cache[layer].c_bg) {
cache[layer].c_fg = fg;
cache[layer].c_bg = bg;
for (int step = 0; step < GRADIENT_STEPS; step++) {
clut[step] = gdc_color32_blend_a4(fg, bg, step);
}
DMA2D_CLUTCfgTypeDef clut;
clut.CLUTColorMode = DMA2D_CCM_ARGB8888;
clut.Size = GRADIENT_STEPS - 1;
clut.pCLUT = 0; // ???
HAL_DMA2D_ConfigCLUT(&dma2d_handle, clut, layer);
}
}
static void dma2d_rgb565_copy_mono4_first_col(dma2d_params_t* dp,
const gdc_color16_t* gradient) {
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
uint8_t* srca_ptr = (uint8_t*)dp->srca_row + dp->srca_x / 2;
int height = dp->height;
while (height-- > 0) {
uint8_t fg_lum = srca_ptr[0] >> 4;
dst_ptr[0] = gradient[fg_lum];
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
srca_ptr += dp->srca_stride / sizeof(*srca_ptr);
}
}
static void dma2d_rgb565_copy_mono4_last_col(dma2d_params_t* dp,
const gdc_color16_t* gradient) {
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + (dp->dst_x + dp->width - 1);
uint8_t* srca_ptr = (uint8_t*)dp->srca_row + (dp->srca_x + dp->width - 1) / 2;
uint16_t* srcb_ptr = (uint16_t*)dp->srcb_row + (dp->srcb_x + dp->width - 1);
int height = dp->height;
while (height-- > 0) {
uint8_t fg_lum = srca_ptr[0] & 0x0F;
dst_ptr[0] = gradient[fg_lum];
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
srca_ptr += dp->srca_stride / sizeof(*srca_ptr);
srcb_ptr += dp->srcb_stride / sizeof(*srcb_ptr);
}
}
bool dma2d_rgb565_copy_mono4(gdc_t* gdc, dma2d_params_t* dp) {
const gdc_color16_t* srca_gradient = NULL;
dma2d_wait(gdc);
if (dp->srca_x & 1) {
// First column of mono4 bitmap is odd
// Use the CPU to draw the first column
srca_gradient = gdc_color16_gradient_a4(dp->srca_fg, dp->srca_bg);
dma2d_rgb565_copy_mono4_first_col(dp, srca_gradient);
dp->dst_x += 1;
dp->srca_x += 1;
dp->width -= 1;
}
if (dp->width > 0 && dp->width & 1) {
// The width is odd
// Use the CPU to draw the last column
if (srca_gradient == NULL) {
srca_gradient = gdc_color16_gradient_a4(dp->srca_fg, dp->srca_bg);
}
dma2d_rgb565_copy_mono4_last_col(dp, srca_gradient);
dp->width -= 1;
}
dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
dma2d_handle.Init.Mode = DMA2D_M2M_PFC;
dma2d_handle.Init.OutputOffset =
dp->dst_stride / sizeof(uint16_t) - dp->width;
HAL_DMA2D_Init(&dma2d_handle);
dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
dma2d_handle.LayerCfg[1].InputOffset = dp->srca_stride * 2 - dp->width;
dma2d_handle.LayerCfg[1].AlphaMode = 0;
dma2d_handle.LayerCfg[1].InputAlpha = 0;
HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
dma2d_config_clut(1, dp->srca_fg, dp->srca_bg);
HAL_DMA2D_Start(&dma2d_handle, (uint32_t)dp->srca_row + dp->srca_x / 2,
(uint32_t)dp->dst_row + dp->dst_x * sizeof(uint16_t),
dp->width, dp->height);
return true;
}
bool dma2d_rgb565_copy_rgb565(gdc_t* gdc, dma2d_params_t* dp) {
dma2d_wait(gdc);
dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
dma2d_handle.Init.Mode = DMA2D_M2M_PFC;
dma2d_handle.Init.OutputOffset =
dp->dst_stride / sizeof(uint16_t) - dp->width;
HAL_DMA2D_Init(&dma2d_handle);
dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
dma2d_handle.LayerCfg[1].InputOffset =
dp->srca_stride / sizeof(uint16_t) - dp->width;
dma2d_handle.LayerCfg[1].AlphaMode = 0;
dma2d_handle.LayerCfg[1].InputAlpha = 0;
HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
HAL_DMA2D_Start(&dma2d_handle,
(uint32_t)dp->srca_row + dp->srca_x * sizeof(uint16_t),
(uint32_t)dp->dst_row + dp->dst_x * sizeof(uint16_t),
dp->width, dp->height);
return true;
}
bool dma2d_rgb565_blend_mono4_mono4(gdc_t* gdc, dma2d_params_t* params) {
dma2d_wait(gdc);
return true;
}
static void dma2d_rgb565_blend_mono4_rgb565_first_col(dma2d_params_t* dp) {
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
uint8_t* srca_ptr = (uint8_t*)dp->srca_row + dp->srca_x / 2;
uint16_t* srcb_ptr = (uint16_t*)dp->srcb_row + dp->srcb_x;
int height = dp->height;
while (height-- > 0) {
uint8_t fg_alpha = srca_ptr[0] >> 4;
dst_ptr[0] = gdc_color16_blend_a4(
dp->srca_fg, gdc_color16_to_color(srcb_ptr[0]), fg_alpha);
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
srca_ptr += dp->srca_stride / sizeof(*srca_ptr);
srcb_ptr += dp->srcb_stride / sizeof(*srcb_ptr);
}
}
static void dma2d_rgb565_blend_mono4_rgb565_last_col(dma2d_params_t* dp) {
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + (dp->dst_x + dp->width - 1);
uint8_t* srca_ptr = (uint8_t*)dp->srca_row + (dp->srca_x + dp->width - 1) / 2;
uint16_t* srcb_ptr = (uint16_t*)dp->srcb_row + (dp->srcb_x + dp->width - 1);
int height = dp->height;
while (height-- > 0) {
uint8_t fg_alpha = srca_ptr[0] & 0x0F;
dst_ptr[0] = gdc_color16_blend_a4(
dp->srca_fg, gdc_color16_to_color(srcb_ptr[0]), fg_alpha);
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
srca_ptr += dp->srca_stride / sizeof(*srca_ptr);
srcb_ptr += dp->srcb_stride / sizeof(*srcb_ptr);
}
}
bool dma2d_rgb565_blend_mono4_rgb565(gdc_t* gdc, dma2d_params_t* dp) {
dma2d_wait(gdc);
if (dp->srca_x & 1) {
// First column of mono4 bitmap is odd
// Use the CPU to draw the first column
dma2d_rgb565_blend_mono4_rgb565_first_col(dp);
dp->dst_x += 1;
dp->srca_x += 1;
dp->srcb_x += 1;
dp->width -= 1;
}
if (dp->width > 0 && dp->width & 1) {
// The width is odd
// Use the CPU to draw the last column
dma2d_rgb565_blend_mono4_rgb565_last_col(dp);
dp->width -= 1;
}
if (dp->width > 0) {
dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
dma2d_handle.Init.Mode = DMA2D_M2M_BLEND;
dma2d_handle.Init.OutputOffset =
dp->dst_stride / sizeof(uint16_t) - dp->width;
HAL_DMA2D_Init(&dma2d_handle);
dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4;
dma2d_handle.LayerCfg[1].InputOffset = dp->srca_stride * 2 - dp->width;
dma2d_handle.LayerCfg[1].AlphaMode = 0;
dma2d_handle.LayerCfg[1].InputAlpha = gdc_color_to_color32(dp->srca_fg);
HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
dma2d_handle.LayerCfg[0].InputOffset =
dp->srcb_stride / sizeof(uint16_t) - dp->width;
dma2d_handle.LayerCfg[0].AlphaMode = 0;
dma2d_handle.LayerCfg[0].InputAlpha = 0;
HAL_DMA2D_ConfigLayer(&dma2d_handle, 0);
HAL_DMA2D_BlendingStart(
&dma2d_handle, (uint32_t)dp->srca_row + dp->srca_x / 2,
(uint32_t)dp->srcb_row + dp->srcb_x * sizeof(uint16_t),
(uint32_t)dp->dst_row + dp->dst_x * sizeof(uint16_t), dp->width,
dp->height);
}
return true;
}

@ -7,6 +7,10 @@
#include "common.h"
#include "display.h" // TODO:just for testing, remove later !!!
#include "rust_ui.h" // TODO:just for testing, remove later !!!
#include "touch.h" // TODO:just for testing, remove later !!!
MP_NOINLINE int main_(int argc, char **argv);
int main(int argc, char **argv) {
@ -16,6 +20,15 @@ int main(int argc, char **argv) {
ensure(sectrue * (zkp_context_init() == 0), NULL);
#endif
display_refresh();
display_backlight(255);
while (1) { /// TODO:just for testing, remove later !!!
new_drawing_poc();
display_refresh();
}
#if MICROPY_PY_THREAD
mp_thread_init();
#endif

@ -87,6 +87,7 @@ def configure(
if "dma2d" in features_wanted:
defines += ["USE_DMA2D"]
sources += ["embed/trezorhal/stm32f4/dma2d.c"]
sources += ["embed/trezorhal/stm32f4/dma2d_gdc.c"]
sources += [
"vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c"
]

@ -0,0 +1 @@
Subproject commit 7942fc4ea05026e4e9ce72d680f704e9433bce42
Loading…
Cancel
Save