parent
7ce1242b37
commit
5bec2abb59
@ -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
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 23 KiB |
@ -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 }))
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -0,0 +1 @@
|
||||
Subproject commit 7942fc4ea05026e4e9ce72d680f704e9433bce42
|
Loading…
Reference in new issue