parent
c97d9c0fe0
commit
5a35a60856
@ -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 "gl_color.h"
|
||||
#include "colors.h"
|
||||
|
||||
const gl_color16_t* gl_color16_gradient_a4(gl_color_t fg_color,
|
||||
gl_color_t bg_color) {
|
||||
static gl_color16_t cache[16] = {0};
|
||||
|
||||
if (gl_color_to_color16(bg_color) != cache[0] ||
|
||||
gl_color_to_color16(fg_color) != cache[15]) {
|
||||
for (int alpha = 0; alpha < 16; alpha++) {
|
||||
cache[alpha] = gl_color16_blend_a4(fg_color, bg_color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
return (const gl_color16_t*)&cache[0];
|
||||
}
|
||||
|
||||
const gl_color32_t* gl_color32_gradient_a4(gl_color_t fg_color,
|
||||
gl_color_t bg_color) {
|
||||
static gl_color32_t cache[16] = {0};
|
||||
|
||||
if (bg_color != gl_color32_to_color(cache[0]) ||
|
||||
fg_color != gl_color32_to_color(cache[15])) {
|
||||
for (int alpha = 0; alpha < 16; alpha++) {
|
||||
cache[alpha] = gl_color32_blend_a4(fg_color, bg_color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
return (const gl_color32_t*)&cache[0];
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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 GL_COLOR_H
|
||||
#define GL_COLOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define GL_COLOR_16BIT
|
||||
// #define GL_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 gl_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 gl_color32_t;
|
||||
|
||||
#ifdef GL_COLOR_16BIT
|
||||
#define gl_color_t gl_color16_t
|
||||
#define gl_color_to_color16(c) (c)
|
||||
#define gl_color16_to_color(c) (c)
|
||||
#define gl_color_to_color32(c) (gl_color16_to_color32(c))
|
||||
#define gl_color32_to_color(c) (gl_color32_to_color16(c))
|
||||
#define gl_color_lum(c) (gl_color16_lum(c))
|
||||
#elif GL_COLOR_32BIT
|
||||
#define gl_color_t gl_color32_t
|
||||
#define gl_color_to_color16(c) (gl_color32_to_color16(c))
|
||||
#define gl_color16_to_color(c) (gl_color16_to_color32(c))
|
||||
#define gl_color_to_color32(c) (c)
|
||||
#define gl_color32_to_color(c) (c)
|
||||
#else
|
||||
#error "GL_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 gl_color16_t gl_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 gl_color32_t gl_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 gl_color32_t gl_color16_to_color32(gl_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 gl_color16_t gl_color32_to_color16(gl_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;
|
||||
}
|
||||
|
||||
// Converts 16-bit color into luminance (ranging from 0 to 255)
|
||||
static inline uint8_t gl_color_lum(gl_color16_t color) {
|
||||
uint16_t r = (color & 0x00F80000) >> 8;
|
||||
uint16_t g = (color & 0x0000FC00) >> 5;
|
||||
uint16_t b = (color & 0x000000F8) >> 3;
|
||||
|
||||
return (r + g + b) / 3;
|
||||
}
|
||||
|
||||
#ifdef GL_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 gl_color16_t gl_color16_blend_a4(gl_color16_t fg, gl_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 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 gl_color16_t gl_color16_blend_a8(gl_color16_t fg, gl_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 * (255 - alpha))) / 255;
|
||||
|
||||
uint16_t fg_g = (fg & 0x07E0) >> 5;
|
||||
uint16_t bg_g = (bg & 0x07E0) >> 5;
|
||||
uint16_t g = (fg_g * alpha + (bg_g * (255 - alpha))) / 255;
|
||||
|
||||
uint16_t fg_b = (fg & 0x001F) >> 0;
|
||||
uint16_t bg_b = (bg & 0x001F) >> 0;
|
||||
uint16_t b = (fg_b * alpha + (bg_b * (255 - alpha))) / 255;
|
||||
|
||||
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 gl_color32_t gl_color32_blend_a4(gl_color16_t fg, gl_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 GL_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 gl_color16_t gl_color16_blend_a4(gl_color32_t fg, gl_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 gl_color16_rgb(r, g, b)
|
||||
}
|
||||
|
||||
// Blends foreground and background colors with 8-bit alpha
|
||||
//
|
||||
// Returns a color in 16-bit format
|
||||
//
|
||||
// If alpha is 0, the function returns the background color
|
||||
// If alpha is 255, the function returns the foreground color
|
||||
static inline gl_color16_t gl_color16_blend_a8(gl_color32_t fg, gl_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 * (255 - alpha))) / 255;
|
||||
|
||||
uint16_t fg_g = (fg & 0x0000FF00) >> 8;
|
||||
uint16_t bg_g = (bg & 0x0000FF00) >> 8;
|
||||
uint16_t g = (fg_g * alpha + (bg_g * (255 - alpha))) / 255;
|
||||
|
||||
uint16_t fg_b = (fg & 0x000000FF) >> 0;
|
||||
uint16_t bg_b = (bg & 0x000000FF) >> 0;
|
||||
uint16_t b = (fg_b * alpha + (bg_b * (255 - alpha))) / 255;
|
||||
|
||||
return gl_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 gl_color32_t gl_color32_blend_a4(gl_color32_t fg, gl_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 gl_color32_rgb(r, g, b);
|
||||
}
|
||||
|
||||
#else
|
||||
#error "GL_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 gl_color16_t* gl_color16_gradient_a4(gl_color_t fg, gl_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 gl_color32_t* gl_color32_gradient_a4(gl_color_t fg, gl_color_t bg);
|
||||
|
||||
#endif // TREZORHAL_GL_COLOR_H
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 GL_DMA2D_H
|
||||
#define GL_DMA2D_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "gl_color.h"
|
||||
|
||||
typedef struct {
|
||||
// Destination bitma[
|
||||
// Following fields are used for all operations
|
||||
uint16_t height;
|
||||
uint16_t width;
|
||||
void* dst_row;
|
||||
uint16_t dst_x;
|
||||
uint16_t dst_y;
|
||||
uint16_t dst_stride;
|
||||
|
||||
// Source bitmap
|
||||
// Used for copying and blending, but src_fg & src_alpha
|
||||
// fields are also used for fill operation
|
||||
void* src_row;
|
||||
uint16_t src_x;
|
||||
uint16_t src_y;
|
||||
uint16_t src_stride;
|
||||
gl_color_t src_fg;
|
||||
gl_color_t src_bg;
|
||||
uint8_t src_alpha;
|
||||
|
||||
} dma2d_params_t;
|
||||
|
||||
bool rgb565_fill(const dma2d_params_t* dp);
|
||||
bool rgb565_copy_mono4(const dma2d_params_t* dp);
|
||||
bool rgb565_copy_rgb565(const dma2d_params_t* dp);
|
||||
bool rgb565_blend_mono4(const dma2d_params_t* dp);
|
||||
|
||||
bool rgba8888_fill(const dma2d_params_t* dp);
|
||||
bool rgba8888_copy_mono4(const dma2d_params_t* dp);
|
||||
bool rgba8888_copy_rgb565(const dma2d_params_t* dp);
|
||||
bool rgba8888_copy_rgba8888(const dma2d_params_t* dp);
|
||||
bool rgba8888_blend_mono4(const dma2d_params_t* dp);
|
||||
|
||||
bool mono8_fill(const dma2d_params_t* dp);
|
||||
bool mono8_copy_mono1p(const dma2d_params_t* dp);
|
||||
bool mono8_copy_mono4(const dma2d_params_t* dp);
|
||||
bool mono8_blend_mono1p(const dma2d_params_t* dp);
|
||||
bool mono8_blend_mono4(const dma2d_params_t* dp);
|
||||
|
||||
#endif // GL_DMA2D_H
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 "gl_dma2d.h"
|
||||
|
||||
bool mono8_fill(const dma2d_params_t* dp) {
|
||||
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
uint8_t fg = gl_color_lum(dp->src_fg);
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
dst_ptr[x] = fg;
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mono8_copy_mono1p(const dma2d_params_t* dp) {
|
||||
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
|
||||
uint8_t* src = (uint8_t*)dp->src_row;
|
||||
uint16_t src_ofs = dp->src_stride * dp->src_y + dp->src_x;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
uint8_t fg = gl_color_lum(dp->src_fg);
|
||||
uint8_t bg = gl_color_lum(dp->src_bg);
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
|
||||
uint8_t data = src[(src_ofs + x) / 8];
|
||||
dst_ptr[x] = (data & mask) ? fg : bg;
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
src_ofs += dp->src_stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mono8_copy_mono4(const dma2d_params_t* dp) {
|
||||
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
|
||||
uint8_t* src_row = (uint8_t*)dp->src_row;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
uint8_t fg = gl_color_lum(dp->src_fg);
|
||||
uint8_t bg = gl_color_lum(dp->src_bg);
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
uint8_t src_data = src_row[(x + dp->src_x) / 2];
|
||||
uint8_t src_lum = (x + dp->src_x) & 1 ? src_data >> 4 : src_data & 0xF;
|
||||
dst_ptr[x] = (fg * src_lum + bg * (15 - src_lum)) / 15;
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
src_row += dp->src_stride / sizeof(*src_row);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mono8_blend_mono1p(const dma2d_params_t* dp) {
|
||||
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
|
||||
uint8_t* src = (uint8_t*)dp->src_row;
|
||||
uint16_t src_ofs = dp->src_stride * dp->src_y + dp->src_x;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
uint8_t fg = gl_color_lum(dp->src_fg);
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
|
||||
uint8_t data = src[(src_ofs + x) / 8];
|
||||
dst_ptr[x] = (data & mask) ? fg : dst_ptr[x];
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
src_ofs += dp->src_stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mono8_blend_mono4(const dma2d_params_t* dp) {
|
||||
uint8_t* dst_ptr = (uint8_t*)dp->dst_row + dp->dst_x;
|
||||
uint8_t* src_row = (uint8_t*)dp->src_row;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
uint8_t fg = gl_color_lum(dp->src_fg);
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
uint8_t src_data = src_row[(x + dp->src_x) / 2];
|
||||
uint8_t src_alpha = (x + dp->src_x) & 1 ? src_data >> 4 : src_data & 0x0F;
|
||||
dst_ptr[x] = (fg * src_alpha + dst_ptr[x] * (15 - src_alpha)) / 15;
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
src_row += dp->src_stride / sizeof(*src_row);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 "gl_dma2d.h"
|
||||
|
||||
#if USE_DMA2D
|
||||
#include "dma2d.h"
|
||||
#endif
|
||||
|
||||
bool rgb565_fill(const dma2d_params_t* dp) {
|
||||
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
|
||||
if (dma2d_accessible(dp->dst_row)) {
|
||||
return dma2d_rgb565_fill(dp);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
if (dp->src_alpha == 255) {
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
dst_ptr[x] = dp->src_fg;
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
}
|
||||
} else {
|
||||
uint8_t alpha = dp->src_alpha;
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
dst_ptr[x] = gl_color16_blend_a8(dp->src_fg, dst_ptr[x], alpha);
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool rgb565_copy_mono4(const dma2d_params_t* dp) {
|
||||
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
|
||||
if (dma2d_accessible(dp->dst_row) && dma2d_accessible(dp->src_row)) {
|
||||
return dma2d_rgb565_copy_mono4(dp);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
const gl_color16_t* gradient =
|
||||
gl_color16_gradient_a4(dp->src_fg, dp->src_bg);
|
||||
|
||||
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
|
||||
uint8_t* src_row = (uint8_t*)dp->src_row;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
uint8_t fg_data = src_row[(x + dp->src_x) / 2];
|
||||
uint8_t fg_lum = (x + dp->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
|
||||
dst_ptr[x] = gradient[fg_lum];
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
src_row += dp->src_stride / sizeof(*src_row);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool rgb565_copy_rgb565(const dma2d_params_t* dp) {
|
||||
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
|
||||
if (dma2d_accessible(dp->dst_row) && dma2d_accessible(dp->src_row)) {
|
||||
return dma2d_rgb565_copy_rgb565(dp);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
|
||||
uint16_t* src_ptr = (uint16_t*)dp->src_row + dp->src_x;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
dst_ptr[x] = src_ptr[x];
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
src_ptr += dp->src_stride / sizeof(*src_ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool rgb565_blend_mono4(const dma2d_params_t* dp) {
|
||||
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
|
||||
if (dma2d_accessible(dp->dst_row) && dma2d_accessible(dp->src_row)) {
|
||||
return dma2d_rgb565_blend_mono4(dp);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
uint16_t* dst_ptr = (uint16_t*)dp->dst_row + dp->dst_x;
|
||||
uint8_t* src_row = (uint8_t*)dp->src_row;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
uint8_t fg_data = src_row[(x + dp->src_x) / 2];
|
||||
uint8_t fg_alpha = (x + dp->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
|
||||
dst_ptr[x] = gl_color16_blend_a4(
|
||||
dp->src_fg, gl_color16_to_color(dst_ptr[x]), fg_alpha);
|
||||
}
|
||||
dst_ptr += dp->dst_stride / sizeof(*dst_ptr);
|
||||
src_row += dp->src_stride / sizeof(*src_row);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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 <display.h>
|
||||
|
||||
#include "display_draw.h"
|
||||
#include "fonts/fonts.h"
|
||||
#include "gl_draw.h"
|
||||
|
||||
typedef struct {
|
||||
int16_t dst_x;
|
||||
int16_t dst_y;
|
||||
int16_t src_x;
|
||||
int16_t src_y;
|
||||
int16_t width;
|
||||
int16_t height;
|
||||
} gl_clip_t;
|
||||
|
||||
static inline gl_clip_t gl_clip(gl_rect_t dst, const gl_bitmap_t* bitmap) {
|
||||
int16_t dst_x = dst.x0;
|
||||
int16_t dst_y = dst.y0;
|
||||
|
||||
int16_t src_x = 0;
|
||||
int16_t src_y = 0;
|
||||
|
||||
if (bitmap != NULL) {
|
||||
src_x += bitmap->offset.x;
|
||||
src_y += bitmap->offset.y;
|
||||
|
||||
// Normalize negative x-offset of bitmap
|
||||
if (src_x < 0) {
|
||||
dst_x -= src_x;
|
||||
src_x = 0;
|
||||
}
|
||||
|
||||
// Normalize negative y-offset of src bitmap
|
||||
if (src_y < 0) {
|
||||
dst_y -= src_y;
|
||||
src_y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize negative top-left of destination rectangle
|
||||
if (dst_x < 0) {
|
||||
src_x -= dst_x;
|
||||
dst_x = 0;
|
||||
}
|
||||
|
||||
if (dst_y < 0) {
|
||||
src_y -= dst_y;
|
||||
dst_y = 0;
|
||||
}
|
||||
|
||||
// Calculate dimension of effective rectangle
|
||||
int16_t width = MIN(DISPLAY_RESX, dst.x1) - dst_x;
|
||||
int16_t height = MIN(DISPLAY_RESY, dst.y1) - dst_y;
|
||||
|
||||
if (bitmap != NULL) {
|
||||
width = MIN(width, bitmap->size.x - src_x);
|
||||
height = MIN(height, bitmap->size.y - src_y);
|
||||
}
|
||||
|
||||
gl_clip_t clip = {
|
||||
.dst_x = dst_x,
|
||||
.dst_y = dst_y,
|
||||
.src_x = src_x,
|
||||
.src_y = src_y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
||||
void gl_draw_bar(gl_rect_t rect, gl_color_t color) {
|
||||
gl_clip_t clip = gl_clip(rect, NULL);
|
||||
|
||||
if (clip.width <= 0 || clip.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dma2d_params_t dp = {
|
||||
// Destination bitmap
|
||||
.height = clip.height,
|
||||
.width = clip.width,
|
||||
.dst_row = NULL,
|
||||
.dst_x = clip.dst_x,
|
||||
.dst_y = clip.dst_y,
|
||||
.dst_stride = 0,
|
||||
|
||||
// Source bitmap
|
||||
.src_fg = color,
|
||||
.src_alpha = 255,
|
||||
};
|
||||
|
||||
display_fill(&dp);
|
||||
}
|
||||
|
||||
void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap) {
|
||||
gl_clip_t clip = gl_clip(rect, bitmap);
|
||||
|
||||
if (clip.width <= 0 || clip.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dma2d_params_t dp = {
|
||||
// Destination bitmap
|
||||
.height = clip.height,
|
||||
.width = clip.width,
|
||||
.dst_row = NULL,
|
||||
.dst_x = clip.dst_x,
|
||||
.dst_y = clip.dst_y,
|
||||
.dst_stride = 0,
|
||||
|
||||
// Source bitmap
|
||||
.src_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.src_y,
|
||||
.src_x = clip.src_x,
|
||||
.src_y = clip.src_y,
|
||||
.src_stride = bitmap->stride,
|
||||
.src_fg = bitmap->fg_color,
|
||||
.src_bg = bitmap->bg_color,
|
||||
.src_alpha = 255,
|
||||
};
|
||||
|
||||
#if TREZOR_FONT_BPP == 1
|
||||
if (bitmap->format == GL_FORMAT_MONO1P) {
|
||||
display_copy_mono1p(&dp);
|
||||
}
|
||||
#endif
|
||||
#if TREZOR_FONT_BPP == 4
|
||||
if (bitmap->format == GL_FORMAT_MONO4) {
|
||||
display_copy_mono4(&dp);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if TREZOR_FONT_BPP == 1
|
||||
#define GLYPH_FORMAT GL_FORMAT_MONO1P
|
||||
#define GLYPH_STRIDE(w) (((w) + 7) / 8)
|
||||
#elif TREZOR_FONT_BPP == 2
|
||||
#error Unsupported TREZOR_FONT_BPP value
|
||||
#define GLYPH_FORMAT GL_FORMAT_MONO2
|
||||
#define GLYPH_STRIDE(w) (((w) + 3) / 4)
|
||||
#elif TREZOR_FONT_BPP == 4
|
||||
#define GLYPH_FORMAT GL_FORMAT_MONO4
|
||||
#define GLYPH_STRIDE(w) (((w) + 1) / 2)
|
||||
#elif TREZOR_FONT_BPP == 8
|
||||
#error Unsupported TREZOR_FONT_BPP value
|
||||
#define GLYPH_FORMAT GL_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])
|
||||
|
||||
void gl_draw_text(gl_rect_t rect, const char* text, size_t maxlen,
|
||||
const gl_text_attr_t* attr) {
|
||||
if (text == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
gl_bitmap_t bitmap = {
|
||||
.format = GLYPH_FORMAT,
|
||||
.fg_color = attr->fg_color,
|
||||
.bg_color = attr->bg_color,
|
||||
};
|
||||
|
||||
int max_height = font_max_height(attr->font);
|
||||
int baseline = font_baseline(attr->font);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bitmap.ptr = GLYPH_DATA(glyph);
|
||||
bitmap.stride = GLYPH_STRIDE(GLYPH_WIDTH(glyph));
|
||||
bitmap.size.x = GLYPH_WIDTH(glyph);
|
||||
bitmap.size.y = GLYPH_HEIGHT(glyph);
|
||||
|
||||
bitmap.offset.x = -GLYPH_BEARING_X(glyph);
|
||||
bitmap.offset.y = -(max_height - baseline - GLYPH_BEARING_Y(glyph));
|
||||
|
||||
gl_draw_bitmap(rect, &bitmap);
|
||||
|
||||
rect.x0 += GLYPH_ADVANCE(glyph);
|
||||
}
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// emulation of legacy functions
|
||||
|
||||
void display_bar(int x, int y, int w, int h, uint16_t c) {}
|
||||
|
||||
void display_text(int x, int y, const char* text, int textlen, int font,
|
||||
uint16_t fgcolor, uint16_t bgcolor) {}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 GL_DRAW_H
|
||||
#define GL_DRAW_H
|
||||
|
||||
#include "gl_color.h"
|
||||
|
||||
// 2D rectangle coordinates (x1, y1 point is not included)
|
||||
typedef struct {
|
||||
int16_t x0;
|
||||
int16_t y0;
|
||||
int16_t x1;
|
||||
int16_t y1;
|
||||
} gl_rect_t;
|
||||
|
||||
// Creates a rectangle from top-left coordinates and dimensions
|
||||
static inline gl_rect_t gl_rect_wh(int16_t x, int16_t y, int16_t w, int16_t h) {
|
||||
gl_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 gl_rect_t gl_rect(int16_t x0, int16_t y0, int16_t x1,
|
||||
int16_t y1) {
|
||||
gl_rect_t rect = {
|
||||
.x0 = x0,
|
||||
.y0 = y0,
|
||||
.x1 = x1,
|
||||
.y1 = y1,
|
||||
};
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
// 2D offset/ coordinates
|
||||
typedef struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
} gl_offset_t;
|
||||
|
||||
static inline gl_offset_t gl_offset(int16_t x, int16_t y) {
|
||||
gl_offset_t size = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// 2D size in pixels
|
||||
typedef struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
} gl_size_t;
|
||||
|
||||
static inline gl_size_t gl_size(int16_t x, int16_t y) {
|
||||
gl_size_t size = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// Bitmap pixel format
|
||||
typedef enum {
|
||||
GL_FORMAT_UNKNOWN, //
|
||||
GL_FORMAT_MONO1P, // 1-bpp per pixel (packed)
|
||||
GL_FORMAT_MONO4, // 4-bpp per pixel
|
||||
GL_FORMAT_RGB565, // 16-bpp per pixel
|
||||
GL_FORMAT_RGBA8888, // 32-bpp
|
||||
} gl_format_t;
|
||||
|
||||
// 2D bitmap references
|
||||
typedef struct {
|
||||
// pointer to top-left pixel
|
||||
void* ptr;
|
||||
// stride in bytes
|
||||
size_t stride;
|
||||
// size in pixels
|
||||
gl_size_t size;
|
||||
// format of pixels, GL_FORMAT_xxx
|
||||
uint8_t format;
|
||||
// offset used when bitmap is drawed using gl_draw_bitmap()
|
||||
gl_offset_t offset;
|
||||
// foreground color (used with MONOx formats)
|
||||
gl_color_t fg_color;
|
||||
// background color (used with MONOx formats)
|
||||
gl_color_t bg_color;
|
||||
} gl_bitmap_t;
|
||||
|
||||
// Text attributes
|
||||
typedef struct {
|
||||
int font;
|
||||
gl_color_t fg_color;
|
||||
gl_color_t bg_color;
|
||||
} gl_text_attr_t;
|
||||
|
||||
// Fills a rectangle with a specified color
|
||||
void gl_draw_bar(gl_rect_t rect, gl_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.
|
||||
void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap);
|
||||
|
||||
// !@# TODO
|
||||
void gl_draw_text(gl_rect_t rect, const char* text, size_t maxlen,
|
||||
const gl_text_attr_t* attr);
|
||||
|
||||
#endif // GL_DRAW_H
|
@ -0,0 +1,216 @@
|
||||
use super::ffi;
|
||||
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::Rect,
|
||||
shape::{Bitmap, BitmapFormat, BitmapView},
|
||||
};
|
||||
|
||||
pub type Dma2d = ffi::dma2d_params_t;
|
||||
|
||||
impl Default for Dma2d {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
dst_row: core::ptr::null_mut(),
|
||||
dst_stride: 0,
|
||||
dst_x: 0,
|
||||
dst_y: 0,
|
||||
src_row: core::ptr::null_mut(),
|
||||
src_bg: 0,
|
||||
src_fg: 0,
|
||||
src_stride: 0,
|
||||
src_x: 0,
|
||||
src_y: 0,
|
||||
src_alpha: 255,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dma2d {
|
||||
pub fn new_fill(r: Rect, clip: Rect, color: Color, alpha: u8) -> Option<Self> {
|
||||
let r = r.clamp(clip);
|
||||
if !r.is_empty() {
|
||||
Some(
|
||||
Self::default()
|
||||
.with_rect(r)
|
||||
.with_fg(color)
|
||||
.with_alpha(alpha),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_copy(r: Rect, clip: Rect, src: &BitmapView) -> Option<Self> {
|
||||
let mut offset = src.offset;
|
||||
let mut r_dst = r;
|
||||
|
||||
// Normalize negative x & y-offset of the bitmap
|
||||
if offset.x < 0 {
|
||||
r_dst.x0 -= offset.x;
|
||||
offset.x = 0;
|
||||
}
|
||||
|
||||
if offset.y < 0 {
|
||||
r_dst.y0 -= offset.y;
|
||||
offset.y = 0;
|
||||
}
|
||||
|
||||
// Clip with the canvas viewport
|
||||
let mut r = r_dst.clamp(clip);
|
||||
|
||||
// Clip with the bitmap top-left
|
||||
if r.x0 > r_dst.x0 {
|
||||
offset.x += r.x0 - r_dst.x0;
|
||||
}
|
||||
|
||||
if r.y0 > r_dst.y0 {
|
||||
offset.y += r.y0 - r_dst.y0;
|
||||
}
|
||||
|
||||
// Clip with the bitmap size
|
||||
r.x1 = core::cmp::min(r.x0 + src.size().x - offset.x, r.x1);
|
||||
r.y1 = core::cmp::min(r.y0 + src.size().y - offset.y, r.y1);
|
||||
|
||||
if !r.is_empty() {
|
||||
Some(
|
||||
Dma2d::default()
|
||||
.with_rect(r)
|
||||
.with_src(src.bitmap, offset.x, offset.y)
|
||||
.with_bg(src.bg_color)
|
||||
.with_fg(src.fg_color),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_dst(self, dst: &mut Bitmap) -> Self {
|
||||
Self {
|
||||
dst_row: unsafe { dst.row_ptr(self.dst_y) },
|
||||
dst_stride: dst.stride() as u16,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn with_rect(self, r: Rect) -> Self {
|
||||
Self {
|
||||
width: r.width() as u16,
|
||||
height: r.height() as u16,
|
||||
dst_x: r.x0 as u16,
|
||||
dst_y: r.y0 as u16,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn with_src(self, bitmap: &Bitmap, x: i16, y: i16) -> Self {
|
||||
let bitmap_stride = match bitmap.format() {
|
||||
BitmapFormat::MONO1P => bitmap.width() as u16, // packed bits
|
||||
_ => bitmap.stride() as u16,
|
||||
};
|
||||
|
||||
Self {
|
||||
src_row: unsafe { bitmap.row_ptr(y as u16) },
|
||||
src_stride: bitmap_stride,
|
||||
src_x: x as u16,
|
||||
src_y: y as u16,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn with_fg(self, fg_color: Color) -> Self {
|
||||
Self {
|
||||
src_fg: fg_color.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn with_bg(self, bg_color: Color) -> Self {
|
||||
Self {
|
||||
src_bg: bg_color.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn with_alpha(self, alpha: u8) -> Self {
|
||||
Self {
|
||||
src_alpha: alpha,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_for_transfer() {
|
||||
#[cfg(feature = "dma2d")]
|
||||
unsafe {
|
||||
ffi::dma2d_wait_for_transfer()
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn rgb565_fill(&self) {
|
||||
unsafe { ffi::rgb565_fill(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgb565_copy_mono4(&self) {
|
||||
unsafe { ffi::rgb565_copy_mono4(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgb565_copy_rgb565(&self) {
|
||||
unsafe { ffi::rgb565_copy_rgb565(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgb565_blend_mono4(&self) {
|
||||
unsafe { ffi::rgb565_blend_mono4(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgba8888_fill(&self) {
|
||||
unsafe { ffi::rgba8888_fill(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgba8888_copy_mono4(&self) {
|
||||
unsafe { ffi::rgba8888_copy_mono4(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgba8888_copy_rgb565(&self) {
|
||||
unsafe { ffi::rgba8888_copy_rgb565(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgba8888_copy_rgba8888(&self) {
|
||||
unsafe { ffi::rgba8888_copy_rgba8888(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn rgba8888_blend_mono4(&self) {
|
||||
unsafe { ffi::rgba8888_blend_mono4(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn mono8_fill(&self) {
|
||||
unsafe { ffi::mono8_fill(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn mono8_copy_mono1p(&self) {
|
||||
unsafe { ffi::mono8_copy_mono1p(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn mono8_copy_mono4(&self) {
|
||||
unsafe { ffi::mono8_copy_mono4(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn mono8_blend_mono1p(&self) {
|
||||
unsafe { ffi::mono8_blend_mono1p(self) };
|
||||
}
|
||||
|
||||
pub unsafe fn mono8_blend_mono4(&self) {
|
||||
unsafe { ffi::mono8_blend_mono4(self) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "new_rendering")]
|
||||
pub unsafe fn display_fill(&self) {
|
||||
unsafe { ffi::display_fill(self) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "new_rendering")]
|
||||
pub unsafe fn display_copy_rgb565(&self) {
|
||||
unsafe { ffi::display_copy_rgb565(self) };
|
||||
}
|
||||
}
|
@ -0,0 +1,357 @@
|
||||
use crate::ui::geometry::Offset;
|
||||
/// 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 five public functions - `new`, `push`,
|
||||
/// `push_read`, `pop` and `pop_ready`.
|
||||
///
|
||||
/// The `new()` function creates a blur filter context.
|
||||
/// - The `size` argument specifies the size of the blurred area.
|
||||
/// - The `radius` argument specifies the length of the kernel side.
|
||||
///
|
||||
/// ```rust
|
||||
/// let blur = BlurFilter::new(size, radius);
|
||||
/// ```
|
||||
///
|
||||
/// The `push_ready()` function returns the row from the source bitmap
|
||||
/// needed to be pushed
|
||||
///
|
||||
/// The `push()` function pushes source row data into the sliding window and
|
||||
/// performs all necessary calculations.
|
||||
///
|
||||
/// ```rust
|
||||
/// if let Some(y) = blur.push_ready() {
|
||||
/// blur.push(&src_bitmap.row(y)[x0..x1]);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `pop_ready()` function returns the row from the destination bitmap
|
||||
/// that can be popped out
|
||||
///
|
||||
/// The `pop()` function pops the blurred row from the sliding window.
|
||||
///
|
||||
/// ```rust
|
||||
/// if let Some(y) = blur.pop_ready() {
|
||||
/// blur.pop(&mut dst_bitmap.row(y)[x0..x1]);
|
||||
/// }
|
||||
/// ```
|
||||
use core::mem::size_of;
|
||||
|
||||
const MAX_RADIUS: usize = 4;
|
||||
const MAX_SIDE: usize = 1 + MAX_RADIUS * 2;
|
||||
const MAX_WIDTH: usize = 240;
|
||||
|
||||
pub type BlurBuff = [u8; MAX_WIDTH * (MAX_SIDE * 3 + size_of::<u16>() * 3) + 8];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlurAlgorithm<'a> {
|
||||
size: Offset,
|
||||
radius: usize,
|
||||
row: usize,
|
||||
totals: &'a mut [Rgb<u16>],
|
||||
window: &'a mut [Rgb<u8>],
|
||||
row_count: usize,
|
||||
}
|
||||
|
||||
impl<'a> BlurAlgorithm<'a> {
|
||||
/// Constraints:
|
||||
/// width <= MAX_WIDTH
|
||||
/// radius <= MAX_RADIUS
|
||||
/// width >= radius
|
||||
pub fn new(size: Offset, radius: usize, memory: &'a mut BlurBuff) -> Result<Self, ()> {
|
||||
assert!(size.x as usize <= MAX_WIDTH);
|
||||
assert!(radius <= MAX_RADIUS);
|
||||
assert!(size.x as usize > 2 * radius - 1);
|
||||
|
||||
// Split buffer into two parts
|
||||
let window_size = size.x as usize * (1 + radius * 2);
|
||||
let (window_buff, total_buff) =
|
||||
memory.split_at_mut(window_size * core::mem::size_of::<Rgb<u8>>());
|
||||
|
||||
// Allocate `window` from the beginning of the buffer
|
||||
let (_, window_buff, _) = unsafe { window_buff.align_to_mut() };
|
||||
if window_buff.len() < window_size {
|
||||
return Err(());
|
||||
}
|
||||
let window = &mut window_buff[..window_size];
|
||||
window.iter_mut().for_each(|it| *it = Rgb::<u8>::default());
|
||||
|
||||
// Allocate `totals` from the rest of the buffer
|
||||
let (_, totals_buff, _) = unsafe { total_buff.align_to_mut() };
|
||||
if totals_buff.len() < size.x as usize {
|
||||
return Err(());
|
||||
}
|
||||
let totals = &mut totals_buff[..size.x as usize];
|
||||
totals.iter_mut().for_each(|it| *it = Rgb::<u16>::default());
|
||||
|
||||
Ok(Self {
|
||||
size,
|
||||
radius,
|
||||
row: 0,
|
||||
window,
|
||||
totals,
|
||||
row_count: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.size.x as usize * row;
|
||||
let row = &mut self.window[offset..offset + self.size.x as usize];
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy one row from the window to the another row.
|
||||
fn copy_row(&mut self, from_row: usize, to_row: usize) {
|
||||
let from_offset = self.size.x as usize * from_row;
|
||||
let to_offset = self.size.x as usize * to_row;
|
||||
for i in 0..self.size.x as usize {
|
||||
self.window[to_offset + i] = self.window[from_offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtracts the specified row of sliding window from `totals[]`.
|
||||
fn subtract_row(&mut self, row: usize) {
|
||||
let offset = self.size.x as usize * row;
|
||||
let row = &self.window[offset..offset + self.size.x as usize];
|
||||
|
||||
for (i, item) in row.iter().enumerate() {
|
||||
self.totals[i] -= *item;
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the specified row of sliding window to `totals[]`.
|
||||
fn add_row(&mut self, row: usize) {
|
||||
let offset = self.size.x as usize * row;
|
||||
let row = &self.window[offset..offset + self.size.x as usize];
|
||||
|
||||
for (i, item) in row.iter().enumerate() {
|
||||
self.totals[i] += *item;
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes the most recently pushed row again.
|
||||
fn push_last_row(&mut self) {
|
||||
let to_row = self.row;
|
||||
let from_row = if to_row > 0 {
|
||||
to_row - 1
|
||||
} else {
|
||||
self.box_side() - 1
|
||||
};
|
||||
|
||||
self.subtract_row(to_row);
|
||||
self.copy_row(from_row, to_row);
|
||||
self.add_row(to_row);
|
||||
|
||||
self.row = (to_row + 1) % self.box_side();
|
||||
self.row_count += 1;
|
||||
}
|
||||
|
||||
/// Returns the index of the row needed to be pushed into.
|
||||
pub fn push_ready(&self) -> Option<i16> {
|
||||
let y = core::cmp::max(0, self.row_count as i16 - self.radius as i16);
|
||||
if y < self.size.y {
|
||||
Some(y)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 = (row + 1) % self.box_side();
|
||||
self.row_count += 1;
|
||||
|
||||
while self.row_count <= self.radius {
|
||||
self.push_last_row();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index of row ready to be popped out.
|
||||
pub fn pop_ready(&self) -> Option<i16> {
|
||||
let y = self.row_count as i16 - self.box_side() as i16;
|
||||
if y < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(y)
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the current content of `totals[]` to the output buffer.
|
||||
pub fn pop(&mut self, output: &mut [PixelColor], dim: Option<u8>) {
|
||||
let divisor = match dim {
|
||||
Some(dim) => {
|
||||
if dim > 0 {
|
||||
(self.box_side() as u16 * 255) / dim as u16
|
||||
} else {
|
||||
65535u16
|
||||
}
|
||||
}
|
||||
None => self.box_side() as u16,
|
||||
};
|
||||
|
||||
let shift = 10;
|
||||
let multiplier = (1 << shift) as u32 / divisor as u32;
|
||||
|
||||
for (i, item) in output.iter_mut().enumerate() {
|
||||
*item = self.totals[i].mulshift(multiplier, shift).into();
|
||||
}
|
||||
|
||||
if self.push_ready().is_none() {
|
||||
self.push_last_row();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/// 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 p in circle_points(radius) {
|
||||
/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>..<see_below>
|
||||
/// println!("{}", p.frac); // distance from the circle <0..255>
|
||||
/// println!("{}", p.first); // `v` has changed
|
||||
/// println!("{}", p.last); // next `v` will change
|
||||
/// }
|
||||
///
|
||||
/// `u` axis is the main and increments at each iteration.
|
||||
///
|
||||
/// endpoint [t, t] or [t - 1, t] where t = radius * (1 / sqrt(2))
|
||||
|
||||
pub fn circle_points(radius: i16) -> CirclePoints {
|
||||
CirclePoints {
|
||||
radius,
|
||||
u: 0,
|
||||
v: radius,
|
||||
t1: radius / 16,
|
||||
first: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CirclePoints {
|
||||
radius: i16,
|
||||
u: i16,
|
||||
v: i16,
|
||||
t1: i16,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct CirclePointsItem {
|
||||
pub u: i16,
|
||||
pub v: i16,
|
||||
pub frac: u8,
|
||||
pub first: bool,
|
||||
pub last: bool,
|
||||
}
|
||||
|
||||
impl Iterator for CirclePoints {
|
||||
type Item = CirclePointsItem;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.v >= self.u {
|
||||
let mut item = CirclePointsItem {
|
||||
u: self.u,
|
||||
v: self.v,
|
||||
frac: 255 - ((self.t1 as i32 * 255) / self.radius as i32) as u8,
|
||||
first: self.first,
|
||||
last: false,
|
||||
};
|
||||
|
||||
self.first = false;
|
||||
self.u += 1;
|
||||
self.t1 += self.u;
|
||||
let t2 = self.t1 - self.v;
|
||||
if t2 >= 0 {
|
||||
self.t1 = t2;
|
||||
self.v -= 1;
|
||||
self.first = true;
|
||||
}
|
||||
|
||||
item.last = item.v != self.v;
|
||||
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/// Iterator providing points on a line (using bresenham's algorithm)
|
||||
///
|
||||
/// The iterator supplies coordinates of pixels relative to the
|
||||
/// line's start point.
|
||||
///
|
||||
/// constraint: `du` >= `dv`, `start_u` < `du`
|
||||
///
|
||||
/// for p in line_points(du, dv, start_u) {
|
||||
/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>..<du-1, dv-1>
|
||||
/// println!("{}", p.frac); // distance from the line <0..255>
|
||||
/// println!("{}", p.first); // `v` has changed
|
||||
/// println!("{}", p.last); // next `v` will change
|
||||
/// }
|
||||
///
|
||||
/// `u` axis is the main and increments at each iteration.
|
||||
|
||||
pub fn line_points(du: i16, dv: i16, start_u: i16) -> LinePoints {
|
||||
let mut d = 2 * du - 2 * dv;
|
||||
let mut y = 0;
|
||||
|
||||
for _ in 0..start_u {
|
||||
if d <= 0 {
|
||||
d += 2 * du - 2 * dv;
|
||||
y += 1;
|
||||
} else {
|
||||
d -= 2 * dv;
|
||||
}
|
||||
}
|
||||
|
||||
LinePoints {
|
||||
du,
|
||||
dv,
|
||||
d,
|
||||
u: start_u,
|
||||
v: y,
|
||||
first: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LinePoints {
|
||||
du: i16,
|
||||
dv: i16,
|
||||
d: i16,
|
||||
u: i16,
|
||||
v: i16,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct LinePointsItem {
|
||||
pub u: i16,
|
||||
pub v: i16,
|
||||
pub frac: u8,
|
||||
pub first: bool,
|
||||
pub last: bool,
|
||||
}
|
||||
|
||||
impl Iterator for LinePoints {
|
||||
type Item = LinePointsItem;
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.u < self.du {
|
||||
let frac = if self.dv < self.du {
|
||||
255 - ((self.d + 2 * self.dv - 1) as i32 * 255 / (2 * self.du - 1) as i32) as u8
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let next = LinePointsItem {
|
||||
u: self.u,
|
||||
v: self.v,
|
||||
frac,
|
||||
first: self.first,
|
||||
last: self.d <= 0,
|
||||
};
|
||||
|
||||
if self.d <= 0 {
|
||||
self.d += 2 * self.du - 2 * self.dv;
|
||||
self.v += 1;
|
||||
self.first = true;
|
||||
} else {
|
||||
self.d -= 2 * self.dv;
|
||||
self.first = false;
|
||||
}
|
||||
|
||||
self.u += 1;
|
||||
|
||||
Some(next)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
mod blur;
|
||||
mod circle;
|
||||
mod line;
|
||||
mod trigo;
|
||||
|
||||
pub use blur::{BlurAlgorithm, BlurBuff};
|
||||
pub use circle::circle_points;
|
||||
pub use line::line_points;
|
||||
pub use trigo::{sin_i16, PI4};
|
@ -0,0 +1,29 @@
|
||||
/// Integer representing an angle of 45 degress (PI/4).
|
||||
//
|
||||
// Changing this constant requires revisiting isin() algorithm
|
||||
// (for higher values consider changing T type to i64 or f32)
|
||||
pub const PI4: i16 = 45;
|
||||
|
||||
/// Fast sine approximation.
|
||||
///
|
||||
/// Returns mult * sin(angle).
|
||||
///
|
||||
/// Angle must be in range <0..PI4>.
|
||||
/// This function provides an error within +-1 for multiplier up to 500
|
||||
pub fn sin_i16(angle: i16, mult: i16) -> i16 {
|
||||
assert!(angle >= 0 && angle <= PI4);
|
||||
assert!(mult <= 2500);
|
||||
|
||||
type T = i32;
|
||||
|
||||
// Based on polynomial x - x^3 / 6
|
||||
let x = angle as T;
|
||||
|
||||
// Constants for the approximation
|
||||
const K: f32 = (PI4 as f32) * 4.0 / core::f32::consts::PI;
|
||||
const M: T = (6.0 * K * K + 0.5) as T;
|
||||
const N: T = (6.0 * K * K * K + 0.5) as T;
|
||||
|
||||
// Applying the approximation
|
||||
(((M * x - x * x * x) * mult as T + N / 2) / N) as i16
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
use crate::ui::{display::Color, geometry::Rect};
|
||||
|
||||
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
/// A shape for the rendering variuous type of rectangles.
|
||||
pub struct Bar {
|
||||
/// Rectangle position and dimenstion
|
||||
area: Rect,
|
||||
/// Foreground color (default None)
|
||||
fg_color: Option<Color>,
|
||||
/// Background color (default None)
|
||||
bg_color: Option<Color>,
|
||||
/// Thickness (default 0)
|
||||
thickness: i16,
|
||||
/// Corner radius (default 0)
|
||||
radius: i16,
|
||||
/// Alpha (default 255)
|
||||
alpha: u8,
|
||||
}
|
||||
|
||||
impl Bar {
|
||||
pub fn new(area: Rect) -> Self {
|
||||
Self {
|
||||
area,
|
||||
fg_color: None,
|
||||
bg_color: None,
|
||||
thickness: 1,
|
||||
radius: 0,
|
||||
alpha: 255,
|
||||
}
|
||||
}
|
||||
|
||||
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 with_alpha(self, alpha: u8) -> Self {
|
||||
Self { alpha, ..self }
|
||||
}
|
||||
|
||||
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape<'_> for Bar {
|
||||
fn bounds(&self, _cache: &DrawingCache) -> Rect {
|
||||
self.area
|
||||
}
|
||||
|
||||
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) {
|
||||
// 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
|
||||
if th > 0 {
|
||||
let r = self.area;
|
||||
canvas.fill_rect(Rect { y1: r.y0 + th, ..r }, fg_color, self.alpha);
|
||||
canvas.fill_rect(Rect { x1: r.x0 + th, ..r }, fg_color, self.alpha);
|
||||
canvas.fill_rect(Rect { x0: r.x1 - th, ..r }, fg_color, self.alpha);
|
||||
canvas.fill_rect(Rect { y0: r.y1 - th, ..r }, fg_color, self.alpha);
|
||||
}
|
||||
}
|
||||
if let Some(bg_color) = self.bg_color {
|
||||
// background
|
||||
let bg_r = self.area.shrink(th);
|
||||
canvas.fill_rect(bg_r, bg_color, self.alpha);
|
||||
}
|
||||
} else {
|
||||
if let Some(fg_color) = self.fg_color {
|
||||
if th > 0 {
|
||||
if self.bg_color.is_some() {
|
||||
canvas.fill_round_rect(self.area, self.radius, fg_color, self.alpha);
|
||||
} else {
|
||||
#[cfg(not(feature = "ui_antialiasing"))]
|
||||
canvas.draw_round_rect(self.area, self.radius, fg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(bg_color) = self.bg_color {
|
||||
let bg_r = self.area.shrink(th);
|
||||
canvas.fill_round_rect(bg_r, self.radius, bg_color, self.alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ShapeClone<'s> for Bar {
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let clone = bump.alloc_t::<Bar>()?;
|
||||
Some(clone.uninit.init(Bar { ..self }))
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
use crate::ui::geometry::Rect;
|
||||
|
||||
use super::{Canvas, DrawingCache};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
// ==========================================================================
|
||||
// trait Shape
|
||||
// ==========================================================================
|
||||
|
||||
/// This trait is used internally by so-called Renderers -
|
||||
/// `DirectRenderer` & `ProgressiveRederer`.
|
||||
///
|
||||
/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered
|
||||
/// must implement `Shape` trait.
|
||||
///
|
||||
/// `Shape` objects may use `DrawingCache` as a scratch-pad memory or for
|
||||
/// caching expensive calculations results.
|
||||
pub trait Shape<'s> {
|
||||
/// Returns the smallest bounding rectangle containing whole parts of the
|
||||
/// shape.
|
||||
///
|
||||
/// The function is used by renderer for optimization if the shape
|
||||
/// must be renderer or not.
|
||||
fn bounds(&self, cache: &DrawingCache<'s>) -> Rect;
|
||||
|
||||
/// Draws shape on the canvas.
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'s>);
|
||||
|
||||
/// The function should release all allocated resources needed
|
||||
/// for shape drawing.
|
||||
///
|
||||
/// It's called by renderer if the shape's draw() function won't be called
|
||||
/// anymore.
|
||||
fn cleanup(&mut self, cache: &DrawingCache<'s>);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// trait ShapeClone
|
||||
// ==========================================================================
|
||||
|
||||
/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered
|
||||
/// by `ProgressiveRender` must implement `ShapeClone`.
|
||||
pub trait ShapeClone<'s> {
|
||||
/// Clones a shape object at the specified memory bump.
|
||||
///
|
||||
/// The method is used by `ProgressiveRenderer` to store shape objects for
|
||||
/// deferred drawing.
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
'alloc: 's;
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
use crate::trezorhal::dma2d_new::Dma2d;
|
||||
|
||||
use crate::ui::{display::Color, geometry::Offset};
|
||||
|
||||
use core::{cell::Cell, marker::PhantomData};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum BitmapFormat {
|
||||
/// 1-bit mono
|
||||
MONO1,
|
||||
/// 1-bit mono packed (bitmap stride is in bits)
|
||||
MONO1P,
|
||||
/// 4-bit mono
|
||||
MONO4,
|
||||
/// 8-bit mono
|
||||
MONO8,
|
||||
/// 16-bit color, RGB565 format
|
||||
RGB565,
|
||||
/// 32-bit color, RGBA format
|
||||
RGBA8888,
|
||||
}
|
||||
|
||||
pub struct Bitmap<'a> {
|
||||
/// Pointer to top-left pixel
|
||||
ptr: *mut u8,
|
||||
/// Stride in bytes
|
||||
stride: usize,
|
||||
/// Size in pixels
|
||||
size: Offset,
|
||||
/// Format of pixels
|
||||
format: BitmapFormat,
|
||||
/// Bitmap data is mutable
|
||||
mutable: bool,
|
||||
/// DMA operation is pending
|
||||
dma_pending: Cell<bool>,
|
||||
///
|
||||
_phantom: core::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Bitmap<'a> {
|
||||
/// Creates a new bitmap referencing a specified buffer.
|
||||
///
|
||||
/// Optionally minimal height can be specified and then the height
|
||||
/// of the new bitmap is adjusted to the buffer size.
|
||||
///
|
||||
/// Returns None if the buffer is not big enough.
|
||||
///
|
||||
/// The `buff` needs to be properly aligned and big enough
|
||||
/// to hold a bitmap with the specified format and size
|
||||
pub fn new(
|
||||
format: BitmapFormat,
|
||||
stride: Option<usize>,
|
||||
mut size: Offset,
|
||||
min_height: Option<i16>,
|
||||
buff: &'a [u8],
|
||||
) -> Option<Self> {
|
||||
if size.x < 0 && size.y < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let min_stride = match format {
|
||||
BitmapFormat::MONO1 => (size.x + 7) / 8,
|
||||
BitmapFormat::MONO1P => 0,
|
||||
BitmapFormat::MONO4 => (size.x + 1) / 2,
|
||||
BitmapFormat::MONO8 => size.x,
|
||||
BitmapFormat::RGB565 => size.x * 2,
|
||||
BitmapFormat::RGBA8888 => size.x * 4,
|
||||
} as usize;
|
||||
|
||||
let stride = stride.unwrap_or(min_stride);
|
||||
|
||||
let alignment = match format {
|
||||
BitmapFormat::MONO1 => 1,
|
||||
BitmapFormat::MONO1P => 1,
|
||||
BitmapFormat::MONO4 => 1,
|
||||
BitmapFormat::MONO8 => 1,
|
||||
BitmapFormat::RGB565 => 2,
|
||||
BitmapFormat::RGBA8888 => 4,
|
||||
};
|
||||
|
||||
assert!(stride >= min_stride);
|
||||
assert!(buff.as_ptr() as usize & (alignment - 1) == 0);
|
||||
assert!(stride & (alignment - 1) == 0);
|
||||
|
||||
let max_height = if stride == 0 {
|
||||
size.y as usize
|
||||
} else {
|
||||
buff.len() / stride
|
||||
};
|
||||
|
||||
if size.y as usize > max_height {
|
||||
if let Some(min_height) = min_height {
|
||||
if max_height >= min_height as usize {
|
||||
size.y = max_height as i16;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
ptr: buff.as_ptr() as *mut u8,
|
||||
stride,
|
||||
size,
|
||||
format,
|
||||
mutable: false,
|
||||
dma_pending: Cell::new(false),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new mutable bitmap referencing a specified buffer.
|
||||
///
|
||||
/// Optionally minimal height can be specified and then the height
|
||||
/// of the new bitmap is adjusted to the buffer size.
|
||||
///
|
||||
/// Returns None if the buffer is not big enough.
|
||||
///
|
||||
/// The `buff` needs to be properly aligned and big enough
|
||||
/// to hold a bitmap with the specified format and size
|
||||
pub fn new_mut(
|
||||
format: BitmapFormat,
|
||||
stride: Option<usize>,
|
||||
size: Offset,
|
||||
min_height: Option<i16>,
|
||||
buff: &'a mut [u8],
|
||||
) -> Option<Self> {
|
||||
let mut bitmap = Self::new(format, stride, size, min_height, buff)?;
|
||||
bitmap.mutable = true;
|
||||
return Some(bitmap);
|
||||
}
|
||||
|
||||
/// Returns bitmap width in pixels.
|
||||
pub fn width(&self) -> i16 {
|
||||
self.size.x
|
||||
}
|
||||
|
||||
/// Returns bitmap height in pixels.
|
||||
pub fn height(&self) -> i16 {
|
||||
self.size.y
|
||||
}
|
||||
|
||||
/// Returns bitmap width and height in pixels.
|
||||
pub fn size(&self) -> Offset {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Returns bitmap stride in bytes.
|
||||
pub fn stride(&self) -> usize {
|
||||
self.stride
|
||||
}
|
||||
|
||||
/// Returns bitmap format.
|
||||
pub fn format(&self) -> BitmapFormat {
|
||||
self.format
|
||||
}
|
||||
|
||||
pub fn view(&self) -> BitmapView {
|
||||
BitmapView::new(&self)
|
||||
}
|
||||
|
||||
/// Returns the specified row as an immutable slice.
|
||||
///
|
||||
/// Returns None if row is out of range.
|
||||
pub fn row<T>(&self, row: i16) -> Option<&[T]> {
|
||||
if row >= 0 && row < self.size.y {
|
||||
self.wait_for_dma();
|
||||
let offset = row as usize * (self.stride / core::mem::size_of::<T>());
|
||||
Some(unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
(self.ptr as *const T).add(offset),
|
||||
self.stride / core::mem::size_of::<T>(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the specified row as a mutable slice.
|
||||
///
|
||||
/// Returns None if row is out of range.
|
||||
pub fn row_mut<T>(&mut self, row: i16) -> Option<&mut [T]> {
|
||||
if row >= 0 && row < self.size.y {
|
||||
self.wait_for_dma();
|
||||
let offset = row as usize * (self.stride / core::mem::size_of::<T>());
|
||||
Some(unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
(self.ptr as *mut T).add(offset),
|
||||
self.stride / core::mem::size_of::<T>(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns specified consecutive rows as a mutable slice
|
||||
///
|
||||
/// Returns None if any of requested row is out of range.
|
||||
pub fn rows_mut<T>(&mut self, row: i16, height: i16) -> Option<&mut [T]> {
|
||||
if row >= 0 && height > 0 && row < self.size.y && row + height <= self.size.y {
|
||||
self.wait_for_dma();
|
||||
let offset = self.stride * row as usize;
|
||||
let len = self.stride * height as usize;
|
||||
|
||||
let array = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
self.ptr as *mut T,
|
||||
self.size.y as usize * self.stride / core::mem::size_of::<T>(),
|
||||
)
|
||||
};
|
||||
|
||||
Some(&mut array[offset..offset + len])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Return raw mut pointer to the specified bitmap row.
|
||||
///
|
||||
/// `y` must be in range <0; self.height() - 1>.
|
||||
pub unsafe fn row_ptr(&self, y: u16) -> *mut cty::c_void {
|
||||
unsafe { self.ptr.add(self.stride() * y as usize) as *mut cty::c_void }
|
||||
}
|
||||
|
||||
/// Waits until DMA operation is finished
|
||||
fn wait_for_dma(&self) {
|
||||
if self.dma_pending.get() {
|
||||
Dma2d::wait_for_transfer();
|
||||
self.dma_pending.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark bitmap as DMA operation is pending
|
||||
pub fn mark_dma_pending(&self) {
|
||||
self.dma_pending.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for Bitmap<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.wait_for_dma();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BitmapView<'a> {
|
||||
pub bitmap: &'a Bitmap<'a>,
|
||||
pub offset: Offset,
|
||||
pub fg_color: Color,
|
||||
pub bg_color: Color,
|
||||
}
|
||||
|
||||
impl<'a> BitmapView<'a> {
|
||||
/// Creates a new reference to the bitmap
|
||||
pub fn new(bitmap: &'a Bitmap) -> Self {
|
||||
Self {
|
||||
bitmap,
|
||||
offset: Offset::zero(),
|
||||
fg_color: Color::black(),
|
||||
bg_color: Color::black(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new structure with offset set to the specified value
|
||||
pub fn with_offset(self, offset: Offset) -> Self {
|
||||
Self {
|
||||
offset: (offset + self.offset.into()).into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new structure with foreground color set to the specified value
|
||||
pub fn with_fg(self, fg_color: Color) -> Self {
|
||||
Self {
|
||||
fg_color: fg_color.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new structure with background color set to the specified value
|
||||
pub fn with_bg(self, bg_color: Color) -> Self {
|
||||
Self {
|
||||
bg_color: bg_color.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bitmap width and height in pixels
|
||||
pub fn size(&self) -> Offset {
|
||||
self.bitmap.size
|
||||
}
|
||||
|
||||
/// Returns the bitmap width in pixels
|
||||
pub fn width(&self) -> i16 {
|
||||
self.bitmap.width()
|
||||
}
|
||||
|
||||
/// Returns the bitmap height in pixels
|
||||
pub fn height(&self) -> i16 {
|
||||
self.bitmap.height()
|
||||
}
|
||||
|
||||
/// Returns the bitmap format
|
||||
pub fn format(&self) -> BitmapFormat {
|
||||
self.bitmap.format
|
||||
}
|
||||
|
||||
/// Returns the specified row as an immutable slice.
|
||||
///
|
||||
/// Returns None if row is out of range.
|
||||
pub fn row<T>(&self, row: i16) -> Option<&[T]> {
|
||||
self.bitmap.row(row)
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
pub mod bitmap;
|
||||
pub mod mono8;
|
||||
pub mod rgb565;
|
||||
pub mod rgba8888;
|
||||
|
||||
pub use bitmap::{Bitmap, BitmapFormat, BitmapView};
|
@ -0,0 +1,48 @@
|
||||
use super::{Bitmap, BitmapFormat, BitmapView};
|
||||
use crate::{
|
||||
trezorhal::dma2d_new::Dma2d,
|
||||
ui::{display::Color, geometry::Rect},
|
||||
};
|
||||
|
||||
impl<'a> Bitmap<'a> {
|
||||
/// Fills a rectangle with the specified color.
|
||||
///
|
||||
/// The function is aplicable only on bitmaps with RGB565 format.
|
||||
pub fn mono8_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) {
|
||||
assert!(self.format() == BitmapFormat::MONO8);
|
||||
if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
unsafe { dma2d.mono8_fill() };
|
||||
self.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
pub fn mono8_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
|
||||
assert!(self.format() == BitmapFormat::MONO8);
|
||||
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
match src.format() {
|
||||
BitmapFormat::MONO1P => unsafe { dma2d.mono8_copy_mono1p() },
|
||||
BitmapFormat::MONO4 => unsafe { dma2d.mono8_copy_mono4() },
|
||||
_ => panic!("Unsupported DMA operation"),
|
||||
}
|
||||
self.mark_dma_pending();
|
||||
src.bitmap.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mono8_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
|
||||
assert!(self.format() == BitmapFormat::MONO8);
|
||||
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
match src.format() {
|
||||
BitmapFormat::MONO1P => unsafe { dma2d.mono8_blend_mono1p() },
|
||||
BitmapFormat::MONO4 => unsafe { dma2d.mono8_blend_mono4() },
|
||||
_ => panic!("Unsupported DMA operation"),
|
||||
}
|
||||
self.mark_dma_pending();
|
||||
src.bitmap.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
use super::{Bitmap, BitmapFormat, BitmapView};
|
||||
use crate::{
|
||||
trezorhal::dma2d_new::Dma2d,
|
||||
ui::{display::Color, geometry::Rect},
|
||||
};
|
||||
|
||||
impl<'a> Bitmap<'a> {
|
||||
/// Fills a rectangle with the specified color.
|
||||
///
|
||||
/// The function is aplicable only on bitmaps with RGB565 format.
|
||||
pub fn rgb565_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) {
|
||||
assert!(self.format() == BitmapFormat::RGB565);
|
||||
if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
unsafe { dma2d.rgb565_fill() };
|
||||
self.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
pub fn rgb565_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
|
||||
assert!(self.format() == BitmapFormat::RGB565);
|
||||
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
match src.format() {
|
||||
BitmapFormat::MONO4 => unsafe { dma2d.rgb565_copy_mono4() },
|
||||
BitmapFormat::RGB565 => unsafe { dma2d.rgb565_copy_rgb565() },
|
||||
_ => panic!("Unsupported DMA operation"),
|
||||
}
|
||||
self.mark_dma_pending();
|
||||
src.bitmap.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgb565_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
|
||||
assert!(self.format() == BitmapFormat::RGB565);
|
||||
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
match src.format() {
|
||||
BitmapFormat::MONO4 => unsafe { dma2d.rgb565_blend_mono4() },
|
||||
_ => panic!("Unsupported DMA operation"),
|
||||
}
|
||||
self.mark_dma_pending();
|
||||
src.bitmap.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
use super::{Bitmap, BitmapFormat, BitmapView};
|
||||
use crate::{
|
||||
trezorhal::dma2d_new::Dma2d,
|
||||
ui::{display::Color, geometry::Rect},
|
||||
};
|
||||
|
||||
impl<'a> Bitmap<'a> {
|
||||
/// Fills a rectangle with the specified color.
|
||||
///
|
||||
/// The function is aplicable only on bitmaps with RGBA888 format.
|
||||
pub fn rgba8888_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) {
|
||||
assert!(self.format() == BitmapFormat::RGBA8888);
|
||||
if let Some(dma2d) = Dma2d::new_fill(r, clip, color, alpha) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
unsafe { dma2d.rgba8888_fill() };
|
||||
self.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgba8888_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
|
||||
assert!(self.format() == BitmapFormat::RGBA8888);
|
||||
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
match src.format() {
|
||||
BitmapFormat::MONO4 => unsafe { dma2d.rgba8888_copy_mono4() },
|
||||
BitmapFormat::RGB565 => unsafe { dma2d.rgba8888_copy_rgb565() },
|
||||
BitmapFormat::RGBA8888 => unsafe { dma2d.rgba8888_copy_rgba8888() },
|
||||
_ => panic!("Unsupported DMA operation"),
|
||||
}
|
||||
self.mark_dma_pending();
|
||||
src.bitmap.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgba8888_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) {
|
||||
assert!(self.format() == BitmapFormat::RGBA8888);
|
||||
if let Some(dma2d) = Dma2d::new_copy(r, clip, src) {
|
||||
let dma2d = dma2d.with_dst(self);
|
||||
match src.format() {
|
||||
BitmapFormat::MONO4 => unsafe { dma2d.rgba8888_blend_mono4() },
|
||||
_ => panic!("Unsupported DMA operation"),
|
||||
}
|
||||
self.mark_dma_pending();
|
||||
src.bitmap.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
use crate::ui::geometry::Rect;
|
||||
|
||||
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
pub struct Blurring {
|
||||
// Blurred area
|
||||
area: Rect,
|
||||
/// Blurring kernel radius
|
||||
radius: usize,
|
||||
}
|
||||
|
||||
/// A shape for the blurring of a specified rectangle area.
|
||||
impl Blurring {
|
||||
pub fn new(area: Rect, radius: usize) -> Self {
|
||||
Self { area, radius }
|
||||
}
|
||||
|
||||
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape<'_> for Blurring {
|
||||
fn bounds(&self, _cache: &DrawingCache) -> Rect {
|
||||
self.area
|
||||
}
|
||||
|
||||
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
|
||||
canvas.blur_rect(self.area, self.radius, cache);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ShapeClone<'s> for Blurring {
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let clone = bump.alloc_t::<Blurring>()?;
|
||||
Some(clone.uninit.init(Blurring { ..self }))
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
use super::super::algo::{BlurAlgorithm, BlurBuff};
|
||||
use crate::ui::geometry::Offset;
|
||||
use core::cell::UnsafeCell;
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
pub struct BlurCache<'a> {
|
||||
algo: Option<BlurAlgorithm<'a>>,
|
||||
buff: &'a UnsafeCell<BlurBuff>,
|
||||
tag: u32,
|
||||
}
|
||||
|
||||
impl<'a> BlurCache<'a> {
|
||||
pub fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option<Self>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let buff = bump
|
||||
.alloc_t::<UnsafeCell<BlurBuff>>()?
|
||||
.uninit
|
||||
.init(UnsafeCell::new([0; 7928])); // TODO !!! 7928
|
||||
|
||||
Some(Self {
|
||||
algo: None,
|
||||
buff,
|
||||
tag: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
&mut self,
|
||||
size: Offset,
|
||||
radius: usize,
|
||||
tag: Option<u32>,
|
||||
) -> Result<(&mut BlurAlgorithm<'a>, u32), ()> {
|
||||
if let Some(tag) = tag {
|
||||
if self.tag == tag {
|
||||
return Ok((unwrap!(self.algo.as_mut()), self.tag));
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the existing blurring inbstance holding
|
||||
// a mutable reference to its scratchpad buffer
|
||||
self.algo = None;
|
||||
self.tag += 1;
|
||||
|
||||
// Now there's nobody else holding any reference to our buffer
|
||||
// so we can get mutable reference and pass it to a new
|
||||
// instance of the blurring algorithm
|
||||
let buff = unsafe { &mut *self.buff.get() };
|
||||
|
||||
self.algo = Some(BlurAlgorithm::new(size, radius, buff)?);
|
||||
|
||||
Ok((unwrap!(self.algo.as_mut()), self.tag))
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
use super::zlib_cache::ZlibCache;
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
use super::blur_cache::BlurCache;
|
||||
|
||||
#[cfg(feature = "ui_jpeg_decoder")]
|
||||
use super::jpeg_cache::JpegCache;
|
||||
|
||||
use core::cell::{RefCell, RefMut};
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
const ALIGN_PAD: usize = 8;
|
||||
|
||||
const ZLIB_CACHE_SLOTS: usize = 4;
|
||||
const JPEG_CACHE_SLOTS: usize = 1;
|
||||
const RENDER_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD;
|
||||
const IMAGE_BUFF_SIZE: usize = 2048 + ALIGN_PAD;
|
||||
|
||||
pub type ImageBuff = [u8; IMAGE_BUFF_SIZE];
|
||||
pub type RenderBuff = [u8; RENDER_BUFF_SIZE];
|
||||
|
||||
pub type ImageBuffRef<'a> = RefMut<'a, ImageBuff>;
|
||||
pub type RenderBuffRef<'a> = RefMut<'a, RenderBuff>;
|
||||
|
||||
pub struct DrawingCache<'a> {
|
||||
zlib_cache: RefCell<ZlibCache<'a>>,
|
||||
|
||||
#[cfg(feature = "ui_jpeg_decoder")]
|
||||
jpeg_cache: RefCell<JpegCache<'a>>,
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
blur_cache: RefCell<BlurCache<'a>>,
|
||||
|
||||
render_buff: &'a RefCell<RenderBuff>,
|
||||
image_buff: &'a RefCell<ImageBuff>,
|
||||
}
|
||||
|
||||
fn alloc_buf<'a, const S: usize, B>(bump: &'a B) -> Option<&'a RefCell<[u8; S]>>
|
||||
where
|
||||
B: LocalAllocLeakExt<'a>,
|
||||
{
|
||||
Some(
|
||||
bump.alloc_t::<RefCell<[u8; S]>>()?
|
||||
.uninit
|
||||
.init(RefCell::new([0; S])),
|
||||
)
|
||||
}
|
||||
|
||||
impl<'a> DrawingCache<'a> {
|
||||
pub fn new<TA, TB>(bump_a: &'a TA, bump_b: &'a TB) -> Self
|
||||
where
|
||||
TA: LocalAllocLeakExt<'a>,
|
||||
TB: LocalAllocLeakExt<'a>,
|
||||
{
|
||||
Self {
|
||||
zlib_cache: RefCell::new(unwrap!(
|
||||
ZlibCache::new(bump_a, ZLIB_CACHE_SLOTS),
|
||||
"ZLIB cache alloc"
|
||||
)),
|
||||
#[cfg(feature = "ui_jpeg_decoder")]
|
||||
jpeg_cache: RefCell::new(unwrap!(
|
||||
JpegCache::new(bump_a, JPEG_CACHE_SLOTS),
|
||||
"JPEG cache alloc"
|
||||
)),
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
blur_cache: RefCell::new(unwrap!(BlurCache::new(bump_a), "Blur cache alloc")),
|
||||
render_buff: unwrap!(alloc_buf(bump_b), "Render buff alloc"),
|
||||
image_buff: unwrap!(alloc_buf(bump_b), "Toif buff alloc"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an object for decompression of TOIF images
|
||||
pub fn zlib(&self) -> RefMut<ZlibCache<'a>> {
|
||||
self.zlib_cache.borrow_mut()
|
||||
}
|
||||
|
||||
/// Returns an object for decompression of JPEG images
|
||||
#[cfg(feature = "ui_jpeg_decoder")]
|
||||
pub fn jpeg(&self) -> RefMut<JpegCache<'a>> {
|
||||
self.jpeg_cache.borrow_mut()
|
||||
}
|
||||
|
||||
/// Returns an object providing blurring algorithm
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
pub fn blur(&self) -> RefMut<BlurCache<'a>> {
|
||||
self.blur_cache.borrow_mut()
|
||||
}
|
||||
|
||||
/// Returns a buffer used for ProgressiveRenderer slice
|
||||
pub fn render_buff(&self) -> Option<RenderBuffRef<'a>> {
|
||||
self.render_buff.try_borrow_mut().ok()
|
||||
}
|
||||
|
||||
/// Returns a buffer for intended for drawing of
|
||||
/// QrCode or ToifImage
|
||||
pub fn image_buff(&self) -> Option<ImageBuffRef<'a>> {
|
||||
self.image_buff.try_borrow_mut().ok()
|
||||
}
|
||||
}
|
@ -0,0 +1,364 @@
|
||||
use crate::ui::{
|
||||
display::tjpgd,
|
||||
geometry::{Offset, Point, Rect},
|
||||
shape::{BasicCanvas, Bitmap, BitmapFormat, BitmapView, Canvas, Rgb565Canvas},
|
||||
};
|
||||
|
||||
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_SCRATCHPAD_SIZE: usize = 10500; // the same const > 10336 as in original code
|
||||
|
||||
// Buffer for a cached row of JPEG MCUs (up to 240x16 RGB565 pixels)
|
||||
const ALIGN_PAD: usize = 8;
|
||||
const JPEG_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD;
|
||||
|
||||
pub struct JpegCacheSlot<'a> {
|
||||
// Reference to compressed data
|
||||
jpeg: &'a [u8],
|
||||
// value in range 0..3 leads into scale factor 1 << scale
|
||||
scale: 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_SCRATCHPAD_SIZE]>,
|
||||
// horizontal coordinate of cached row or None
|
||||
// (valid if row_canvas is Some)
|
||||
row_y: i16,
|
||||
// Canvas for recently decoded row of MCU's
|
||||
row_canvas: Option<Rgb565Canvas<'a>>,
|
||||
// Buffer for slice canvas
|
||||
row_buff: &'a UnsafeCell<[u8; JPEG_BUFF_SIZE]>,
|
||||
}
|
||||
|
||||
impl<'a> JpegCacheSlot<'a> {
|
||||
fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option<Self>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let scratchpad = bump
|
||||
.alloc_t::<UnsafeCell<[u8; JPEG_SCRATCHPAD_SIZE]>>()?
|
||||
.uninit
|
||||
.init(UnsafeCell::new([0; JPEG_SCRATCHPAD_SIZE]));
|
||||
|
||||
let canvas_buff = bump
|
||||
.alloc_t::<UnsafeCell<[u8; JPEG_BUFF_SIZE]>>()?
|
||||
.uninit
|
||||
.init(UnsafeCell::new([0; JPEG_BUFF_SIZE]));
|
||||
|
||||
Some(Self {
|
||||
jpeg: &[],
|
||||
scale: 0,
|
||||
input: None,
|
||||
decoder: None,
|
||||
scratchpad,
|
||||
row_y: 0,
|
||||
row_canvas: None,
|
||||
row_buff: canvas_buff,
|
||||
})
|
||||
}
|
||||
|
||||
fn reset<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result<(), tjpgd::Error> {
|
||||
// Drop the existing decoder holding
|
||||
// a mutable reference to the scratchpad and canvas buffer & c
|
||||
self.decoder = None;
|
||||
self.row_canvas = None;
|
||||
|
||||
if !jpeg.is_empty() {
|
||||
// Now there's nobody else holding any reference to our scratchpad buffer
|
||||
// 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
|
||||
let mut decoder = tjpgd::JDEC::new(&mut input, scratchpad)?;
|
||||
// Set decoder scale factor
|
||||
decoder.set_scale(scale)?;
|
||||
self.decoder = Some(decoder);
|
||||
// Save modified input buffer
|
||||
self.input = Some(input);
|
||||
} else {
|
||||
self.input = None;
|
||||
}
|
||||
|
||||
self.jpeg = jpeg;
|
||||
self.scale = scale;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_for<'i: 'a>(&self, jpeg: &'i [u8], scale: u8) -> bool {
|
||||
jpeg == self.jpeg && scale == self.scale && self.decoder.is_some()
|
||||
}
|
||||
|
||||
pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result<Offset, tjpgd::Error> {
|
||||
if !self.is_for(jpeg, scale) {
|
||||
self.reset(jpeg, scale)?;
|
||||
}
|
||||
let decoder = unwrap!(self.decoder.as_mut()); // should never fail
|
||||
let divisor = 1 << self.scale;
|
||||
Ok(Offset::new(
|
||||
decoder.width() / divisor,
|
||||
decoder.height() / divisor,
|
||||
))
|
||||
}
|
||||
|
||||
// left-top origin of output rectangle must be aligned to JPEG MCU size
|
||||
pub fn decompress_mcu<'i: 'a>(
|
||||
&mut self,
|
||||
jpeg: &'i [u8],
|
||||
scale: u8,
|
||||
offset: Point,
|
||||
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
|
||||
) -> Result<(), tjpgd::Error> {
|
||||
// Reset the slot if the JPEG image is different
|
||||
if !self.is_for(jpeg, scale) {
|
||||
self.reset(jpeg, scale)?;
|
||||
}
|
||||
|
||||
// Get coordinates of the next coming MCU
|
||||
let decoder = unwrap!(self.decoder.as_ref()); // should never fail
|
||||
let divisor = 1 << self.scale;
|
||||
let next_mcu = Offset::new(
|
||||
decoder.next_mcu().0 as i16 / divisor,
|
||||
decoder.next_mcu().1 as i16 / divisor,
|
||||
);
|
||||
|
||||
// Get height of the MCUs (8 or 16pixels)
|
||||
let mcu_height = decoder.mcu_height() / (1 << self.scale);
|
||||
|
||||
// Reset the decoder if pixel at the offset 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, scale)?;
|
||||
}
|
||||
|
||||
let decoder = unwrap!(self.decoder.as_mut()); // should never fail
|
||||
let input = unwrap!(self.input.as_mut()); // should never fail
|
||||
let mut output = JpegFnOutput::new(output);
|
||||
|
||||
match decoder.decomp2(input, &mut output) {
|
||||
Ok(_) | Err(tjpgd::Error::Interrupted) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decompress_row<'i: 'a>(
|
||||
&mut self,
|
||||
jpeg: &'i [u8],
|
||||
scale: u8,
|
||||
mut offset_y: i16,
|
||||
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
|
||||
) -> Result<(), tjpgd::Error> {
|
||||
// Reset the slot if the JPEG image is different
|
||||
if !self.is_for(jpeg, scale) {
|
||||
self.reset(jpeg, scale)?;
|
||||
}
|
||||
|
||||
let mut row_canvas = self.row_canvas.take();
|
||||
let mut row_y = self.row_y;
|
||||
|
||||
// Use cached data if possible
|
||||
if let Some(row_canvas) = row_canvas.as_mut() {
|
||||
if offset_y >= self.row_y && offset_y < self.row_y + row_canvas.height() {
|
||||
if !output(
|
||||
Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)),
|
||||
row_canvas.view(),
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
// Align to the next MCU row
|
||||
offset_y += row_canvas.height() - offset_y % row_canvas.height();
|
||||
}
|
||||
} else {
|
||||
// Create a new row for cahing decoded JPEG data
|
||||
// Now there's nobody else holding any reference to canvas_buff so
|
||||
// we can get a mutable reference and pass it to a new instance
|
||||
// of Rgb565Canvas
|
||||
let canvas_buff = unsafe { &mut *self.row_buff.get() };
|
||||
// Prepare canvas as a cache for a row of decoded JPEG MCUs
|
||||
let decoder = unwrap!(self.decoder.as_ref()); // shoud never fail
|
||||
let divisor = 1 << self.scale;
|
||||
row_canvas = Some(unwrap!(
|
||||
Rgb565Canvas::new(
|
||||
Offset::new(decoder.width() / divisor, decoder.mcu_height() / divisor),
|
||||
None,
|
||||
canvas_buff
|
||||
),
|
||||
"Buffer too small"
|
||||
));
|
||||
}
|
||||
|
||||
self.decompress_mcu(
|
||||
jpeg,
|
||||
scale,
|
||||
Point::new(0, offset_y),
|
||||
&mut |mcu_r, mcu_bitmap| {
|
||||
// Get canvas for MCU caching
|
||||
let row_canvas = unwrap!(row_canvas.as_mut()); // should never fail
|
||||
|
||||
// Calculate coordinates in the row canvas
|
||||
let dst_r = Rect {
|
||||
y0: 0,
|
||||
y1: mcu_r.height(),
|
||||
..mcu_r
|
||||
};
|
||||
// Draw a MCU
|
||||
row_canvas.draw_bitmap(dst_r, mcu_bitmap);
|
||||
|
||||
if mcu_r.x1 < row_canvas.size().x {
|
||||
// We are not done with the row yet
|
||||
true
|
||||
} else {
|
||||
// We have a complete row, let's pass it to the callee
|
||||
row_y = mcu_r.y0;
|
||||
output(
|
||||
Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)),
|
||||
row_canvas.view(),
|
||||
)
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
// Store the recently decoded row for future use
|
||||
self.row_y = row_y;
|
||||
self.row_canvas = row_canvas;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct JpegFnOutput<F>
|
||||
where
|
||||
F: FnMut(Rect, BitmapView) -> bool,
|
||||
{
|
||||
output: F,
|
||||
}
|
||||
|
||||
impl<F> JpegFnOutput<F>
|
||||
where
|
||||
F: FnMut(Rect, BitmapView) -> bool,
|
||||
{
|
||||
pub fn new(output: F) -> Self {
|
||||
Self { output }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> trezor_tjpgdec::JpegOutput for JpegFnOutput<F>
|
||||
where
|
||||
F: FnMut(Rect, BitmapView) -> 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),
|
||||
);
|
||||
|
||||
// SAFETY: aligning from [u16] -> [u8]
|
||||
let (_, pixels, _) = unsafe { pixels.align_to() };
|
||||
|
||||
// Create readonly bitmap
|
||||
let mcu_bitmap = unwrap!(Bitmap::new(
|
||||
BitmapFormat::RGB565,
|
||||
None,
|
||||
mcu_r.size(),
|
||||
None,
|
||||
pixels,
|
||||
));
|
||||
|
||||
// Return true to continue decompression
|
||||
(self.output)(mcu_r, BitmapView::new(&mcu_bitmap))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JpegCache<'a> {
|
||||
slots: FixedVec<'a, JpegCacheSlot<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> JpegCache<'a> {
|
||||
pub fn new<'alloc: 'a, T>(bump: &'alloc T, slot_count: usize) -> Option<Self>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
assert!(slot_count <= 1); // we support just 1 decoder
|
||||
|
||||
let mut cache = Self {
|
||||
slots: bump.fixed_vec(slot_count)?,
|
||||
};
|
||||
|
||||
for _ in 0..cache.slots.capacity() {
|
||||
unwrap!(cache.slots.push(JpegCacheSlot::new(bump)?)); // should never fail
|
||||
}
|
||||
|
||||
Some(cache)
|
||||
}
|
||||
|
||||
pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result<Offset, tjpgd::Error> {
|
||||
if self.slots.capacity() > 0 {
|
||||
self.slots[0].get_size(jpeg, scale)
|
||||
} else {
|
||||
Err(tjpgd::Error::MemoryPool)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decompress_mcu<'i: 'a>(
|
||||
&mut self,
|
||||
jpeg: &'i [u8],
|
||||
scale: u8,
|
||||
offset: Point,
|
||||
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
|
||||
) -> Result<(), tjpgd::Error> {
|
||||
if self.slots.capacity() > 0 {
|
||||
self.slots[0].decompress_mcu(jpeg, scale, offset, output)
|
||||
} else {
|
||||
Err(tjpgd::Error::MemoryPool)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decompress_row<'i: 'a>(
|
||||
&mut self,
|
||||
jpeg: &'i [u8],
|
||||
scale: u8,
|
||||
offset_y: i16,
|
||||
output: &mut dyn FnMut(Rect, BitmapView) -> bool,
|
||||
) -> Result<(), tjpgd::Error> {
|
||||
if self.slots.capacity() > 0 {
|
||||
self.slots[0].decompress_row(jpeg, scale, offset_y, output)
|
||||
} else {
|
||||
Err(tjpgd::Error::MemoryPool)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
pub mod blur_cache;
|
||||
pub mod drawing_cache;
|
||||
|
||||
#[cfg(feature = "ui_jpeg_decoder")]
|
||||
pub mod jpeg_cache;
|
||||
|
||||
pub mod zlib_cache;
|
@ -0,0 +1,166 @@
|
||||
use crate::{
|
||||
trezorhal::uzlib::{UzlibContext, UZLIB_WINDOW_SIZE},
|
||||
ui::display::toif::Toif,
|
||||
};
|
||||
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>(bump: &'alloc T) -> Option<Self>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let window = bump
|
||||
.alloc_t::<UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>>()?
|
||||
.uninit
|
||||
.init(UnsafeCell::new([0; UZLIB_WINDOW_SIZE]));
|
||||
|
||||
Some(Self {
|
||||
zdata: &[],
|
||||
offset: 0,
|
||||
dc: None,
|
||||
window,
|
||||
})
|
||||
}
|
||||
|
||||
/// May be called with zdata == &[] to make the slot free
|
||||
fn reset(&mut self, zdata: &'a [u8]) {
|
||||
// Drop the existing decompression context holding
|
||||
// a mutable reference to window buffer
|
||||
self.dc = None;
|
||||
|
||||
if !zdata.is_empty() {
|
||||
// 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(&self, zdata: &[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>(bump: &'alloc T, slot_count: usize) -> Option<Self>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let mut cache = Self {
|
||||
slots: bump.fixed_vec(slot_count)?,
|
||||
};
|
||||
|
||||
for _ in 0..cache.slots.capacity() {
|
||||
unwrap!(cache.slots.push(ZlibCacheSlot::new(bump)?)); // should never fail
|
||||
}
|
||||
|
||||
Some(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: &'a [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)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uncompress_toif(
|
||||
&mut self,
|
||||
toif: Toif<'a>,
|
||||
from_row: i16,
|
||||
dest_buf: &mut [u8],
|
||||
) -> Result<(), ()> {
|
||||
let from_offset = toif.stride() * from_row as usize;
|
||||
self.uncompress(toif.zdata(), from_offset, dest_buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,891 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
algo::{circle_points, line_points, sin_i16, PI4},
|
||||
BitmapView, Viewport,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
use crate::ui::shape::DrawingCache;
|
||||
|
||||
pub trait BasicCanvas {
|
||||
/// Returns dimensions of the canvas in pixels.
|
||||
fn size(&self) -> Offset;
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// Returns the width of the canvas in pixels.
|
||||
fn width(&self) -> i16 {
|
||||
self.size().x
|
||||
}
|
||||
|
||||
/// Returns the height of the canvas in pixels.
|
||||
fn height(&self) -> i16 {
|
||||
self.size().y
|
||||
}
|
||||
|
||||
/// Gets the current drawing viewport previously set by `set_viewport()`
|
||||
/// function.
|
||||
fn viewport(&self) -> Viewport;
|
||||
|
||||
/// Sets the active viewport valid for all subsequent drawing operations.
|
||||
fn set_viewport(&mut self, vp: Viewport);
|
||||
|
||||
/// Sets the new viewport that's intersection of the
|
||||
/// current viewport and the `window` rectangle relative
|
||||
/// to the current viewport. The viewport's origin is
|
||||
/// set to the top-left corener of the `window`.
|
||||
fn set_window(&mut self, window: Rect) -> Viewport {
|
||||
let viewport = self.viewport();
|
||||
self.set_viewport(viewport.relative_window(window));
|
||||
viewport
|
||||
}
|
||||
|
||||
/// Sets the new viewport that's intersection of the
|
||||
/// current viewport and the `clip` rectangle relative
|
||||
/// to the current viewport. The viewport's origin is
|
||||
/// not changed.
|
||||
fn set_clip(&mut self, clip: Rect) -> Viewport {
|
||||
let viewport = self.viewport();
|
||||
self.set_viewport(viewport.relative_clip(clip));
|
||||
viewport
|
||||
}
|
||||
|
||||
/// Draws a filled rectangle with the specified color.
|
||||
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8);
|
||||
|
||||
/// Fills the canvas background with the specified color.
|
||||
fn fill_background(&mut self, color: Color) {
|
||||
self.fill_rect(self.viewport().clip, color, 255);
|
||||
}
|
||||
|
||||
/// Draws a bitmap of bitmap into to the rectangle.
|
||||
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView);
|
||||
}
|
||||
|
||||
pub trait Canvas: BasicCanvas {
|
||||
/// Returns a non-mutable view of the underlying bitmap.
|
||||
fn view(&self) -> BitmapView;
|
||||
|
||||
/// Draw a pixel at specified coordinates.
|
||||
fn draw_pixel(&mut self, pt: Point, color: Color);
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Blends a bitmap with the canvas background
|
||||
fn blend_bitmap(&mut self, r: Rect, src: BitmapView);
|
||||
|
||||
/// 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.
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache);
|
||||
|
||||
/// Draws an outline of a rectangle with rounded corners.
|
||||
fn draw_round_rect(&mut self, r: Rect, radius: i16, color: Color) {
|
||||
let split = unwrap!(circle_points(radius).last()).v;
|
||||
|
||||
let b = Rect {
|
||||
y1: r.y0 + radius - split + 1,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
||||
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
||||
if p.v == radius && p.last {
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
|
||||
} 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 p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
||||
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
||||
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,
|
||||
255,
|
||||
);
|
||||
|
||||
self.fill_rect(
|
||||
Rect {
|
||||
x0: r.x1 - 1,
|
||||
y0: r.y0 + radius + 1,
|
||||
x1: r.x1,
|
||||
y1: r.y1 - radius - 1,
|
||||
},
|
||||
color,
|
||||
255,
|
||||
);
|
||||
|
||||
let b = Rect {
|
||||
y0: r.y1 - radius - 1,
|
||||
y1: r.y1 - radius - 1 + split,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
||||
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
||||
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 p in circle_points(radius) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
||||
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
||||
|
||||
if p.v == radius && p.last {
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
|
||||
} else {
|
||||
self.draw_pixel(pt_l, color);
|
||||
self.draw_pixel(pt_r, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws filled rectangle with rounded corners.
|
||||
#[cfg(not(feature = "ui_antialiasing"))]
|
||||
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
|
||||
let split = unwrap!(circle_points(radius).last()).v;
|
||||
|
||||
let b = Rect {
|
||||
y1: r.y0 + radius - split + 1,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius) {
|
||||
if p.last {
|
||||
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
||||
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let b = Rect {
|
||||
y0: r.y0 + radius - split + 1,
|
||||
y1: r.y0 + radius + 1,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
||||
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
self.fill_rect(
|
||||
Rect {
|
||||
x0: r.x0,
|
||||
y0: r.y0 + radius + 1,
|
||||
x1: r.x1,
|
||||
y1: r.y1 - radius - 1,
|
||||
},
|
||||
color,
|
||||
alpha,
|
||||
);
|
||||
|
||||
let b = Rect {
|
||||
y0: r.y1 - radius - 1,
|
||||
y1: r.y1 - radius - 1 + split,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
||||
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
let b = Rect {
|
||||
y0: r.y1 - radius - 1 + split,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius) {
|
||||
if p.last {
|
||||
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
||||
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws filled rectangle with antialiased rounded corners.
|
||||
#[cfg(feature = "ui_antialiasing")]
|
||||
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
|
||||
let split = unwrap!(circle_points(radius).last()).v;
|
||||
|
||||
let b = Rect {
|
||||
y1: r.y0 + radius - split + 1,
|
||||
..r
|
||||
};
|
||||
|
||||
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
||||
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
|
||||
if p.first {
|
||||
let inner = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(inner, color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let b = Rect {
|
||||
y0: r.y0 + radius - split + 1,
|
||||
y1: r.y0 + radius + 1,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
||||
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
|
||||
let inner = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(inner, color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
self.fill_rect(
|
||||
Rect {
|
||||
x0: r.x0,
|
||||
y0: r.y0 + radius + 1,
|
||||
x1: r.x1,
|
||||
y1: r.y1 - radius - 1,
|
||||
},
|
||||
color,
|
||||
alpha,
|
||||
);
|
||||
|
||||
let b = Rect {
|
||||
y0: r.y1 - radius - 1,
|
||||
y1: r.y1 - radius - 1 + split,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
||||
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
|
||||
let b = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(b, color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
let b = Rect {
|
||||
y0: r.y1 - radius - 1 + split,
|
||||
..r
|
||||
};
|
||||
|
||||
if self.viewport().contains(b) {
|
||||
for p in circle_points(radius) {
|
||||
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
|
||||
if p.first {
|
||||
let b = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(b, color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws circle with the specified center and the radius.
|
||||
#[cfg(not(feature = "ui_antialiasing"))]
|
||||
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
|
||||
let split = unwrap!(circle_points(radius).last()).v;
|
||||
|
||||
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 p in circle_points(radius) {
|
||||
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
||||
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
||||
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 p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
||||
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
||||
self.draw_pixel(pt_l, color);
|
||||
self.draw_pixel(pt_r, color);
|
||||
}
|
||||
}
|
||||
|
||||
let r = Rect::new(
|
||||
Point::new(center.x - radius, center.y + 1),
|
||||
Point::new(center.x + radius + 1, center.y + split + 1),
|
||||
);
|
||||
|
||||
if self.viewport().contains(r) {
|
||||
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
||||
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
||||
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 p in circle_points(radius) {
|
||||
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
||||
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
||||
self.draw_pixel(pt_l, color);
|
||||
self.draw_pixel(pt_r, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws antialiased circle with the specified center and the radius.
|
||||
/*#[cfg(feature = "ui_antialiasing")]
|
||||
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
|
||||
let split = unwrap!(circle_points(radius).last()).v;
|
||||
|
||||
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 p in circle_points(radius) {
|
||||
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
||||
self.blend_pixel(pt_l, color, p.frac);
|
||||
self.blend_pixel(pt_l.under(), color, 255 - p.frac);
|
||||
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
||||
self.blend_pixel(pt_r, color, p.frac);
|
||||
self.blend_pixel(pt_r.under(), color, 255 - p.frac);
|
||||
}
|
||||
}
|
||||
|
||||
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 p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
||||
self.blend_pixel(pt_l, color, p.frac);
|
||||
self.blend_pixel(pt_l.onright(), color, 255 - p.frac);
|
||||
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
||||
self.blend_pixel(pt_r, color, p.frac);
|
||||
self.blend_pixel(pt_r.onleft(), color, 255 - p.frac);
|
||||
}
|
||||
}
|
||||
|
||||
let r = Rect::new(
|
||||
Point::new(center.x - radius, center.y + 1),
|
||||
Point::new(center.x + radius + 1, center.y + split + 1),
|
||||
);
|
||||
|
||||
if self.viewport().contains(r) {
|
||||
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
||||
self.blend_pixel(pt_l, color, p.frac);
|
||||
self.blend_pixel(pt_l.onright(), color, 255 - p.frac);
|
||||
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
||||
self.blend_pixel(pt_r, color, p.frac);
|
||||
self.blend_pixel(pt_r.onleft(), color, 255 - p.frac);
|
||||
}
|
||||
}
|
||||
|
||||
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 p in circle_points(radius) {
|
||||
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
||||
self.blend_pixel(pt_l, color, p.frac);
|
||||
self.blend_pixel(pt_l.above(), color, 255 - p.frac);
|
||||
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
||||
self.blend_pixel(pt_r, color, p.frac);
|
||||
self.blend_pixel(pt_r.above(), color, 255 - p.frac);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/// Draws filled circle with the specified center and the radius.
|
||||
#[cfg(not(feature = "ui_antialiasing"))]
|
||||
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
|
||||
let split = unwrap!(circle_points(radius).last()).v;
|
||||
let alpha = 255;
|
||||
|
||||
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 p in circle_points(radius) {
|
||||
if p.last {
|
||||
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
||||
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 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 p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
||||
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
let r = Rect::new(
|
||||
Point::new(center.x - radius, center.y + 1),
|
||||
Point::new(center.x + radius + 1, center.y + split + 1),
|
||||
);
|
||||
|
||||
if self.viewport().contains(r) {
|
||||
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
||||
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 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 p in circle_points(radius) {
|
||||
if p.last {
|
||||
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
||||
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
||||
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws antialiased filled circle with the specified center and the
|
||||
/// radius.
|
||||
#[cfg(feature = "ui_antialiasing")]
|
||||
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
|
||||
let split = unwrap!(circle_points(radius).last()).v;
|
||||
|
||||
let alpha = 255;
|
||||
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
||||
|
||||
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 p in circle_points(radius) {
|
||||
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
||||
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
if pt_l != pt_r {
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
}
|
||||
|
||||
if p.first {
|
||||
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(r, color, 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 p in circle_points(radius).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
||||
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
|
||||
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(r, color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
let r = Rect::new(
|
||||
Point::new(center.x - radius, center.y + 1),
|
||||
Point::new(center.x + radius + 1, center.y + split + 1),
|
||||
);
|
||||
|
||||
if self.viewport().contains(r) {
|
||||
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
||||
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
||||
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
|
||||
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(r, color, 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 p in circle_points(radius) {
|
||||
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
||||
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
||||
if pt_l != pt_r {
|
||||
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
||||
}
|
||||
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
||||
|
||||
if p.first {
|
||||
let r = Rect::new(pt_l.onright(), pt_r.under());
|
||||
self.fill_rect(r, color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills circle sector with a specified color.
|
||||
fn fill_sector(
|
||||
&mut self,
|
||||
center: Point,
|
||||
radius: i16,
|
||||
mut start: i16,
|
||||
mut end: i16,
|
||||
color: Color,
|
||||
) {
|
||||
start = (PI4 * 8 + start % (PI4 * 8)) % (PI4 * 8);
|
||||
end = (PI4 * 8 + end % (PI4 * 8)) % (PI4 * 8);
|
||||
|
||||
let alpha = 255;
|
||||
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
||||
|
||||
if start != end {
|
||||
// The algorithm fills everything except the middle point ;-)
|
||||
self.draw_pixel(center, color);
|
||||
}
|
||||
|
||||
for octant in 0..8 {
|
||||
let angle = octant * PI4;
|
||||
|
||||
// Function for calculation of 'u' coordinate inside the circle octant
|
||||
// radius * sin(angle)
|
||||
let sin = |angle: i16| -> i16 { sin_i16(angle, radius) };
|
||||
|
||||
// Calculate the octant's bounding rectangle
|
||||
let p = Point::new(sin(PI4) + 1, -radius - 1).rot(octant);
|
||||
let r = Rect::new(center, p + center.into()).normalize();
|
||||
|
||||
// Skip octant if not visible
|
||||
if !self.viewport().contains(r) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Function for filling a line between two endpoints with antialiasing.
|
||||
// The function is special for each octant using 4 different axes of symmetry
|
||||
let filler = &mut |p1: Option<Point>, p1_frac, p2: Point, p2_frac| {
|
||||
let p2: Point = center + p2.rot(octant).into();
|
||||
self.blend_pixel(p2, color, alpha_mul(p2_frac));
|
||||
if let Some(p1) = p1 {
|
||||
let p1: Point = center + p1.rot(octant).into();
|
||||
let ofs = Point::new(-1, 0).rot(octant);
|
||||
self.blend_pixel(p1 + ofs.into(), color, alpha_mul(p1_frac));
|
||||
if ofs.x + ofs.y < 0 {
|
||||
if ofs.x != 0 {
|
||||
self.fill_rect(Rect::new(p1, p2.under()), color, alpha);
|
||||
} else {
|
||||
self.fill_rect(Rect::new(p1, p2.onright()), color, alpha);
|
||||
}
|
||||
} else {
|
||||
let p1 = p1 + ofs.into();
|
||||
let p2 = p2 + ofs.into();
|
||||
if ofs.x != 0 {
|
||||
self.fill_rect(Rect::new(p2, p1.under()), color, alpha);
|
||||
} else {
|
||||
self.fill_rect(Rect::new(p2, p1.onright()), color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let corr = if octant & 1 == 0 {
|
||||
// The clockwise octant
|
||||
|angle| angle
|
||||
} else {
|
||||
// The anticlockwise octant
|
||||
|angle| PI4 - angle
|
||||
};
|
||||
|
||||
if start <= end {
|
||||
// Octant may contain 0 or 1 sector
|
||||
if start < angle + PI4 && end > angle {
|
||||
if start <= angle && end >= angle + PI4 {
|
||||
// Fill all pixels in the octant
|
||||
fill_octant(radius, 0, sin(PI4), filler);
|
||||
} else {
|
||||
// Partial fill
|
||||
let u1 = if start <= angle {
|
||||
sin(corr(0))
|
||||
} else {
|
||||
sin(corr(start - angle))
|
||||
};
|
||||
let u2 = if end <= angle + PI4 {
|
||||
sin(corr(end - angle))
|
||||
} else {
|
||||
sin(corr(PI4))
|
||||
};
|
||||
|
||||
fill_octant(radius, u1, u2, filler);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Octant may contain 0, 1 or 2 sectors
|
||||
if end >= angle + PI4 || start <= angle {
|
||||
// Fill all pixels in the octant
|
||||
fill_octant(radius, 0, sin(PI4), filler);
|
||||
} else {
|
||||
// Partial fill
|
||||
if (end > angle) && (end < angle + PI4) {
|
||||
// Fill up to `end`
|
||||
fill_octant(radius, sin(corr(0)), sin(corr(end - angle)), filler);
|
||||
}
|
||||
if start < angle + PI4 {
|
||||
// Fill all from `start`
|
||||
fill_octant(radius, sin(corr(start - angle)), sin(corr(PI4)), filler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates endpoints of a single octant of a circle
|
||||
///
|
||||
/// Used internally by `Canvas::fill_sector()`.
|
||||
fn fill_octant(
|
||||
radius: i16,
|
||||
mut u1: i16,
|
||||
mut u2: i16,
|
||||
fill: &mut impl FnMut(Option<Point>, u8, Point, u8),
|
||||
) {
|
||||
// Starting end ending points on
|
||||
if u1 > u2 {
|
||||
(u1, u2) = (u2, u1);
|
||||
}
|
||||
|
||||
let mut iter = circle_points(radius).skip(u1 as usize);
|
||||
|
||||
// Intersection of the p1 line and the circle
|
||||
let p1_start = unwrap!(iter.next());
|
||||
|
||||
// Intersection of the p1 line and the circle
|
||||
let mut p2_start = p1_start;
|
||||
|
||||
loop {
|
||||
if let Some(p) = iter.next() {
|
||||
if p.u > u2 {
|
||||
break;
|
||||
}
|
||||
p2_start = p;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Flag if we draw section up to 45degs
|
||||
let join_flag = iter.next().is_none();
|
||||
|
||||
// Process area between a p1 line and the circle
|
||||
let mut p1_iter = line_points(p1_start.v, p1_start.u, 0);
|
||||
let mut first = true;
|
||||
let mut skip = 0;
|
||||
|
||||
for c in circle_points(radius)
|
||||
.skip(p1_start.u as usize)
|
||||
.take((p2_start.u - p1_start.u) as usize)
|
||||
{
|
||||
let p2_coord = Point::new(c.u, -c.v);
|
||||
|
||||
if c.first || first {
|
||||
let p1 = unwrap!(p1_iter.next());
|
||||
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
|
||||
first = false;
|
||||
|
||||
fill(Some(p1_coord), p1.frac, p2_coord, c.frac);
|
||||
} else {
|
||||
fill(None, 0, p2_coord, c.frac);
|
||||
}
|
||||
|
||||
skip = if c.last { 0 } else { 1 };
|
||||
}
|
||||
|
||||
// Process area between a p1 and p2 lines
|
||||
let p2_iter = line_points(p2_start.v, p2_start.u, 0).skip(skip);
|
||||
for (p1, p2) in p1_iter.zip(p2_iter) {
|
||||
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
|
||||
let p2_coord = Point::new(p2_start.u - p2.v, -p2_start.v + p2.u);
|
||||
let p2_frac = if join_flag { 255 } else { 255 - p2.frac };
|
||||
fill(Some(p1_coord), p1.frac, p2_coord, p2_frac);
|
||||
}
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Normalizes the rectangle coordinates.
|
||||
///
|
||||
/// Returns a new `Rect` with potentially swapped left/right,
|
||||
/// top/bottom coordinates, ensuring that `x0`, `y0` represents
|
||||
/// the top-left corner and `x1`, `y1` represents the bottom-right corner.
|
||||
pub fn normalize(&self) -> Self {
|
||||
Rect {
|
||||
x0: core::cmp::min(self.x0, self.x1),
|
||||
y0: core::cmp::min(self.y0, self.y1),
|
||||
x1: core::cmp::max(self.x0, self.x1),
|
||||
y1: core::cmp::max(self.y0, self.y1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fn rot(self, octant: i16) -> Self {
|
||||
let mut result = self;
|
||||
|
||||
if (octant + 1) & 2 != 0 {
|
||||
result = Point::new(-result.y, -result.x);
|
||||
}
|
||||
|
||||
if octant & 4 != 0 {
|
||||
result = Point::new(-result.x, result.y);
|
||||
}
|
||||
|
||||
if (octant + 2) & 4 != 0 {
|
||||
result = Point::new(result.x, -result.y);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
mod common;
|
||||
mod mono8;
|
||||
mod rgb565;
|
||||
mod rgba8888;
|
||||
mod viewport;
|
||||
|
||||
pub use common::{BasicCanvas, Canvas};
|
||||
pub use mono8::Mono8Canvas;
|
||||
pub use rgb565::Rgb565Canvas;
|
||||
pub use rgba8888::Rgba8888Canvas;
|
||||
pub use viewport::Viewport;
|
@ -0,0 +1,100 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::{
|
||||
super::{Bitmap, BitmapFormat, BitmapView},
|
||||
BasicCanvas, Canvas, Viewport,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
use super::super::DrawingCache;
|
||||
|
||||
/// A struct representing 8-bit monochromatic canvas
|
||||
pub struct Mono8Canvas<'a> {
|
||||
bitmap: Bitmap<'a>,
|
||||
viewport: Viewport,
|
||||
}
|
||||
|
||||
impl<'a> Mono8Canvas<'a> {
|
||||
/// Creates a new canvas with the specified size and buffer.
|
||||
///
|
||||
/// Optionally minimal height can be specified and then the height
|
||||
/// of the new bitmap is adjusted to the buffer size.
|
||||
///
|
||||
/// Returns None if the buffer is not big enough.
|
||||
pub fn new(size: Offset, min_height: Option<i16>, buff: &'a mut [u8]) -> Option<Self> {
|
||||
let bitmap = Bitmap::new_mut(BitmapFormat::MONO8, None, size, min_height, buff)?;
|
||||
let viewport = Viewport::from_size(bitmap.size());
|
||||
Some(Self { bitmap, viewport })
|
||||
}
|
||||
|
||||
/// Returns the specified row as a mutable slice.
|
||||
///
|
||||
/// Returns None if row is out of range.
|
||||
pub fn row_mut(&mut self, row: i16) -> Option<&mut [u8]> {
|
||||
self.bitmap.row_mut(row)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BasicCanvas for Mono8Canvas<'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, alpha: u8) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.mono8_fill(r, self.viewport.clip, color, alpha);
|
||||
}
|
||||
|
||||
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.mono8_copy(r, self.viewport.clip, &bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Canvas for Mono8Canvas<'a> {
|
||||
fn view(&self) -> BitmapView {
|
||||
BitmapView::new(&self.bitmap)
|
||||
}
|
||||
|
||||
fn draw_pixel(&mut self, pt: Point, color: Color) {
|
||||
let pt = pt + self.viewport.origin;
|
||||
if self.viewport.clip.contains(pt) {
|
||||
if let Some(row) = self.row_mut(pt.y) {
|
||||
row[pt.x as usize] = color.luminance() as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
|
||||
let pt = pt + self.viewport.origin;
|
||||
if self.viewport.clip.contains(pt) {
|
||||
if let Some(row) = self.row_mut(pt.y) {
|
||||
let pixel = &mut row[pt.x as usize];
|
||||
let fg_color = color.luminance() as u16;
|
||||
let bg_color = *pixel as u16;
|
||||
*pixel = ((fg_color * alpha as u16 + bg_color * (255 - alpha) as u16) / 255) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.mono8_blend(r, self.viewport.clip, &src);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) {
|
||||
// Not implemented
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::{
|
||||
super::{Bitmap, BitmapFormat, BitmapView},
|
||||
BasicCanvas, Canvas, Viewport,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
use super::super::DrawingCache;
|
||||
|
||||
/// A struct representing 16-bit (RGB565) color canvas
|
||||
pub struct Rgb565Canvas<'a> {
|
||||
bitmap: Bitmap<'a>,
|
||||
viewport: Viewport,
|
||||
}
|
||||
|
||||
impl<'a> Rgb565Canvas<'a> {
|
||||
/// Creates a new canvas with the specified size and buffer.
|
||||
///
|
||||
/// Optionally minimal height can be specified and then the height
|
||||
/// of the new bitmap is adjusted to the buffer size.
|
||||
///
|
||||
/// Returns None if the buffer is not big enough.
|
||||
pub fn new(size: Offset, min_height: Option<i16>, buff: &'a mut [u8]) -> Option<Self> {
|
||||
let bitmap = Bitmap::new_mut(BitmapFormat::RGB565, None, size, min_height, buff)?;
|
||||
let viewport = Viewport::from_size(bitmap.size());
|
||||
Some(Self { bitmap, viewport })
|
||||
}
|
||||
|
||||
/// Returns the specified row as a mutable slice.
|
||||
///
|
||||
/// Returns None if row is out of range.
|
||||
pub fn row_mut(&mut self, row: i16) -> Option<&mut [u16]> {
|
||||
self.bitmap.row_mut(row)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BasicCanvas for Rgb565Canvas<'a> {
|
||||
fn size(&self) -> Offset {
|
||||
self.bitmap.size()
|
||||
}
|
||||
|
||||
fn viewport(&self) -> Viewport {
|
||||
self.viewport
|
||||
}
|
||||
|
||||
fn set_viewport(&mut self, viewport: Viewport) {
|
||||
self.viewport = viewport.absolute_clip(self.bounds());
|
||||
}
|
||||
|
||||
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.rgb565_fill(r, self.viewport.clip, color, alpha);
|
||||
}
|
||||
|
||||
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.rgb565_copy(r, self.viewport.clip, &bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Canvas for Rgb565Canvas<'a> {
|
||||
fn view(&self) -> BitmapView {
|
||||
BitmapView::new(&self.bitmap)
|
||||
}
|
||||
|
||||
fn draw_pixel(&mut self, pt: Point, color: Color) {
|
||||
let pt = pt + self.viewport.origin;
|
||||
if self.viewport.clip.contains(pt) {
|
||||
if let Some(row) = self.row_mut(pt.y) {
|
||||
row[pt.x as usize] = color.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) {
|
||||
let pt = pt + self.viewport.origin;
|
||||
if self.viewport.clip.contains(pt) {
|
||||
if let Some(row) = self.row_mut(pt.y) {
|
||||
let pixel = &mut row[pt.x as usize];
|
||||
let bg_color: Color = (*pixel).into();
|
||||
*pixel = bg_color.blend(color, alpha).into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.rgb565_blend(r, self.viewport.clip, &src);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache) {
|
||||
let clip = r.translate(self.viewport.origin).clamp(self.viewport.clip);
|
||||
|
||||
let ofs = radius as i16;
|
||||
|
||||
if clip.width() > 2 * ofs - 1 && clip.height() > 2 * ofs - 1 {
|
||||
let mut blur_cache = cache.blur();
|
||||
let (blur, _) = unwrap!(
|
||||
blur_cache.get(clip.size(), radius, None),
|
||||
"Too small blur buffer"
|
||||
);
|
||||
|
||||
loop {
|
||||
if let Some(y) = blur.push_ready() {
|
||||
let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic
|
||||
blur.push(&row[clip.x0 as usize..clip.x1 as usize]);
|
||||
}
|
||||
if let Some(y) = blur.pop_ready() {
|
||||
let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic
|
||||
blur.pop(&mut row[clip.x0 as usize..clip.x1 as usize], None);
|
||||
if y + 1 >= clip.height() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::{
|
||||
super::{Bitmap, BitmapFormat, BitmapView},
|
||||
BasicCanvas, Canvas, Viewport,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
use super::super::DrawingCache;
|
||||
|
||||
/// A struct representing 32-bit (RGBA8888) color canvas
|
||||
pub struct Rgba8888Canvas<'a> {
|
||||
bitmap: Bitmap<'a>,
|
||||
viewport: Viewport,
|
||||
}
|
||||
|
||||
impl<'a> Rgba8888Canvas<'a> {
|
||||
/// Creates a new canvas with the specified size and buffer.
|
||||
///
|
||||
/// Optionally minimal height can be specified and then the height
|
||||
/// of the new bitmap is adjusted to the buffer size.
|
||||
///
|
||||
/// Returns None if the buffer is not big enough.
|
||||
pub fn new(size: Offset, min_height: Option<i16>, buff: &'a mut [u8]) -> Option<Self> {
|
||||
let bitmap = Bitmap::new_mut(BitmapFormat::RGBA8888, None, size, min_height, buff)?;
|
||||
let viewport = Viewport::from_size(bitmap.size());
|
||||
Some(Self { bitmap, viewport })
|
||||
}
|
||||
|
||||
/// Returns the specified row as a mutable slice.
|
||||
///
|
||||
/// Returns None if row is out of range.
|
||||
pub fn row_mut(&mut self, row: i16) -> Option<&mut [u32]> {
|
||||
self.bitmap.row_mut(row)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BasicCanvas for Rgba8888Canvas<'a> {
|
||||
fn size(&self) -> Offset {
|
||||
self.bitmap.size()
|
||||
}
|
||||
|
||||
fn viewport(&self) -> Viewport {
|
||||
self.viewport
|
||||
}
|
||||
|
||||
fn set_viewport(&mut self, viewport: Viewport) {
|
||||
self.viewport = viewport.absolute_clip(self.bounds());
|
||||
}
|
||||
|
||||
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap
|
||||
.rgba8888_fill(r, self.viewport.clip, color, alpha);
|
||||
}
|
||||
|
||||
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.rgba8888_copy(r, self.viewport.clip, &bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Canvas for Rgba8888Canvas<'a> {
|
||||
fn view(&self) -> BitmapView {
|
||||
BitmapView::new(&self.bitmap)
|
||||
}
|
||||
|
||||
fn draw_pixel(&mut self, pt: Point, color: Color) {
|
||||
let pt = pt + self.viewport.origin;
|
||||
if self.viewport.clip.contains(pt) {
|
||||
if let Some(row) = self.row_mut(pt.y) {
|
||||
row[pt.x as usize] = color.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_pixel(&mut self, _pt: Point, _color: Color, _alpha: u8) {
|
||||
// TODO: not implemented yet, requires 32-bit color blending routines
|
||||
}
|
||||
|
||||
fn blend_bitmap(&mut self, r: Rect, src: BitmapView) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
self.bitmap.rgba8888_blend(r, self.viewport.clip, &src);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) {
|
||||
// TODO
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
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 clip rectangle and origin at
|
||||
/// (0,0).
|
||||
pub fn new(clip: Rect) -> Self {
|
||||
Self {
|
||||
clip,
|
||||
origin: Offset::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new viewport with specified size and origin at (0,0).
|
||||
pub fn from_size(size: Offset) -> Self {
|
||||
Self {
|
||||
clip: Rect::from_size(size),
|
||||
origin: Offset::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the viewport intersects with the specified rectangle
|
||||
/// given in relative coordinates.
|
||||
pub fn contains(&self, r: Rect) -> bool {
|
||||
!r.translate(self.origin).clamp(self.clip).is_empty()
|
||||
}
|
||||
|
||||
pub fn translate(self, offset: Offset) -> Self {
|
||||
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) -> Self {
|
||||
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) -> Self {
|
||||
Self {
|
||||
clip: r.clamp(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) -> Self {
|
||||
Self {
|
||||
clip: r.translate(self.origin).clamp(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) -> Self {
|
||||
let clip = r.translate(self.origin).clamp(self.clip);
|
||||
let origin = self.origin + (clip.top_left() - self.clip.top_left());
|
||||
Self { clip, origin }
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Point, Rect},
|
||||
};
|
||||
|
||||
use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
/// A shape for rendering various types of circles or circle sectors.
|
||||
pub struct Circle {
|
||||
center: Point,
|
||||
radius: i16,
|
||||
fg_color: Option<Color>,
|
||||
bg_color: Option<Color>,
|
||||
thickness: i16,
|
||||
start_angle: Option<i16>,
|
||||
end_angle: Option<i16>,
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn new(center: Point, radius: i16) -> Self {
|
||||
Self {
|
||||
center,
|
||||
radius,
|
||||
fg_color: None,
|
||||
bg_color: None,
|
||||
thickness: 1,
|
||||
start_angle: None,
|
||||
end_angle: None,
|
||||
}
|
||||
}
|
||||
|
||||
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, ..self }
|
||||
}
|
||||
|
||||
pub fn with_start_angle(self, from_angle: i16) -> Self {
|
||||
Self {
|
||||
start_angle: Some(from_angle),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_end_angle(self, to_angle: i16) -> Self {
|
||||
Self {
|
||||
end_angle: Some(to_angle),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape<'_> for Circle {
|
||||
fn bounds(&self, _cache: &DrawingCache) -> 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(&mut self, _cache: &DrawingCache) {}
|
||||
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) {
|
||||
// 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.start_angle.is_none() && self.end_angle.is_none() {
|
||||
if th == 1 {
|
||||
if let Some(color) = self.bg_color {
|
||||
canvas.fill_circle(self.center, self.radius, color);
|
||||
}
|
||||
if let Some(color) = self.fg_color {
|
||||
#[cfg(not(feature = "ui_antialiasing"))]
|
||||
canvas.draw_circle(self.center, self.radius, color);
|
||||
#[cfg(feature = "ui_antialiasing")]
|
||||
canvas.fill_circle(self.center, self.radius, color);
|
||||
}
|
||||
} else {
|
||||
if let Some(color) = self.fg_color {
|
||||
if th > 0 {
|
||||
canvas.fill_circle(self.center, self.radius, color);
|
||||
}
|
||||
}
|
||||
if let Some(color) = self.bg_color {
|
||||
canvas.fill_circle(self.center, self.radius - th, color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let start = self.start_angle.unwrap_or(0);
|
||||
let end = self.end_angle.unwrap_or(360);
|
||||
|
||||
if let Some(color) = self.fg_color {
|
||||
if th > 0 {
|
||||
canvas.fill_sector(self.center, self.radius, start, end, color);
|
||||
}
|
||||
}
|
||||
if let Some(color) = self.bg_color {
|
||||
canvas.fill_sector(self.center, self.radius - th, start, end, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ShapeClone<'s> for Circle {
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let clone = bump.alloc_t::<Circle>()?;
|
||||
Some(clone.uninit.init(Circle { ..self }))
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::Rect,
|
||||
shape::{DirectRenderer, Mono8Canvas},
|
||||
};
|
||||
|
||||
pub fn render_on_display<'a, F>(_clip: Option<Rect>, _bg_color: Option<Color>, _func: F)
|
||||
where
|
||||
F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>),
|
||||
{
|
||||
panic!("Not implemented")
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Rect},
|
||||
shape::{BasicCanvas, DirectRenderer, DrawingCache, Mono8Canvas, Viewport},
|
||||
};
|
||||
|
||||
use crate::trezorhal::display;
|
||||
|
||||
use static_alloc::Bump;
|
||||
|
||||
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
|
||||
where
|
||||
F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>),
|
||||
{
|
||||
static mut BUMP: Bump<[u8; 40 * 1024]> = Bump::uninit();
|
||||
|
||||
let bump = unsafe { &mut *core::ptr::addr_of_mut!(BUMP) };
|
||||
{
|
||||
let width = display::DISPLAY_RESX as i16;
|
||||
let height = display::DISPLAY_RESY as i16;
|
||||
|
||||
bump.reset();
|
||||
|
||||
let cache = DrawingCache::new(bump, bump);
|
||||
|
||||
let fb = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
display::get_frame_addr() as *mut u8,
|
||||
width as usize * height as usize * core::mem::size_of::<u8>(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut canvas = unwrap!(Mono8Canvas::new(Offset::new(width, height), None, fb));
|
||||
|
||||
if let Some(clip) = clip {
|
||||
canvas.set_viewport(Viewport::new(clip));
|
||||
}
|
||||
|
||||
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
|
||||
|
||||
func(&mut target);
|
||||
|
||||
display::refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Rect},
|
||||
shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport},
|
||||
};
|
||||
|
||||
use crate::trezorhal::display;
|
||||
|
||||
use static_alloc::Bump;
|
||||
|
||||
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
|
||||
where
|
||||
F: FnOnce(&mut DirectRenderer<'_, 'a, Rgb565Canvas<'a>>),
|
||||
{
|
||||
#[link_section = ".no_dma_buffers"]
|
||||
static mut BUMP_A: Bump<[u8; 40 * 1024]> = Bump::uninit();
|
||||
|
||||
#[link_section = ".buf"]
|
||||
static mut BUMP_B: Bump<[u8; 16 * 1024]> = Bump::uninit();
|
||||
|
||||
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
|
||||
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
|
||||
{
|
||||
let width = display::DISPLAY_RESX as i16;
|
||||
let height = display::DISPLAY_RESY as i16;
|
||||
|
||||
bump_a.reset();
|
||||
bump_b.reset();
|
||||
|
||||
let cache = DrawingCache::new(bump_a, bump_b);
|
||||
|
||||
let fb = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
display::get_frame_addr() as *mut u8,
|
||||
width as usize * height as usize * core::mem::size_of::<u16>(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut canvas = unwrap!(Rgb565Canvas::new(Offset::new(width, height), None, fb));
|
||||
|
||||
if let Some(clip) = clip {
|
||||
canvas.set_viewport(Viewport::new(clip));
|
||||
}
|
||||
|
||||
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
|
||||
|
||||
func(&mut target);
|
||||
|
||||
display::refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Rect},
|
||||
shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport},
|
||||
};
|
||||
|
||||
use static_alloc::Bump;
|
||||
|
||||
use crate::trezorhal::display;
|
||||
|
||||
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
|
||||
where
|
||||
F: FnOnce(&mut DirectRenderer<'_, 'a, Rgba8888Canvas<'a>>),
|
||||
{
|
||||
#[link_section = ".no_dma_buffers"]
|
||||
static mut BUMP_A: Bump<[u8; 40 * 1024]> = Bump::uninit();
|
||||
|
||||
#[link_section = ".buf"]
|
||||
static mut BUMP_B: Bump<[u8; 16 * 1024]> = Bump::uninit();
|
||||
|
||||
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
|
||||
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
|
||||
{
|
||||
let width = display::DISPLAY_RESX as i16;
|
||||
let height = display::DISPLAY_RESY as i16;
|
||||
|
||||
bump_a.reset();
|
||||
bump_b.reset();
|
||||
|
||||
let cache = DrawingCache::new(bump_a, bump_b);
|
||||
|
||||
let fb = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
display::get_frame_addr() as *mut u8,
|
||||
width as usize * height as usize * core::mem::size_of::<u32>(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut canvas = unwrap!(Rgba8888Canvas::new(Offset::new(width, height), None, fb));
|
||||
|
||||
if let Some(clip) = clip {
|
||||
canvas.set_viewport(Viewport::new(clip));
|
||||
}
|
||||
|
||||
let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache);
|
||||
|
||||
func(&mut target);
|
||||
|
||||
display::refresh();
|
||||
}
|
||||
}
|
@ -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,24 @@
|
||||
#[cfg(all(feature = "xframebuffer", feature = "display_mono"))]
|
||||
pub mod fb_mono8;
|
||||
#[cfg(all(feature = "xframebuffer", feature = "display_mono"))]
|
||||
pub use fb_mono8::render_on_display;
|
||||
|
||||
#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))]
|
||||
pub mod nofb_rgb565;
|
||||
#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))]
|
||||
pub use nofb_rgb565::render_on_display;
|
||||
|
||||
#[cfg(all(feature = "xframebuffer", feature = "display_rgb565"))]
|
||||
pub mod fb_rgb565;
|
||||
#[cfg(all(feature = "xframebuffer", feature = "display_rgb565"))]
|
||||
pub use fb_rgb565::render_on_display;
|
||||
|
||||
#[cfg(all(feature = "xframebuffer", feature = "display_rgba8888"))]
|
||||
pub mod fb_rgba8888;
|
||||
#[cfg(all(feature = "xframebuffer", feature = "display_rgba8888"))]
|
||||
pub use fb_rgba8888::render_on_display;
|
||||
|
||||
#[cfg(not(feature = "new_rendering"))]
|
||||
pub mod fake_display;
|
||||
#[cfg(not(feature = "new_rendering"))]
|
||||
pub use fake_display::render_on_display;
|
@ -0,0 +1,88 @@
|
||||
use crate::trezorhal::dma2d_new::Dma2d;
|
||||
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Rect},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
BasicCanvas, BitmapFormat, BitmapView, DrawingCache, ProgressiveRenderer, Viewport,
|
||||
};
|
||||
|
||||
use static_alloc::Bump;
|
||||
|
||||
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
|
||||
where
|
||||
F: FnOnce(&mut ProgressiveRenderer<'_, 'a, Bump<[u8; 40 * 1024]>, DisplayCanvas>),
|
||||
{
|
||||
#[link_section = ".no_dma_buffers"]
|
||||
static mut BUMP_A: Bump<[u8; 40 * 1024]> = Bump::uninit();
|
||||
|
||||
#[link_section = ".buf"]
|
||||
static mut BUMP_B: Bump<[u8; 16 * 1024]> = Bump::uninit();
|
||||
|
||||
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
|
||||
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
|
||||
{
|
||||
bump_a.reset();
|
||||
bump_b.reset();
|
||||
|
||||
let cache = DrawingCache::new(bump_a, bump_b);
|
||||
let mut canvas = DisplayCanvas::acquire().unwrap();
|
||||
|
||||
if let Some(clip) = clip {
|
||||
canvas.set_viewport(Viewport::new(clip));
|
||||
}
|
||||
|
||||
let mut target = ProgressiveRenderer::new(&mut canvas, bg_color, &cache, bump_a, 45);
|
||||
|
||||
func(&mut target);
|
||||
|
||||
target.render(16);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplayCanvas {
|
||||
size: Offset,
|
||||
viewport: Viewport,
|
||||
}
|
||||
|
||||
impl DisplayCanvas {
|
||||
pub fn acquire() -> Option<Self> {
|
||||
let size = Offset::new(240, 240); // TODO
|
||||
let viewport = Viewport::from_size(size);
|
||||
Some(Self { size, viewport })
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicCanvas for DisplayCanvas {
|
||||
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.size
|
||||
}
|
||||
|
||||
fn fill_rect(&mut self, r: Rect, color: Color, _alpha: u8) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
if let Some(dma2d) = Dma2d::new_fill(r, self.viewport.clip, color, 255) {
|
||||
unsafe { dma2d.display_fill() };
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
if let Some(dma2d) = Dma2d::new_copy(r, self.viewport.clip, &bitmap) {
|
||||
match bitmap.format() {
|
||||
BitmapFormat::RGB565 => unsafe { dma2d.display_copy_rgb565() },
|
||||
_ => panic!("Unsupported DMA operation"),
|
||||
}
|
||||
bitmap.bitmap.mark_dma_pending();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
use crate::ui::geometry::{Alignment2D, Offset, Point, Rect};
|
||||
|
||||
use super::{Bitmap, BitmapFormat, BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
/// A shape for rendering compressed JPEG images.
|
||||
pub struct JpegImage<'a> {
|
||||
/// Image position
|
||||
pos: Point,
|
||||
// Image position alignment
|
||||
align: Alignment2D,
|
||||
/// JPEG data
|
||||
jpeg: &'a [u8],
|
||||
/// Scale factor (default 0)
|
||||
scale: u8,
|
||||
/// Blurring radius or 0 if no blurring required (default 0)
|
||||
blur_radius: usize,
|
||||
/// Dimming of blurred image in range of 0..255 (default 255)
|
||||
dim: u8,
|
||||
/// Set if blurring is pending
|
||||
/// (used only during image drawing).
|
||||
blur_tag: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> JpegImage<'a> {
|
||||
pub fn new(pos: Point, jpeg: &'a [u8]) -> Self {
|
||||
JpegImage {
|
||||
pos,
|
||||
align: Alignment2D::TOP_LEFT,
|
||||
scale: 0,
|
||||
dim: 255,
|
||||
blur_radius: 0,
|
||||
jpeg,
|
||||
blur_tag: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_align(self, align: Alignment2D) -> Self {
|
||||
Self { align, ..self }
|
||||
}
|
||||
|
||||
pub fn with_scale(self, scale: u8) -> Self {
|
||||
assert!(scale <= 3);
|
||||
Self { scale, ..self }
|
||||
}
|
||||
|
||||
pub fn with_blur(self, blur_radius: usize) -> Self {
|
||||
Self {
|
||||
blur_radius,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_dim(self, dim: u8) -> Self {
|
||||
Self { dim, ..self }
|
||||
}
|
||||
|
||||
pub fn render(self, renderer: &mut impl Renderer<'a>) {
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Shape<'a> for JpegImage<'a> {
|
||||
fn bounds(&self, cache: &DrawingCache<'a>) -> Rect {
|
||||
let size = unwrap!(cache.jpeg().get_size(self.jpeg, self.scale), "Invalid JPEG");
|
||||
Rect::from_top_left_and_size(size.snap(self.pos, self.align), size)
|
||||
}
|
||||
|
||||
fn cleanup(&mut self, _cache: &DrawingCache<'a>) {
|
||||
self.blur_tag = None;
|
||||
}
|
||||
|
||||
/*
|
||||
// Faster implementation suitable for DirectRenderer without blurring support
|
||||
// (but is terribly slow on ProgressiveRenderer if slices are not aligned
|
||||
// to JPEG MCUs )
|
||||
fn draw(&mut self, canvas: &mut dyn RgbCanvasEx, cache: &DrawingCache<'a>) {
|
||||
let bounds = self.bounds(cache);
|
||||
let clip = canvas.viewport().relative_clip(bounds).clip;
|
||||
|
||||
// translate clip to JPEG relative coordinates
|
||||
let clip = clip.translate(-canvas.viewport().origin);
|
||||
let clip = clip.translate((-bounds.top_left()).into());
|
||||
|
||||
unwrap!(
|
||||
cache.jpeg().decompress_mcu(
|
||||
self.jpeg,
|
||||
self.scale,
|
||||
clip.top_left(),
|
||||
&mut |mcu_r, mcu_bitmap| {
|
||||
// Draw single MCU
|
||||
canvas.draw_bitmap(mcu_r.translate(bounds.top_left().into()), mcu_bitmap);
|
||||
// Return true if we are not done yet
|
||||
mcu_r.x1 < clip.x1 || mcu_r.y1 < clip.y1
|
||||
}
|
||||
),
|
||||
"Invalid JPEG"
|
||||
);
|
||||
}*/
|
||||
|
||||
// This is a little bit slower implementation suitable for ProgressiveRenderer
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||
let bounds = self.bounds(cache);
|
||||
let clip = canvas.viewport().relative_clip(bounds).clip;
|
||||
|
||||
// Translate clip to JPEG relative coordinates
|
||||
let clip = clip.translate(-canvas.viewport().origin);
|
||||
let clip = clip.translate((-bounds.top_left()).into());
|
||||
|
||||
if self.blur_radius == 0 {
|
||||
// Draw JPEG without blurring
|
||||
|
||||
// Routine for drawing single JPEG MCU
|
||||
let draw_mcu = &mut |row_r: Rect, row_bitmap: BitmapView| {
|
||||
// Draw a row of decoded MCUs
|
||||
canvas.draw_bitmap(row_r.translate(bounds.top_left().into()), row_bitmap);
|
||||
// Return true if we are not done yet
|
||||
row_r.y1 < clip.y1
|
||||
};
|
||||
|
||||
unwrap!(
|
||||
cache
|
||||
.jpeg()
|
||||
.decompress_row(self.jpeg, self.scale, clip.y0, draw_mcu),
|
||||
"Invalid JPEG"
|
||||
);
|
||||
} else {
|
||||
// Draw JPEG with blurring effect
|
||||
let jpeg_size = self.bounds(cache).size();
|
||||
|
||||
// Get a single line working bitmap
|
||||
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
|
||||
let mut slice = unwrap!(
|
||||
Bitmap::new(
|
||||
BitmapFormat::RGB565,
|
||||
None,
|
||||
Offset::new(jpeg_size.x, 1),
|
||||
None,
|
||||
&mut buff[..]
|
||||
),
|
||||
"Too small buffer"
|
||||
);
|
||||
|
||||
// Get the blurring algorithm instance
|
||||
let mut blur_cache = cache.blur();
|
||||
let (blur, blur_tag) =
|
||||
unwrap!(blur_cache.get(jpeg_size, self.blur_radius, self.blur_tag));
|
||||
self.blur_tag = Some(blur_tag);
|
||||
|
||||
if let Some(y) = blur.push_ready() {
|
||||
// A function for drawing a row of JPEG MCUs
|
||||
let draw_row = &mut |row_r: Rect, jpeg_slice: BitmapView| {
|
||||
loop {
|
||||
if let Some(y) = blur.push_ready() {
|
||||
if y < row_r.y1 {
|
||||
// should never fail
|
||||
blur.push(unwrap!(jpeg_slice.row(y - row_r.y0)));
|
||||
} else {
|
||||
return true; // need more data
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(y) = blur.pop_ready() {
|
||||
blur.pop(unwrap!(slice.row_mut(0)), Some(self.dim)); // should never fail
|
||||
let dst_r = Rect::from_top_left_and_size(bounds.top_left(), jpeg_size)
|
||||
.translate(Offset::new(0, y));
|
||||
canvas.draw_bitmap(dst_r, slice.view());
|
||||
|
||||
if y + 1 >= clip.y1 {
|
||||
return false; // we are done
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unwrap!(
|
||||
cache
|
||||
.jpeg()
|
||||
.decompress_row(self.jpeg, self.scale, y, draw_row),
|
||||
"Invalid JPEG"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ShapeClone<'a> for JpegImage<'a> {
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'a>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let clone = bump.alloc_t::<JpegImage>()?;
|
||||
Some(clone.uninit.init(JpegImage { ..self }))
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
mod algo;
|
||||
mod bar;
|
||||
mod base;
|
||||
mod bitmap;
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
mod blur;
|
||||
mod cache;
|
||||
mod canvas;
|
||||
mod circle;
|
||||
mod display;
|
||||
#[cfg(feature = "ui_jpeg_decoder")]
|
||||
mod jpeg;
|
||||
mod qrcode;
|
||||
mod render;
|
||||
mod text;
|
||||
mod toif;
|
||||
|
||||
pub use algo::PI4;
|
||||
pub use bar::Bar;
|
||||
pub use base::{Shape, ShapeClone};
|
||||
pub use bitmap::{Bitmap, BitmapFormat, BitmapView};
|
||||
#[cfg(feature = "ui_blurring")]
|
||||
pub use blur::Blurring;
|
||||
pub use cache::drawing_cache::DrawingCache;
|
||||
pub use canvas::{BasicCanvas, Canvas, Mono8Canvas, Rgb565Canvas, Rgba8888Canvas, Viewport};
|
||||
pub use circle::Circle;
|
||||
pub use display::render_on_display;
|
||||
#[cfg(feature = "ui_jpeg_decoder")]
|
||||
pub use jpeg::JpegImage;
|
||||
pub use qrcode::QrImage;
|
||||
pub use render::{DirectRenderer, ProgressiveRenderer, Renderer};
|
||||
pub use text::Text;
|
||||
pub use toif::ToifImage;
|
@ -0,0 +1,169 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Rect},
|
||||
};
|
||||
|
||||
use qrcodegen::QrCode;
|
||||
|
||||
use super::{
|
||||
algo::line_points, Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone,
|
||||
};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
const MAX_QRCODE_BYTES: usize = 400;
|
||||
|
||||
/// A shape for `QrCode` rendering.
|
||||
pub struct QrImage {
|
||||
/// Destination rectangle
|
||||
area: Rect,
|
||||
/// QR code bitmap
|
||||
qr_modules: [u8; MAX_QRCODE_BYTES],
|
||||
/// Size of QR code bitmap in bytes
|
||||
qr_size: i16,
|
||||
/// Foreground color
|
||||
fg_color: Color,
|
||||
/// Optional background color
|
||||
bg_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl QrImage {
|
||||
pub fn new(area: Rect, qrcode: &QrCode) -> Self {
|
||||
if area.width() < qrcode.size() as i16 || area.height() < qrcode.size() as i16 {
|
||||
panic!("Too small area");
|
||||
}
|
||||
|
||||
let mut result = QrImage {
|
||||
area,
|
||||
qr_size: qrcode.size() as i16,
|
||||
qr_modules: [0u8; MAX_QRCODE_BYTES],
|
||||
fg_color: Color::white(),
|
||||
bg_color: None,
|
||||
};
|
||||
|
||||
// Copy content of QR code to the qrmodules buffer
|
||||
for y in 0..result.qr_size {
|
||||
for x in 0..result.qr_size {
|
||||
result.set_module(x, y, qrcode.get_module(x as i32, y as i32));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn set_module(&mut self, x: i16, y: i16, value: bool) {
|
||||
// Every row starts at byte aligned address
|
||||
let row_offset = (y * (self.qr_size + 7) / 8) as usize;
|
||||
let row = &mut self.qr_modules[row_offset..];
|
||||
let col_offset = (x / 8) as usize;
|
||||
let col_bit = 1 << (x & 0x7);
|
||||
if value {
|
||||
row[col_offset] |= col_bit;
|
||||
} else {
|
||||
row[col_offset] &= col_bit ^ 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_module(&self, x: i16, y: i16) -> bool {
|
||||
// Every row starts at byte aligned address
|
||||
let row_offset = (y * (self.qr_size + 7) / 8) as usize;
|
||||
let row = &self.qr_modules[row_offset..];
|
||||
let col_offset = (x / 8) as usize;
|
||||
let col_bit = 1 << (x & 0x7);
|
||||
(row[col_offset] & col_bit) != 0
|
||||
}
|
||||
|
||||
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<'s>(self, renderer: &mut impl Renderer<'s>) {
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
|
||||
fn draw_row(&self, slice_row: &mut [u8], qr_y: i16) {
|
||||
slice_row.iter_mut().for_each(|b| *b = 0);
|
||||
|
||||
let mut qr_module = false;
|
||||
|
||||
for p in line_points(self.area.x1 - self.area.x0, self.qr_size, 0) {
|
||||
if p.first {
|
||||
qr_module = self.get_module(p.v, qr_y);
|
||||
}
|
||||
if !qr_module {
|
||||
if p.u & 0x01 == 0 {
|
||||
slice_row[(p.u / 2) as usize] |= 0x0F;
|
||||
} else {
|
||||
slice_row[(p.u / 2) as usize] |= 0xF0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape<'_> for QrImage {
|
||||
fn bounds(&self, _cache: &DrawingCache) -> Rect {
|
||||
self.area
|
||||
}
|
||||
|
||||
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
|
||||
let buff = &mut unwrap!(cache.image_buff(), "No TOIF buffer");
|
||||
|
||||
let mut slice = unwrap!(
|
||||
Bitmap::new_mut(
|
||||
BitmapFormat::MONO4,
|
||||
None,
|
||||
Offset::new(self.area.width(), 1),
|
||||
Some(1),
|
||||
&mut buff[..]
|
||||
),
|
||||
"Too small buffer"
|
||||
);
|
||||
|
||||
let clip = canvas.viewport().relative_clip(self.bounds(cache)).clip;
|
||||
|
||||
// translate clip to the relative coordinates
|
||||
let clip = clip.translate(-canvas.viewport().origin);
|
||||
let clip = clip.translate((-self.area.top_left()).into());
|
||||
|
||||
for p in line_points(self.area.y1 - self.area.y0, self.qr_size, clip.y0)
|
||||
.take(clip.height() as usize)
|
||||
{
|
||||
if p.first {
|
||||
self.draw_row(slice.row_mut(0).unwrap(), p.v);
|
||||
}
|
||||
|
||||
let r = Rect {
|
||||
y0: self.area.y0 + p.u,
|
||||
y1: self.area.y0 + p.u + 1,
|
||||
..self.area
|
||||
};
|
||||
|
||||
let slice_view = slice.view().with_fg(self.fg_color);
|
||||
|
||||
match self.bg_color {
|
||||
Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)),
|
||||
None => canvas.blend_bitmap(r, slice_view),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ShapeClone<'s> for QrImage {
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let clone = bump.alloc_t::<QrImage>()?;
|
||||
Some(clone.uninit.init(QrImage { ..self }))
|
||||
}
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::{Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::{BasicCanvas, Canvas, DrawingCache, Rgb565Canvas, Shape, ShapeClone, Viewport};
|
||||
|
||||
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<'a> + ShapeClone<'a>;
|
||||
|
||||
fn in_window(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) {
|
||||
let original = self.set_window(r);
|
||||
inner(self);
|
||||
self.set_viewport(original);
|
||||
}
|
||||
|
||||
fn in_clip(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) {
|
||||
let original = self.set_clip(r);
|
||||
inner(self);
|
||||
self.set_viewport(original);
|
||||
}
|
||||
|
||||
fn with_origin(&mut self, origin: Offset, inner: &dyn Fn(&mut Self)) {
|
||||
let original = self.viewport();
|
||||
self.set_viewport(self.viewport().with_origin(origin));
|
||||
inner(self);
|
||||
self.set_viewport(original);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// struct DirectRenderer
|
||||
// ==========================================================================
|
||||
|
||||
/// A simple implementation of a Renderer that draws directly onto the CanvasEx
|
||||
pub struct DirectRenderer<'a, 'alloc, C>
|
||||
where
|
||||
C: Canvas,
|
||||
{
|
||||
/// Target canvas
|
||||
canvas: &'a mut C,
|
||||
/// Drawing cache (decompression context, scratch-pad memory)
|
||||
cache: &'a DrawingCache<'alloc>,
|
||||
}
|
||||
|
||||
impl<'a, 'alloc, C> DirectRenderer<'a, 'alloc, C>
|
||||
where
|
||||
C: Canvas,
|
||||
{
|
||||
/// Creates a new DirectRenderer instance with the given canvas
|
||||
pub fn new(
|
||||
canvas: &'a mut C,
|
||||
bg_color: Option<Color>,
|
||||
cache: &'a DrawingCache<'alloc>,
|
||||
) -> Self {
|
||||
if let Some(color) = bg_color {
|
||||
canvas.fill_background(color);
|
||||
}
|
||||
|
||||
// TODO: consider storing original canvas.viewport
|
||||
// and restoring it by drop() function
|
||||
|
||||
Self { canvas, cache }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'alloc, C> Renderer<'alloc> for DirectRenderer<'a, 'alloc, C>
|
||||
where
|
||||
C: Canvas,
|
||||
{
|
||||
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, mut shape: S)
|
||||
where
|
||||
S: Shape<'alloc> + ShapeClone<'alloc>,
|
||||
{
|
||||
if self.canvas.viewport().contains(shape.bounds(self.cache)) {
|
||||
shape.draw(self.canvas, self.cache);
|
||||
shape.cleanup(self.cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// struct ProgressiveRenderer
|
||||
// ==========================================================================
|
||||
|
||||
struct ShapeHolder<'a> {
|
||||
shape: &'a mut dyn Shape<'a>,
|
||||
viewport: Viewport,
|
||||
}
|
||||
|
||||
/// A more advanced Renderer implementation that supports deferred rendering.
|
||||
pub struct ProgressiveRenderer<'a, 'alloc, T, C>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
C: BasicCanvas,
|
||||
{
|
||||
/// Target canvas
|
||||
canvas: &'a mut C,
|
||||
/// Bump for cloning shapes
|
||||
bump: &'alloc T,
|
||||
/// List of rendered shapes
|
||||
shapes: FixedVec<'alloc, ShapeHolder<'alloc>>,
|
||||
/// Current viewport
|
||||
viewport: Viewport,
|
||||
// Default background color
|
||||
bg_color: Option<Color>,
|
||||
/// Drawing cache (decompression context, scratch-pad memory)
|
||||
cache: &'a DrawingCache<'alloc>,
|
||||
}
|
||||
|
||||
impl<'a, 'alloc, T, C> ProgressiveRenderer<'a, 'alloc, T, C>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
C: BasicCanvas,
|
||||
{
|
||||
/// Creates a new ProgressiveRenderer instance
|
||||
pub fn new(
|
||||
canvas: &'a mut C,
|
||||
bg_color: Option<Color>,
|
||||
cache: &'a DrawingCache<'alloc>,
|
||||
bump: &'alloc T,
|
||||
max_shapes: usize,
|
||||
) -> Self {
|
||||
let viewport = canvas.viewport();
|
||||
Self {
|
||||
canvas,
|
||||
bump,
|
||||
shapes: unwrap!(bump.fixed_vec(max_shapes), "No shape memory"),
|
||||
viewport,
|
||||
bg_color,
|
||||
cache,
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders 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 buff = &mut unwrap!(self.cache.render_buff(), "No render buffer");
|
||||
|
||||
let mut slice = unwrap!(
|
||||
Rgb565Canvas::new(
|
||||
Offset::new(canvas_clip.width(), lines as i16),
|
||||
Some(1),
|
||||
&mut buff[..],
|
||||
),
|
||||
"No render memory"
|
||||
);
|
||||
|
||||
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_mut() {
|
||||
let shape_viewport = holder.viewport.absolute_clip(slice_r);
|
||||
let shape_bounds = holder.shape.bounds(self.cache);
|
||||
|
||||
// Is the shape overlapping the current slice?
|
||||
if shape_viewport.contains(shape_bounds) {
|
||||
slice.set_viewport(shape_viewport.translate((-slice_r.top_left()).into()));
|
||||
holder.shape.draw(&mut slice, self.cache);
|
||||
|
||||
if shape_bounds.y1 + shape_viewport.origin.y <= shape_viewport.clip.y1 {
|
||||
// The shape will never be drawn again
|
||||
holder.shape.cleanup(self.cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.canvas.draw_bitmap(slice_r, slice.view());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'alloc, T, C> Renderer<'alloc> for ProgressiveRenderer<'a, 'alloc, T, C>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
C: BasicCanvas,
|
||||
{
|
||||
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<'alloc> + ShapeClone<'alloc>,
|
||||
{
|
||||
// Is the shape visible?
|
||||
if self.viewport.contains(shape.bounds(self.cache)) {
|
||||
// Clone the shape & push it to the list
|
||||
let holder = ShapeHolder {
|
||||
shape: unwrap!(shape.clone_at_bump(self.bump), "No shape memory"),
|
||||
viewport: self.viewport,
|
||||
};
|
||||
unwrap!(self.shapes.push(holder), "Shape list full");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
use crate::ui::{
|
||||
display::{Color, Font},
|
||||
geometry::{Alignment, Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::{BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
/// A shape for text strings rendering.
|
||||
pub struct Text<'a> {
|
||||
// Text position
|
||||
pos: Point,
|
||||
// Text string
|
||||
text: &'a str,
|
||||
// Text color
|
||||
color: Color,
|
||||
// Text font
|
||||
font: Font,
|
||||
// Horizontal alignment
|
||||
align: Alignment,
|
||||
// Final bounds calculated when rendered
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl<'a> Text<'a> {
|
||||
/// Creates a `shape::Text` structure with a specified
|
||||
/// text (`str`) and the top-left corner (`pos`).
|
||||
pub fn new(pos: Point, text: &'a str) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
text,
|
||||
color: Color::white(),
|
||||
font: Font::NORMAL,
|
||||
align: Alignment::Start,
|
||||
bounds: Rect::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_fg(self, color: Color) -> Self {
|
||||
Self { color, ..self }
|
||||
}
|
||||
|
||||
pub fn with_font(self, font: Font) -> Self {
|
||||
Self { font, ..self }
|
||||
}
|
||||
|
||||
pub fn with_align(self, align: Alignment) -> Self {
|
||||
Self { align, ..self }
|
||||
}
|
||||
|
||||
pub fn render<'r>(mut self, renderer: &mut impl Renderer<'r>) {
|
||||
self.bounds = self.calc_bounds();
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
|
||||
fn aligned_pos(&self) -> Point {
|
||||
match self.align {
|
||||
Alignment::Start => self.pos,
|
||||
Alignment::Center => Point::new(
|
||||
self.font.horz_center(self.pos.x, self.pos.x, self.text),
|
||||
self.pos.y,
|
||||
),
|
||||
Alignment::End => Point::new(self.pos.x - self.font.text_width(self.text), self.pos.y),
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_bounds(&self) -> Rect {
|
||||
let pos = self.aligned_pos();
|
||||
let (ascent, descent) = self.font.visible_text_height_ex(self.text);
|
||||
Rect {
|
||||
x0: pos.x,
|
||||
y0: pos.y - ascent,
|
||||
x1: pos.x + self.font.text_width(self.text),
|
||||
y1: pos.y + descent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Shape<'_> for Text<'a> {
|
||||
fn bounds(&self, _cache: &DrawingCache) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn cleanup(&mut self, _cache: &DrawingCache) {}
|
||||
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
|
||||
let mut r = self.bounds(cache);
|
||||
let max_ascent = self.pos.y - r.y0;
|
||||
|
||||
// TODO: optimize text clipping, use canvas.viewport()
|
||||
|
||||
for ch in self.text.chars() {
|
||||
if r.x0 >= r.x1 {
|
||||
break;
|
||||
}
|
||||
|
||||
let glyph = self.font.get_glyph(ch);
|
||||
let glyph_bitmap = glyph.bitmap();
|
||||
let glyph_view = BitmapView::new(&glyph_bitmap)
|
||||
.with_fg(self.color)
|
||||
.with_offset(Offset::new(
|
||||
-glyph.bearing_x,
|
||||
-(max_ascent - glyph.bearing_y),
|
||||
));
|
||||
|
||||
canvas.blend_bitmap(r, glyph_view);
|
||||
r.x0 += glyph.adv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 's> ShapeClone<'s> for Text<'a> {
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let clone = bump.alloc_t::<Text>()?;
|
||||
let text = bump.copy_str(self.text)?;
|
||||
Some(clone.uninit.init(Text { text, ..self }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Font {
|
||||
fn visible_text_height_ex(&self, text: &str) -> (i16, i16) {
|
||||
let (mut ascent, mut descent) = (0, 0);
|
||||
for c in text.chars() {
|
||||
let glyph = self.get_glyph(c);
|
||||
ascent = ascent.max(glyph.bearing_y);
|
||||
descent = descent.max(glyph.height - glyph.bearing_y);
|
||||
}
|
||||
(ascent, descent)
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
use crate::ui::{
|
||||
display::{toif::Toif, Color},
|
||||
geometry::{Alignment2D, Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::{Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
/// A shape for rendering compressed TOIF images.
|
||||
pub struct ToifImage<'a> {
|
||||
/// Image position
|
||||
pos: Point,
|
||||
// Image position alignment
|
||||
align: Alignment2D,
|
||||
// Image data
|
||||
toif: Toif<'a>,
|
||||
// Foreground color
|
||||
fg_color: Color,
|
||||
// Optional background color
|
||||
bg_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl<'a> ToifImage<'a> {
|
||||
pub fn new(pos: Point, toif: Toif<'a>) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
align: Alignment2D::TOP_LEFT,
|
||||
toif,
|
||||
fg_color: Color::white(),
|
||||
bg_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_align(self, align: Alignment2D) -> Self {
|
||||
Self { align, ..self }
|
||||
}
|
||||
|
||||
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(self, renderer: &mut impl Renderer<'a>) {
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
|
||||
fn draw_grayscale(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||
// TODO: introduce new viewport/shape function for this calculation
|
||||
let bounds = self.bounds(cache);
|
||||
let viewport = canvas.viewport();
|
||||
let mut clip = self
|
||||
.bounds(cache)
|
||||
.clamp(viewport.clip.translate(-viewport.origin))
|
||||
.translate((-bounds.top_left()).into());
|
||||
|
||||
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
|
||||
let mut slice = unwrap!(
|
||||
Bitmap::new_mut(
|
||||
BitmapFormat::MONO4,
|
||||
None,
|
||||
self.toif.size(),
|
||||
Some(1),
|
||||
&mut buff[..]
|
||||
),
|
||||
"Too small buffer"
|
||||
);
|
||||
|
||||
while !clip.is_empty() {
|
||||
let height = core::cmp::min(slice.height(), clip.height());
|
||||
unwrap!(
|
||||
cache.zlib().uncompress_toif(
|
||||
self.toif,
|
||||
clip.y0,
|
||||
unwrap!(slice.rows_mut(0, height)), // should never fail
|
||||
),
|
||||
"Invalid TOIF"
|
||||
);
|
||||
|
||||
let r = clip.translate(bounds.top_left().into());
|
||||
|
||||
let slice_view = slice
|
||||
.view()
|
||||
.with_fg(self.fg_color)
|
||||
.with_offset(Offset::new(r.x0 - bounds.top_left().x, 0));
|
||||
|
||||
match self.bg_color {
|
||||
Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)),
|
||||
None => canvas.blend_bitmap(r, slice_view),
|
||||
}
|
||||
|
||||
clip.y0 += height;
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_rgb(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||
// TODO: introduce new viewport/shape function for this calculation
|
||||
let bounds = self.bounds(cache);
|
||||
let viewport = canvas.viewport();
|
||||
let mut clip = self
|
||||
.bounds(cache)
|
||||
.clamp(viewport.clip.translate(-viewport.origin))
|
||||
.translate((-bounds.top_left()).into());
|
||||
|
||||
let buff = &mut unwrap!(cache.image_buff(), "No image buffer");
|
||||
let mut slice = unwrap!(
|
||||
Bitmap::new_mut(
|
||||
BitmapFormat::RGB565,
|
||||
None,
|
||||
self.toif.size(),
|
||||
Some(1),
|
||||
&mut buff[..]
|
||||
),
|
||||
"Too small buffer"
|
||||
);
|
||||
|
||||
while !clip.is_empty() {
|
||||
let height = core::cmp::min(slice.height(), clip.height());
|
||||
|
||||
if let Some(row_bytes) = slice.rows_mut(0, height) {
|
||||
// always true
|
||||
unwrap!(
|
||||
cache.zlib().uncompress_toif(self.toif, clip.y0, row_bytes,),
|
||||
"Invalid TOIF"
|
||||
);
|
||||
}
|
||||
|
||||
let r = clip.translate(bounds.top_left().into());
|
||||
|
||||
let slice_view = slice
|
||||
.view()
|
||||
.with_offset(Offset::new(r.x0 - bounds.top_left().x, 0));
|
||||
|
||||
canvas.draw_bitmap(r, slice_view);
|
||||
|
||||
clip.y0 += height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Shape<'a> for ToifImage<'a> {
|
||||
fn bounds(&self, _cache: &DrawingCache<'a>) -> Rect {
|
||||
let size = Offset::new(self.toif.width(), self.toif.height());
|
||||
Rect::from_top_left_and_size(size.snap(self.pos, self.align), size)
|
||||
}
|
||||
|
||||
fn cleanup(&mut self, _cache: &DrawingCache<'a>) {
|
||||
// TODO: inform the cache that we won't use the zlib slot anymore
|
||||
}
|
||||
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||
if self.toif.is_grayscale() {
|
||||
self.draw_grayscale(canvas, cache);
|
||||
} else {
|
||||
self.draw_rgb(canvas, cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ShapeClone<'a> for ToifImage<'a> {
|
||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'a>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'alloc>,
|
||||
{
|
||||
let clone = bump.alloc_t::<ToifImage>()?;
|
||||
Some(clone.uninit.init(ToifImage { ..self }))
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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 <string.h>
|
||||
|
||||
#include <xdisplay.h>
|
||||
|
||||
#include "display_fb.h"
|
||||
#include "display_io.h"
|
||||
#include "display_panel.h"
|
||||
|
||||
#include "backlight_pwm.h"
|
||||
|
||||
#include "supervise.h"
|
||||
|
||||
#ifndef BOARDLOADER
|
||||
#include "bg_copy.h"
|
||||
#endif
|
||||
|
||||
#if (DISPLAY_RESX != 240) || (DISPLAY_RESY != 240)
|
||||
#error "Incompatible display resolution"
|
||||
#endif
|
||||
|
||||
// Display driver context.
|
||||
typedef struct {
|
||||
// Current display orientation (0, 90, 180, 270)
|
||||
int orientation_angle;
|
||||
} display_driver_t;
|
||||
|
||||
// Display driver instance
|
||||
static display_driver_t g_display_driver;
|
||||
|
||||
void display_init(void) {
|
||||
display_driver_t* drv = &g_display_driver;
|
||||
memset(drv, 0, sizeof(display_driver_t));
|
||||
|
||||
display_io_init_gpio();
|
||||
display_io_init_fmc();
|
||||
display_panel_init();
|
||||
display_panel_set_little_endian();
|
||||
backlight_pwm_init();
|
||||
|
||||
#ifdef XFRAMEBUFFER
|
||||
display_io_init_te_interrupt();
|
||||
#endif
|
||||
}
|
||||
|
||||
void display_reinit(void) {
|
||||
display_driver_t* drv = &g_display_driver;
|
||||
memset(drv, 0, sizeof(display_driver_t));
|
||||
|
||||
// Reinitialize FMC to set correct timing
|
||||
// We have to do this in reinit because boardloader is fixed.
|
||||
display_io_init_fmc();
|
||||
|
||||
// Important for model T as this is not set in boardloader
|
||||
display_panel_set_little_endian();
|
||||
display_panel_init_gamma();
|
||||
backlight_pwm_reinit();
|
||||
|
||||
#ifdef XFRAMEBUFFER
|
||||
display_io_init_te_interrupt();
|
||||
#endif
|
||||
}
|
||||
|
||||
void display_finish_actions(void) {
|
||||
// !@# disable interrupt
|
||||
// !@# wait for dma ops
|
||||
}
|
||||
|
||||
int display_set_backlight(int level) {
|
||||
#ifdef XFRAMEBUFFER
|
||||
#ifndef BOARDLOADER
|
||||
// wait for DMA transfer to finish before changing backlight
|
||||
// so that we know that panel has current data
|
||||
if (backlight_pwm_get() != level && !is_mode_handler()) {
|
||||
bg_copy_wait();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return backlight_pwm_set(level);
|
||||
}
|
||||
|
||||
int display_get_backlight(void) { return backlight_pwm_get(); }
|
||||
|
||||
int display_set_orientation(int angle) {
|
||||
display_driver_t* drv = &g_display_driver;
|
||||
|
||||
if (angle != drv->orientation_angle) {
|
||||
if (angle == 0 || angle == 90 || angle == 180 || angle == 270) {
|
||||
drv->orientation_angle = angle;
|
||||
|
||||
#ifdef XFRAMEBUFFER
|
||||
memset(physical_frame_buffer_0, 0, sizeof(physical_frame_buffer_0));
|
||||
memset(physical_frame_buffer_1, 0, sizeof(physical_frame_buffer_1));
|
||||
#endif
|
||||
|
||||
display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
|
||||
for (uint32_t i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
|
||||
// 2 bytes per pixel because we're using RGB 5-6-5 format
|
||||
ISSUE_PIXEL_DATA(0x0000);
|
||||
}
|
||||
|
||||
display_panel_rotate(angle);
|
||||
}
|
||||
}
|
||||
|
||||
return drv->orientation_angle;
|
||||
}
|
||||
|
||||
int display_get_orientation(void) {
|
||||
display_driver_t* drv = &g_display_driver;
|
||||
|
||||
return drv->orientation_angle;
|
||||
}
|
||||
|
||||
#ifndef XFRAMEBUFFER
|
||||
void display_refresh(void) {
|
||||
// if the framebuffer is not used the implementation is empty
|
||||
}
|
||||
#endif
|
||||
|
||||
void display_wait_for_sync(void) {
|
||||
#ifdef DISPLAY_TE_PIN
|
||||
uint32_t id = display_panel_identify();
|
||||
if (id && (id != DISPLAY_ID_GC9307)) {
|
||||
// synchronize with the panel synchronization signal
|
||||
// in order to avoid visual tearing effects
|
||||
while (GPIO_PIN_SET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN))
|
||||
;
|
||||
while (GPIO_PIN_RESET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN))
|
||||
;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* display_save(const char* prefix) { return NULL; }
|
||||
|
||||
void display_clear_save(void) {}
|
||||
|
||||
void display_set_compatible_settings(void) { display_panel_set_big_endian(); }
|
||||
|
||||
static inline void set_window(const dma2d_params_t* dp) {
|
||||
display_panel_set_window(dp->dst_x, dp->dst_y, dp->dst_x + dp->width - 1,
|
||||
dp->dst_y + dp->height + 1);
|
||||
}
|
||||
|
||||
// Fills a rectangle with a specified color
|
||||
void display_fill(const dma2d_params_t* dp) {
|
||||
set_window(dp);
|
||||
|
||||
uint16_t height = dp->height;
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
ISSUE_PIXEL_DATA(dp->src_fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copies an RGB565 bitmap to specified rectangle
|
||||
void display_copy_rgb565(const dma2d_params_t* dp) {
|
||||
set_window(dp);
|
||||
|
||||
uint16_t* src_ptr = (uint16_t*)dp->src_row + dp->src_x;
|
||||
uint16_t height = dp->height;
|
||||
|
||||
while (height-- > 0) {
|
||||
for (int x = 0; x < dp->width; x++) {
|
||||
ISSUE_PIXEL_DATA(src_ptr[x]);
|
||||
}
|
||||
src_ptr += dp->src_stride / sizeof(*src_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Copies a MONO4 bitmap to specified rectangle
|
||||
// void display_copy_mono4(gdc_dma2d_t *dp);
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include TREZOR_BOARD
|
||||
#include STM32_HAL_H
|
||||
|
||||
#include "display_fb.h"
|
||||
#include "display_io.h"
|
||||
#include "display_panel.h"
|
||||
|
||||
#include "irq.h"
|
||||
#include "supervise.h"
|
||||
|
||||
#ifndef BOARDLOADER
|
||||
#include "bg_copy.h"
|
||||
#endif
|
||||
|
||||
#ifdef XFRAMEBUFFER
|
||||
|
||||
#ifndef STM32U5
|
||||
#error Framebuffer only supported on STM32U5 for now
|
||||
#endif
|
||||
|
||||
// Physical frame buffers in internal SRAM memory
|
||||
__attribute__((section(".fb1")))
|
||||
ALIGN_32BYTES(uint8_t physical_frame_buffer_0[PHYSICAL_FRAME_BUFFER_SIZE]);
|
||||
|
||||
__attribute__((section(".fb2")))
|
||||
ALIGN_32BYTES(uint8_t physical_frame_buffer_1[PHYSICAL_FRAME_BUFFER_SIZE]);
|
||||
|
||||
// The current frame buffer selector at fixed memory address
|
||||
// It's shared between bootloaders and the firmware
|
||||
__attribute__((section(".framebuffer_select"))) uint32_t current_frame_buffer =
|
||||
0;
|
||||
|
||||
static bool pending_fb_switch = false;
|
||||
|
||||
#ifndef BOARDLOADER
|
||||
void DISPLAY_TE_INTERRUPT_HANDLER(void) {
|
||||
HAL_NVIC_DisableIRQ(DISPLAY_TE_INTERRUPT_NUM);
|
||||
|
||||
if (current_frame_buffer == 1) {
|
||||
bg_copy_start_const_out_8((uint8_t *)physical_frame_buffer_1,
|
||||
(uint8_t *)DISPLAY_DATA_ADDRESS,
|
||||
DISPLAY_RESX * DISPLAY_RESY * 2);
|
||||
|
||||
} else {
|
||||
bg_copy_start_const_out_8((uint8_t *)physical_frame_buffer_0,
|
||||
(uint8_t *)DISPLAY_DATA_ADDRESS,
|
||||
DISPLAY_RESX * DISPLAY_RESY * 2);
|
||||
}
|
||||
|
||||
pending_fb_switch = false;
|
||||
__HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN);
|
||||
}
|
||||
|
||||
static void wait_for_fb_switch(void) {
|
||||
while (pending_fb_switch) {
|
||||
__WFI();
|
||||
}
|
||||
bg_copy_wait();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void copy_fb_to_display(uint16_t *fb) {
|
||||
for (int i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
|
||||
// 2 bytes per pixel because we're using RGB 5-6-5 format
|
||||
ISSUE_PIXEL_DATA(fb[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void switch_fb_manually(void) {
|
||||
// sync with the panel refresh
|
||||
while (GPIO_PIN_SET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) {
|
||||
}
|
||||
while (GPIO_PIN_RESET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) {
|
||||
}
|
||||
|
||||
if (current_frame_buffer == 0) {
|
||||
current_frame_buffer = 1;
|
||||
copy_fb_to_display((uint16_t *)physical_frame_buffer_1);
|
||||
memcpy(physical_frame_buffer_0, physical_frame_buffer_1,
|
||||
sizeof(physical_frame_buffer_0));
|
||||
|
||||
} else {
|
||||
current_frame_buffer = 0;
|
||||
copy_fb_to_display((uint16_t *)physical_frame_buffer_0);
|
||||
memcpy(physical_frame_buffer_1, physical_frame_buffer_0,
|
||||
sizeof(physical_frame_buffer_1));
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef BOARDLOADER
|
||||
static void switch_fb_in_backround(void) {
|
||||
if (current_frame_buffer == 0) {
|
||||
current_frame_buffer = 1;
|
||||
|
||||
memcpy(physical_frame_buffer_0, physical_frame_buffer_1,
|
||||
sizeof(physical_frame_buffer_0));
|
||||
|
||||
pending_fb_switch = true;
|
||||
__HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN);
|
||||
svc_enableIRQ(DISPLAY_TE_INTERRUPT_NUM);
|
||||
} else {
|
||||
current_frame_buffer = 0;
|
||||
memcpy(physical_frame_buffer_1, physical_frame_buffer_0,
|
||||
sizeof(physical_frame_buffer_1));
|
||||
|
||||
pending_fb_switch = true;
|
||||
__HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN);
|
||||
svc_enableIRQ(DISPLAY_TE_INTERRUPT_NUM);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void *display_get_frame_addr(void) {
|
||||
if (current_frame_buffer == 0) {
|
||||
return (void *)physical_frame_buffer_1;
|
||||
} else {
|
||||
return (void *)physical_frame_buffer_0;
|
||||
}
|
||||
}
|
||||
|
||||
void display_refresh(void) {
|
||||
#ifndef BOARDLOADER
|
||||
wait_for_fb_switch();
|
||||
display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
|
||||
|
||||
if (is_mode_handler()) {
|
||||
switch_fb_manually();
|
||||
} else {
|
||||
switch_fb_in_backround();
|
||||
}
|
||||
#else
|
||||
display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
|
||||
switch_fb_manually();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // XFRAMEBUFFER
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 TREZOR_HAL_DISPLAY_INTERNAL_H
|
||||
#define TREZOR_HAL_DISPLAY_INTERNAL_H
|
||||
|
||||
#include TREZOR_BOARD
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef XFRAMEBUFFER
|
||||
|
||||
// Size of the physical frame buffer in bytes
|
||||
#define PHYSICAL_FRAME_BUFFER_SIZE (DISPLAY_RESX * DISPLAY_RESY * 2)
|
||||
|
||||
// Physical frame buffers in internal SRAM memory
|
||||
//
|
||||
// Both frame buffers layes in the fixed addresses that
|
||||
// are shared between bootloaders and the firmware.
|
||||
extern uint8_t physical_frame_buffer_0[PHYSICAL_FRAME_BUFFER_SIZE];
|
||||
extern uint8_t physical_frame_buffer_1[PHYSICAL_FRAME_BUFFER_SIZE];
|
||||
|
||||
// The current frame buffer selector at fixed memory address
|
||||
//
|
||||
// The variable address is shared between bootloaders and the firmware
|
||||
extern uint32_t current_frame_buffer;
|
||||
|
||||
#endif // XFRAMEBUFFER
|
||||
|
||||
#endif // TREZOR_HAL_DISPLAY_INTERNAL_H
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include TREZOR_BOARD
|
||||
#include STM32_HAL_H
|
||||
|
||||
#include "display_io.h"
|
||||
#include "irq.h"
|
||||
|
||||
__IO DISP_MEM_TYPE *const DISPLAY_CMD_ADDRESS =
|
||||
(__IO DISP_MEM_TYPE *const)((uint32_t)DISPLAY_MEMORY_BASE);
|
||||
__IO DISP_MEM_TYPE *const DISPLAY_DATA_ADDRESS =
|
||||
(__IO DISP_MEM_TYPE *const)((uint32_t)DISPLAY_MEMORY_BASE |
|
||||
(DISPLAY_ADDR_SHIFT << DISPLAY_MEMORY_PIN));
|
||||
|
||||
void display_io_init_gpio(void) {
|
||||
// init peripherals
|
||||
__HAL_RCC_GPIOE_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOC_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOD_CLK_ENABLE();
|
||||
__HAL_RCC_FMC_CLK_ENABLE();
|
||||
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
|
||||
// LCD_RST/PC14
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
|
||||
GPIO_InitStructure.Alternate = 0;
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_14;
|
||||
// default to keeping display in reset
|
||||
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET);
|
||||
HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);
|
||||
|
||||
#ifdef DISPLAY_TE_PIN
|
||||
// LCD_FMARK (tearing effect)
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
GPIO_InitStructure.Alternate = 0;
|
||||
GPIO_InitStructure.Pin = DISPLAY_TE_PIN;
|
||||
HAL_GPIO_Init(DISPLAY_TE_PORT, &GPIO_InitStructure);
|
||||
#endif
|
||||
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
GPIO_InitStructure.Alternate = GPIO_AF12_FMC;
|
||||
// LCD_CS/PD7 LCD_RS/PD11 LCD_RD/PD4 LCD_WR/PD5
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_4 | GPIO_PIN_5;
|
||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
|
||||
// LCD_D0/PD14 LCD_D1/PD15 LCD_D2/PD0 LCD_D3/PD1
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_14 | GPIO_PIN_15 | GPIO_PIN_0 | GPIO_PIN_1;
|
||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
|
||||
// LCD_D4/PE7 LCD_D5/PE8 LCD_D6/PE9 LCD_D7/PE10
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
|
||||
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
|
||||
#ifdef USE_DISP_I8080_16BIT_DW
|
||||
// LCD_D8/PE11 LCD_D9/PE12 LCD_D10/PE13 LCD_D11/PE14
|
||||
GPIO_InitStructure.Pin =
|
||||
GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14;
|
||||
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
|
||||
// LCD_D12/PE15
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_15;
|
||||
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
|
||||
// LCD_D13/PD8 LCD_D14/PD9 LCD_D15/PD10
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
|
||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
|
||||
#endif
|
||||
}
|
||||
|
||||
void display_io_init_fmc(void) {
|
||||
// Reference UM1725 "Description of STM32F4 HAL and LL drivers",
|
||||
// section 64.2.1 "How to use this driver"
|
||||
SRAM_HandleTypeDef external_display_data_sram = {0};
|
||||
external_display_data_sram.Instance = FMC_NORSRAM_DEVICE;
|
||||
external_display_data_sram.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
|
||||
external_display_data_sram.Init.NSBank = FMC_NORSRAM_BANK1;
|
||||
external_display_data_sram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;
|
||||
external_display_data_sram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
|
||||
#ifdef USE_DISP_I8080_16BIT_DW
|
||||
external_display_data_sram.Init.MemoryDataWidth =
|
||||
FMC_NORSRAM_MEM_BUS_WIDTH_16;
|
||||
#elif USE_DISP_I8080_8BIT_DW
|
||||
external_display_data_sram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8;
|
||||
#endif
|
||||
external_display_data_sram.Init.BurstAccessMode =
|
||||
FMC_BURST_ACCESS_MODE_DISABLE;
|
||||
external_display_data_sram.Init.WaitSignalPolarity =
|
||||
FMC_WAIT_SIGNAL_POLARITY_LOW;
|
||||
external_display_data_sram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS;
|
||||
external_display_data_sram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
|
||||
external_display_data_sram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
|
||||
external_display_data_sram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
|
||||
external_display_data_sram.Init.AsynchronousWait =
|
||||
FMC_ASYNCHRONOUS_WAIT_DISABLE;
|
||||
external_display_data_sram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
|
||||
external_display_data_sram.Init.ContinuousClock =
|
||||
FMC_CONTINUOUS_CLOCK_SYNC_ONLY;
|
||||
external_display_data_sram.Init.PageSize = FMC_PAGE_SIZE_NONE;
|
||||
|
||||
// reference RM0090 section 37.5 Table 259, 37.5.4, Mode 1 SRAM, and 37.5.6
|
||||
FMC_NORSRAM_TimingTypeDef normal_mode_timing = {0};
|
||||
normal_mode_timing.AddressSetupTime = 5;
|
||||
normal_mode_timing.AddressHoldTime = 1; // don't care
|
||||
normal_mode_timing.DataSetupTime = 6;
|
||||
normal_mode_timing.BusTurnAroundDuration = 0; // don't care
|
||||
normal_mode_timing.CLKDivision = 2; // don't care
|
||||
normal_mode_timing.DataLatency = 2; // don't care
|
||||
normal_mode_timing.AccessMode = FMC_ACCESS_MODE_A;
|
||||
|
||||
HAL_SRAM_Init(&external_display_data_sram, &normal_mode_timing, NULL);
|
||||
}
|
||||
|
||||
#ifdef DISPLAY_TE_INTERRUPT_HANDLER
|
||||
void display_io_init_te_interrupt(void) {
|
||||
EXTI_HandleTypeDef EXTI_Handle = {0};
|
||||
EXTI_ConfigTypeDef EXTI_Config = {0};
|
||||
EXTI_Config.GPIOSel = DISPLAY_TE_INTERRUPT_GPIOSEL;
|
||||
EXTI_Config.Line = DISPLAY_TE_INTERRUPT_EXTI_LINE;
|
||||
EXTI_Config.Mode = EXTI_MODE_INTERRUPT;
|
||||
EXTI_Config.Trigger = EXTI_TRIGGER_RISING;
|
||||
HAL_EXTI_SetConfigLine(&EXTI_Handle, &EXTI_Config);
|
||||
|
||||
// setup interrupt for tearing effect pin
|
||||
HAL_NVIC_SetPriority(DISPLAY_TE_INTERRUPT_NUM, IRQ_PRI_DMA, 0);
|
||||
}
|
||||
#endif
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_DISPLAY_IO_H
|
||||
#define TREZORHAL_DISPLAY_IO_H
|
||||
|
||||
#include STM32_HAL_H
|
||||
#include TREZOR_BOARD
|
||||
|
||||
void display_io_init_gpio(void);
|
||||
void display_io_init_fmc(void);
|
||||
void display_io_init_te_interrupt(void);
|
||||
|
||||
#ifndef FMC_BANK1
|
||||
#define FMC_BANK1 0x60000000U
|
||||
#endif
|
||||
|
||||
#define DISPLAY_MEMORY_BASE FMC_BANK1
|
||||
#define DISPLAY_MEMORY_PIN 16
|
||||
|
||||
#ifdef USE_DISP_I8080_16BIT_DW
|
||||
#define DISPLAY_ADDR_SHIFT 2
|
||||
#define DISP_MEM_TYPE uint16_t
|
||||
#elif USE_DISP_I8080_8BIT_DW
|
||||
#define DISPLAY_ADDR_SHIFT 1
|
||||
#define DISP_MEM_TYPE uint8_t
|
||||
#else
|
||||
#error "Unsupported display interface"
|
||||
#endif
|
||||
|
||||
/*#define DISPLAY_CMD_ADDRESS ((__IO DISP_MEM_TYPE *)(DISPLAY_MEMORY_BASE))
|
||||
#define DISPLAY_DATA_ADDRESS \
|
||||
((__IO DISP_MEM_TYPE *)(DISPLAY_MEMORY_BASE | \
|
||||
(DISPLAY_ADDR_SHIFT << DISPLAY_MEMORY_PIN)))
|
||||
*/
|
||||
|
||||
extern __IO DISP_MEM_TYPE *const DISPLAY_CMD_ADDRESS;
|
||||
extern __IO DISP_MEM_TYPE *const DISPLAY_DATA_ADDRESS;
|
||||
|
||||
#define ISSUE_CMD_BYTE(X) (*(DISPLAY_CMD_ADDRESS) = (X))
|
||||
#define ISSUE_DATA_BYTE(X) (*(DISPLAY_DATA_ADDRESS) = (X))
|
||||
|
||||
#ifdef USE_DISP_I8080_16BIT_DW
|
||||
#define ISSUE_PIXEL_DATA(X) DATA(X)
|
||||
#elif USE_DISP_I8080_8BIT_DW
|
||||
#define ISSUE_PIXEL_DATA(X) \
|
||||
ISSUE_DATA_BYTE((X)&0xFF); \
|
||||
ISSUE_DATA_BYTE((X) >> 8)
|
||||
#endif
|
||||
|
||||
#endif // TREZORHAL_DISPLAY_IO_H
|
@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// using const volatile instead of #define results in binaries that change
|
||||
// only in 1-byte when the flag changes.
|
||||
// using #define leads compiler to over-optimize the code leading to bigger
|
||||
// differencies in the resulting binaries.
|
||||
|
||||
#include "display_panel.h"
|
||||
#include "display_io.h"
|
||||
|
||||
#ifdef TREZOR_MODEL_T
|
||||
#include "panels/154a.h"
|
||||
#include "panels/lx154a2411.h"
|
||||
#include "panels/lx154a2422.h"
|
||||
#include "panels/tf15411a.h"
|
||||
#else
|
||||
#include "panels/lx154a2422.h"
|
||||
#endif
|
||||
|
||||
// using const volatile instead of #define results in binaries that change
|
||||
// only in 1-byte when the flag changes.
|
||||
// using #define leads compiler to over-optimize the code leading to bigger
|
||||
// differencies in the resulting binaries.
|
||||
const volatile uint8_t DISPLAY_ST7789V_INVERT_COLORS2 = 1;
|
||||
|
||||
#ifdef DISPLAY_IDENTIFY
|
||||
static uint32_t read_display_id(uint8_t command) {
|
||||
volatile uint8_t c = 0;
|
||||
uint32_t id = 0;
|
||||
ISSUE_CMD_BYTE(command);
|
||||
c = *DISPLAY_DATA_ADDRESS; // first returned value is a dummy value and
|
||||
// should be discarded
|
||||
c = *DISPLAY_DATA_ADDRESS;
|
||||
id |= (c << 16);
|
||||
c = *DISPLAY_DATA_ADDRESS;
|
||||
id |= (c << 8);
|
||||
c = *DISPLAY_DATA_ADDRESS;
|
||||
id |= c;
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t display_panel_identify(void) {
|
||||
static uint32_t id = 0x000000U;
|
||||
static bool id_initialized = false;
|
||||
|
||||
// Return immediately if id has been already initialized
|
||||
if (id_initialized) return id;
|
||||
|
||||
// RDDID: Read Display ID
|
||||
id = read_display_id(0x04);
|
||||
// the default RDDID for ILI9341 should be 0x8000.
|
||||
// some display modules return 0x0.
|
||||
// the ILI9341 has an extra id, let's check it here.
|
||||
if ((id != DISPLAY_ID_ST7789V) && (id != DISPLAY_ID_GC9307)) {
|
||||
// Read ID4
|
||||
uint32_t id4 = read_display_id(0xD3);
|
||||
if (id4 == DISPLAY_ID_ILI9341V) { // definitely found a ILI9341
|
||||
id = id4;
|
||||
}
|
||||
}
|
||||
id_initialized = true;
|
||||
return id;
|
||||
}
|
||||
#else
|
||||
uint32_t display_panbel_identify(void) { return DISPLAY_ID_ST7789V; }
|
||||
#endif
|
||||
|
||||
bool display_panel_is_inverted() {
|
||||
bool inv_on = false;
|
||||
uint32_t id = display_panel_identify();
|
||||
if (id == DISPLAY_ID_ST7789V) {
|
||||
volatile uint8_t c = 0;
|
||||
ISSUE_CMD_BYTE(0x09); // read display status
|
||||
c = *DISPLAY_DATA_ADDRESS; // don't care
|
||||
c = *DISPLAY_DATA_ADDRESS; // don't care
|
||||
c = *DISPLAY_DATA_ADDRESS; // don't care
|
||||
c = *DISPLAY_DATA_ADDRESS;
|
||||
if (c & 0x20) {
|
||||
inv_on = true;
|
||||
}
|
||||
c = *DISPLAY_DATA_ADDRESS; // don't care
|
||||
}
|
||||
|
||||
return inv_on;
|
||||
}
|
||||
|
||||
void display_panel_sleep(void) {
|
||||
uint32_t id = display_panel_identify();
|
||||
if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
|
||||
(id == DISPLAY_ID_ST7789V)) {
|
||||
ISSUE_CMD_BYTE(0x28); // DISPOFF: Display Off
|
||||
ISSUE_CMD_BYTE(0x10); // SLPIN: Sleep in
|
||||
HAL_Delay(5); // need to wait 5 milliseconds after "sleep in" before
|
||||
// sending any new commands
|
||||
}
|
||||
}
|
||||
|
||||
void display_panel_unsleep(void) {
|
||||
uint32_t id = display_panel_identify();
|
||||
if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
|
||||
(id == DISPLAY_ID_ST7789V)) {
|
||||
ISSUE_CMD_BYTE(0x11); // SLPOUT: Sleep Out
|
||||
HAL_Delay(5); // need to wait 5 milliseconds after "sleep out" before
|
||||
// sending any new commands
|
||||
ISSUE_CMD_BYTE(0x29); // DISPON: Display On
|
||||
}
|
||||
}
|
||||
|
||||
void display_panel_set_window(uint16_t x0, uint16_t y0, uint16_t x1,
|
||||
uint16_t y1) {
|
||||
uint32_t id = display_panel_identify();
|
||||
if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) ||
|
||||
(id == DISPLAY_ID_ST7789V)) {
|
||||
ISSUE_CMD_BYTE(0x2A);
|
||||
ISSUE_DATA_BYTE(x0 >> 8);
|
||||
ISSUE_DATA_BYTE(x0 & 0xFF);
|
||||
ISSUE_DATA_BYTE(x1 >> 8);
|
||||
ISSUE_DATA_BYTE(x1 & 0xFF); // column addr set
|
||||
ISSUE_CMD_BYTE(0x2B);
|
||||
ISSUE_DATA_BYTE(y0 >> 8);
|
||||
ISSUE_DATA_BYTE(y0 & 0xFF);
|
||||
ISSUE_DATA_BYTE(y1 >> 8);
|
||||
ISSUE_DATA_BYTE(y1 & 0xFF); // row addr set
|
||||
ISSUE_CMD_BYTE(0x2C);
|
||||
}
|
||||
}
|
||||
|
||||
void display_panel_set_little_endian(void) {
|
||||
uint32_t id = display_panel_identify();
|
||||
if (id == DISPLAY_ID_GC9307) {
|
||||
// CANNOT SET ENDIAN FOR GC9307
|
||||
} else if (id == DISPLAY_ID_ST7789V) {
|
||||
ISSUE_CMD_BYTE(0xB0);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0xF8);
|
||||
} else if (id == DISPLAY_ID_ILI9341V) {
|
||||
// Interface Control: XOR BGR as ST7789V does
|
||||
ISSUE_CMD_BYTE(0xF6);
|
||||
ISSUE_DATA_BYTE(0x09);
|
||||
ISSUE_DATA_BYTE(0x30);
|
||||
ISSUE_DATA_BYTE(0x20);
|
||||
}
|
||||
}
|
||||
|
||||
void display_panel_set_big_endian(void) {
|
||||
uint32_t id = display_panel_identify();
|
||||
if (id == DISPLAY_ID_GC9307) {
|
||||
// CANNOT SET ENDIAN FOR GC9307
|
||||
} else if (id == DISPLAY_ID_ST7789V) {
|
||||
ISSUE_CMD_BYTE(0xB0);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0xF0);
|
||||
} else if (id == DISPLAY_ID_ILI9341V) {
|
||||
// Interface Control: XOR BGR as ST7789V does
|
||||
ISSUE_CMD_BYTE(0xF6);
|
||||
ISSUE_DATA_BYTE(0x09);
|
||||
ISSUE_DATA_BYTE(0x30);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void display_panal_init(void) {
|
||||
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); // LCD_RST/PC14
|
||||
// wait 10 milliseconds. only needs to be low for 10 microseconds.
|
||||
// my dev display module ties display reset and touch panel reset together.
|
||||
// keeping this low for max(display_reset_time, ctpm_reset_time) aids
|
||||
// development and does not hurt.
|
||||
HAL_Delay(10);
|
||||
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET); // LCD_RST/PC14
|
||||
// max wait time for hardware reset is 120 milliseconds
|
||||
// (experienced display flakiness using only 5ms wait before sending commands)
|
||||
HAL_Delay(120);
|
||||
|
||||
// identify the controller we will communicate with
|
||||
#ifdef TREZOR_MODEL_T
|
||||
uint32_t id = display_panel_identify();
|
||||
if (id == DISPLAY_ID_GC9307) {
|
||||
tf15411a_init_seq();
|
||||
} else if (id == DISPLAY_ID_ST7789V) {
|
||||
if (DISPLAY_ST7789V_INVERT_COLORS2) {
|
||||
lx154a2422_init_seq();
|
||||
} else {
|
||||
lx154a2411_init_seq();
|
||||
}
|
||||
} else if (id == DISPLAY_ID_ILI9341V) {
|
||||
_154a_init_seq();
|
||||
}
|
||||
#else
|
||||
lx154a2422_init_seq();
|
||||
#endif
|
||||
|
||||
display_panel_unsleep();
|
||||
}
|
||||
|
||||
void display_panel_init_gamma(void) {
|
||||
#ifdef TREZOR_MODEL_T
|
||||
uint32_t id = display_panel_identify();
|
||||
if (id == DISPLAY_ID_ST7789V && display_panel_is_inverted()) {
|
||||
// newest TT display - set proper gamma
|
||||
lx154a2422_gamma();
|
||||
} else if (id == DISPLAY_ID_ST7789V) {
|
||||
lx154a2411_gamma();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void display_panel_rotate(int angle) {
|
||||
#ifdef TREZOR_MODEL_T
|
||||
uint32_t id = display_panel_identify();
|
||||
if (id == DISPLAY_ID_GC9307) {
|
||||
tf15411a_rotate(angle);
|
||||
} else {
|
||||
lx154a2422_rotate(angle);
|
||||
}
|
||||
#else
|
||||
lx154a2422_rotate(angle);
|
||||
#endif
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_ST7789_PANEL_H
|
||||
#define TREZORHAL_ST7789_PANEL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// section "9.1.3 RDDID (04h): Read Display ID"
|
||||
// of ST7789V datasheet
|
||||
#define DISPLAY_ID_ST7789V 0x858552U
|
||||
// section "6.2.1. Read display identification information (04h)"
|
||||
// of GC9307 datasheet
|
||||
#define DISPLAY_ID_GC9307 0x009307U
|
||||
// section "8.3.23 Read ID4 (D3h)"
|
||||
// of ILI9341V datasheet
|
||||
#define DISPLAY_ID_ILI9341V 0x009341U
|
||||
|
||||
// Identifies the connected display panel and
|
||||
// returns one of DISPLAY_ID_xxx constant
|
||||
uint32_t display_panel_identify(void);
|
||||
bool display_panel_is_inverted();
|
||||
|
||||
void display_panel_init(void);
|
||||
void display_panel_init_gamma(void);
|
||||
void display_panel_set_little_endian(void);
|
||||
void display_panel_set_big_endian(void);
|
||||
|
||||
void display_panel_sleep(void);
|
||||
void display_panel_unsleep(void);
|
||||
void display_panel_set_window(uint16_t x0, uint16_t y0, uint16_t x1,
|
||||
uint16_t y1);
|
||||
void display_panel_rotate(int angle);
|
||||
|
||||
#endif // TREZORHAL_ST7789_PANEL_H
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 "../display_io.h"
|
||||
|
||||
void _154a_init_seq(void) {
|
||||
// most recent manual: https://www.newhavendisplay.com/app_notes/ILI9341.pdf
|
||||
// TEON: Tearing Effect Line On; V-blanking only
|
||||
ISSUE_CMD_BYTE(0x35);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
|
||||
// input)
|
||||
ISSUE_CMD_BYTE(0x3A);
|
||||
ISSUE_DATA_BYTE(0x55);
|
||||
|
||||
// Display Function Control: gate scan direction 319 -> 0
|
||||
ISSUE_CMD_BYTE(0xB6);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0xC2);
|
||||
ISSUE_DATA_BYTE(0x27);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// Interface Control: XOR BGR as ST7789V does
|
||||
ISSUE_CMD_BYTE(0xF6);
|
||||
ISSUE_DATA_BYTE(0x09);
|
||||
ISSUE_DATA_BYTE(0x30);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// the above config is the most important and definitely necessary
|
||||
|
||||
ISSUE_CMD_BYTE(0xCF);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0xC1);
|
||||
ISSUE_DATA_BYTE(0x30);
|
||||
|
||||
ISSUE_CMD_BYTE(0xED);
|
||||
ISSUE_DATA_BYTE(0x64);
|
||||
ISSUE_DATA_BYTE(0x03);
|
||||
ISSUE_DATA_BYTE(0x12);
|
||||
ISSUE_DATA_BYTE(0x81);
|
||||
|
||||
ISSUE_CMD_BYTE(0xE8);
|
||||
ISSUE_DATA_BYTE(0x85);
|
||||
ISSUE_DATA_BYTE(0x10);
|
||||
ISSUE_DATA_BYTE(0x7A);
|
||||
|
||||
ISSUE_CMD_BYTE(0xF7);
|
||||
ISSUE_DATA_BYTE(0x20);
|
||||
|
||||
ISSUE_CMD_BYTE(0xEA);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// power control VRH[5:0]
|
||||
ISSUE_CMD_BYTE(0xC0);
|
||||
ISSUE_DATA_BYTE(0x23);
|
||||
|
||||
// power control SAP[2:0] BT[3:0]
|
||||
ISSUE_CMD_BYTE(0xC1);
|
||||
ISSUE_DATA_BYTE(0x12);
|
||||
|
||||
// vcm control 1
|
||||
ISSUE_CMD_BYTE(0xC5);
|
||||
ISSUE_DATA_BYTE(0x60);
|
||||
ISSUE_DATA_BYTE(0x44);
|
||||
|
||||
// vcm control 2
|
||||
ISSUE_CMD_BYTE(0xC7);
|
||||
ISSUE_DATA_BYTE(0x8A);
|
||||
|
||||
// framerate
|
||||
ISSUE_CMD_BYTE(0xB1);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0x18);
|
||||
|
||||
// 3 gamma func disable
|
||||
ISSUE_CMD_BYTE(0xF2);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// gamma curve 1
|
||||
ISSUE_CMD_BYTE(0xE0);
|
||||
ISSUE_DATA_BYTE(0x0F);
|
||||
ISSUE_DATA_BYTE(0x2F);
|
||||
ISSUE_DATA_BYTE(0x2C);
|
||||
ISSUE_DATA_BYTE(0x0B);
|
||||
ISSUE_DATA_BYTE(0x0F);
|
||||
ISSUE_DATA_BYTE(0x09);
|
||||
ISSUE_DATA_BYTE(0x56);
|
||||
ISSUE_DATA_BYTE(0xD9);
|
||||
ISSUE_DATA_BYTE(0x4A);
|
||||
ISSUE_DATA_BYTE(0x0B);
|
||||
ISSUE_DATA_BYTE(0x14);
|
||||
ISSUE_DATA_BYTE(0x05);
|
||||
ISSUE_DATA_BYTE(0x0C);
|
||||
ISSUE_DATA_BYTE(0x06);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// gamma curve 2
|
||||
ISSUE_CMD_BYTE(0xE1);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0x10);
|
||||
ISSUE_DATA_BYTE(0x13);
|
||||
ISSUE_DATA_BYTE(0x04);
|
||||
ISSUE_DATA_BYTE(0x10);
|
||||
ISSUE_DATA_BYTE(0x06);
|
||||
ISSUE_DATA_BYTE(0x25);
|
||||
ISSUE_DATA_BYTE(0x26);
|
||||
ISSUE_DATA_BYTE(0x3B);
|
||||
ISSUE_DATA_BYTE(0x04);
|
||||
ISSUE_DATA_BYTE(0x0B);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x33);
|
||||
ISSUE_DATA_BYTE(0x39);
|
||||
ISSUE_DATA_BYTE(0x0F);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 _154A_H_
|
||||
#define _154A_H_
|
||||
|
||||
// ILI9341 IC controller
|
||||
|
||||
void _154a_init_seq(void);
|
||||
|
||||
#endif
|
@ -0,0 +1,82 @@
|
||||
|
||||
#include "../display_io.h"
|
||||
|
||||
void lx154a2411_gamma(void) {
|
||||
// positive voltage correction
|
||||
ISSUE_CMD_BYTE(0xE0);
|
||||
ISSUE_DATA_BYTE(0xD0);
|
||||
ISSUE_DATA_BYTE(0x03);
|
||||
ISSUE_DATA_BYTE(0x08);
|
||||
ISSUE_DATA_BYTE(0x0E);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
ISSUE_DATA_BYTE(0x2B);
|
||||
ISSUE_DATA_BYTE(0x3B);
|
||||
ISSUE_DATA_BYTE(0x44);
|
||||
ISSUE_DATA_BYTE(0x4C);
|
||||
ISSUE_DATA_BYTE(0x2B);
|
||||
ISSUE_DATA_BYTE(0x16);
|
||||
ISSUE_DATA_BYTE(0x15);
|
||||
ISSUE_DATA_BYTE(0x1E);
|
||||
ISSUE_DATA_BYTE(0x21);
|
||||
|
||||
// negative voltage correction
|
||||
ISSUE_CMD_BYTE(0xE1);
|
||||
ISSUE_DATA_BYTE(0xD0);
|
||||
ISSUE_DATA_BYTE(0x03);
|
||||
ISSUE_DATA_BYTE(0x08);
|
||||
ISSUE_DATA_BYTE(0x0E);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
ISSUE_DATA_BYTE(0x2B);
|
||||
ISSUE_DATA_BYTE(0x3B);
|
||||
ISSUE_DATA_BYTE(0x54);
|
||||
ISSUE_DATA_BYTE(0x4C);
|
||||
ISSUE_DATA_BYTE(0x2B);
|
||||
ISSUE_DATA_BYTE(0x16);
|
||||
ISSUE_DATA_BYTE(0x15);
|
||||
ISSUE_DATA_BYTE(0x1E);
|
||||
ISSUE_DATA_BYTE(0x21);
|
||||
}
|
||||
|
||||
void lx154a2411_init_seq(void) {
|
||||
// most recent manual:
|
||||
// https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
|
||||
// TEON: Tearing Effect Line On; V-blanking only
|
||||
ISSUE_CMD_BYTE(0x35);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
|
||||
// input)
|
||||
ISSUE_CMD_BYTE(0x3A);
|
||||
ISSUE_DATA_BYTE(0x55);
|
||||
|
||||
// CMD2EN: Commands in command table 2 can be executed when EXTC level is Low
|
||||
ISSUE_CMD_BYTE(0xDF);
|
||||
ISSUE_DATA_BYTE(0x5A);
|
||||
ISSUE_DATA_BYTE(0x69);
|
||||
ISSUE_DATA_BYTE(0x02);
|
||||
ISSUE_DATA_BYTE(0x01);
|
||||
|
||||
// LCMCTRL: LCM Control: XOR RGB setting
|
||||
ISSUE_CMD_BYTE(0xC0);
|
||||
ISSUE_DATA_BYTE(0x20);
|
||||
|
||||
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is gate 80.;
|
||||
// gate scan direction 319 -> 0
|
||||
ISSUE_CMD_BYTE(0xE4);
|
||||
ISSUE_DATA_BYTE(0x1D);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
|
||||
// INVOFF (20h): Display Inversion Off
|
||||
// INVON (21h): Display Inversion On
|
||||
ISSUE_CMD_BYTE(0x20);
|
||||
|
||||
// the above config is the most important and definitely necessary
|
||||
|
||||
// PWCTRL1: Power Control 1
|
||||
ISSUE_CMD_BYTE(0xD0);
|
||||
ISSUE_DATA_BYTE(0xA4);
|
||||
ISSUE_DATA_BYTE(0xA1);
|
||||
|
||||
lx154a2411_gamma();
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 LX154A2411_H_
|
||||
#define LX154A2411_H_
|
||||
|
||||
// ST7789_V IC controller
|
||||
void lx154a2411_gamma(void);
|
||||
void lx154a2411_init_seq(void);
|
||||
|
||||
#endif
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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 "../display_io.h"
|
||||
#include "touch.h"
|
||||
|
||||
void lx154a2422_gamma(void) {
|
||||
// positive voltage correction
|
||||
ISSUE_CMD_BYTE(0xE0);
|
||||
ISSUE_DATA_BYTE(0xD0);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x10);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x26);
|
||||
ISSUE_DATA_BYTE(0x36);
|
||||
ISSUE_DATA_BYTE(0x34);
|
||||
ISSUE_DATA_BYTE(0x4D);
|
||||
ISSUE_DATA_BYTE(0x18);
|
||||
ISSUE_DATA_BYTE(0x13);
|
||||
ISSUE_DATA_BYTE(0x14);
|
||||
ISSUE_DATA_BYTE(0x2F);
|
||||
ISSUE_DATA_BYTE(0x34);
|
||||
|
||||
// negative voltage correction
|
||||
ISSUE_CMD_BYTE(0xE1);
|
||||
ISSUE_DATA_BYTE(0xD0);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x10);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x09);
|
||||
ISSUE_DATA_BYTE(0x26);
|
||||
ISSUE_DATA_BYTE(0x36);
|
||||
ISSUE_DATA_BYTE(0x53);
|
||||
ISSUE_DATA_BYTE(0x4C);
|
||||
ISSUE_DATA_BYTE(0x18);
|
||||
ISSUE_DATA_BYTE(0x14);
|
||||
ISSUE_DATA_BYTE(0x14);
|
||||
ISSUE_DATA_BYTE(0x2F);
|
||||
ISSUE_DATA_BYTE(0x34);
|
||||
}
|
||||
|
||||
void lx154a2422_init_seq(void) {
|
||||
// most recent manual:
|
||||
// https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
|
||||
// TEON: Tearing Effect Line On; V-blanking only
|
||||
ISSUE_CMD_BYTE(0x35);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
|
||||
// input)
|
||||
ISSUE_CMD_BYTE(0x3A);
|
||||
ISSUE_DATA_BYTE(0x55);
|
||||
|
||||
// CMD2EN: Commands in command table 2 can be executed when EXTC level is Low
|
||||
ISSUE_CMD_BYTE(0xDF);
|
||||
ISSUE_DATA_BYTE(0x5A);
|
||||
ISSUE_DATA_BYTE(0x69);
|
||||
ISSUE_DATA_BYTE(0x02);
|
||||
ISSUE_DATA_BYTE(0x01);
|
||||
|
||||
// LCMCTRL: LCM Control: XOR RGB setting
|
||||
ISSUE_CMD_BYTE(0xC0);
|
||||
ISSUE_DATA_BYTE(0x20);
|
||||
|
||||
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is gate 80.;
|
||||
// gate scan direction 319 -> 0
|
||||
ISSUE_CMD_BYTE(0xE4);
|
||||
ISSUE_DATA_BYTE(0x1D);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
|
||||
// INVOFF (20h): Display Inversion Off
|
||||
// INVON (21h): Display Inversion On
|
||||
ISSUE_CMD_BYTE(0x21);
|
||||
|
||||
// the above config is the most important and definitely necessary
|
||||
|
||||
// PWCTRL1: Power Control 1
|
||||
ISSUE_CMD_BYTE(0xD0);
|
||||
ISSUE_DATA_BYTE(0xA4);
|
||||
ISSUE_DATA_BYTE(0xA1);
|
||||
|
||||
lx154a2422_gamma();
|
||||
}
|
||||
|
||||
void lx154a2422_rotate(int degrees) {
|
||||
uint16_t shift = 0;
|
||||
|
||||
#define RGB (1 << 3)
|
||||
#define ML (1 << 4) // vertical refresh order
|
||||
#define MH (1 << 2) // horizontal refresh order
|
||||
#define MV (1 << 5)
|
||||
#define MX (1 << 6)
|
||||
#define MY (1 << 7)
|
||||
// MADCTL: Memory Data Access Control - reference:
|
||||
// section 8.12 in the ST7789V manual
|
||||
uint8_t display_command_parameter = 0;
|
||||
switch (degrees) {
|
||||
case 0:
|
||||
display_command_parameter = 0;
|
||||
break;
|
||||
case 90:
|
||||
display_command_parameter = MV | MX | MH | ML;
|
||||
shift = 1;
|
||||
break;
|
||||
case 180:
|
||||
display_command_parameter = MX | MY | MH | ML;
|
||||
shift = 1;
|
||||
break;
|
||||
case 270:
|
||||
display_command_parameter = MV | MY;
|
||||
break;
|
||||
}
|
||||
|
||||
ISSUE_CMD_BYTE(0x36);
|
||||
ISSUE_DATA_BYTE(display_command_parameter);
|
||||
|
||||
if (shift) {
|
||||
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
|
||||
// gate 80.; gate scan direction 319 -> 0
|
||||
ISSUE_CMD_BYTE(0xE4);
|
||||
ISSUE_DATA_BYTE(0x1D);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
} else {
|
||||
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
|
||||
// gate 80.; gate scan direction 319 -> 0
|
||||
ISSUE_CMD_BYTE(0xE4);
|
||||
ISSUE_DATA_BYTE(0x1D);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
}
|
||||
}
|
@ -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 LX154A2422_H_
|
||||
#define LX154A2422_H_
|
||||
|
||||
#include "displays/st7789v.h"
|
||||
|
||||
void lx154a2422_init_seq(void);
|
||||
void lx154a2422_gamma(void);
|
||||
void lx154a2422_rotate(int degrees);
|
||||
|
||||
#endif
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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 "../display_io.h"
|
||||
|
||||
void tf15411a_init_seq(void) {
|
||||
// Inter Register Enable1
|
||||
ISSUE_CMD_BYTE(0xFE);
|
||||
|
||||
// Inter Register Enable2
|
||||
ISSUE_CMD_BYTE(0xEF);
|
||||
|
||||
// TEON: Tearing Effect Line On; V-blanking only
|
||||
ISSUE_CMD_BYTE(0x35);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits
|
||||
// input)
|
||||
ISSUE_CMD_BYTE(0x3A);
|
||||
ISSUE_DATA_BYTE(0x55);
|
||||
|
||||
// Frame Rate
|
||||
// ISSUE_CMD_BYTE(0xE8); ISSUE_DATA_BYTE(0x12); ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
// Power Control 2
|
||||
ISSUE_CMD_BYTE(0xC3);
|
||||
ISSUE_DATA_BYTE(0x27);
|
||||
|
||||
// Power Control 3
|
||||
ISSUE_CMD_BYTE(0xC4);
|
||||
ISSUE_DATA_BYTE(0x18);
|
||||
|
||||
// Power Control 4
|
||||
ISSUE_CMD_BYTE(0xC9);
|
||||
ISSUE_DATA_BYTE(0x1F);
|
||||
|
||||
ISSUE_CMD_BYTE(0xC5);
|
||||
ISSUE_DATA_BYTE(0x0F);
|
||||
|
||||
ISSUE_CMD_BYTE(0xC6);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
|
||||
ISSUE_CMD_BYTE(0xC7);
|
||||
ISSUE_DATA_BYTE(0x10);
|
||||
|
||||
ISSUE_CMD_BYTE(0xC8);
|
||||
ISSUE_DATA_BYTE(0x01);
|
||||
|
||||
ISSUE_CMD_BYTE(0xFF);
|
||||
ISSUE_DATA_BYTE(0x62);
|
||||
|
||||
ISSUE_CMD_BYTE(0x99);
|
||||
ISSUE_DATA_BYTE(0x3E);
|
||||
|
||||
ISSUE_CMD_BYTE(0x9D);
|
||||
ISSUE_DATA_BYTE(0x4B);
|
||||
|
||||
ISSUE_CMD_BYTE(0x8E);
|
||||
ISSUE_DATA_BYTE(0x0F);
|
||||
|
||||
// SET_GAMMA1
|
||||
ISSUE_CMD_BYTE(0xF0);
|
||||
ISSUE_DATA_BYTE(0x8F);
|
||||
ISSUE_DATA_BYTE(0x1B);
|
||||
ISSUE_DATA_BYTE(0x05);
|
||||
ISSUE_DATA_BYTE(0x06);
|
||||
ISSUE_DATA_BYTE(0x07);
|
||||
ISSUE_DATA_BYTE(0x42);
|
||||
|
||||
// SET_GAMMA3
|
||||
ISSUE_CMD_BYTE(0xF2);
|
||||
ISSUE_DATA_BYTE(0x5C);
|
||||
ISSUE_DATA_BYTE(0x1F);
|
||||
ISSUE_DATA_BYTE(0x12);
|
||||
ISSUE_DATA_BYTE(0x10);
|
||||
ISSUE_DATA_BYTE(0x07);
|
||||
ISSUE_DATA_BYTE(0x43);
|
||||
|
||||
// SET_GAMMA2
|
||||
ISSUE_CMD_BYTE(0xF1);
|
||||
ISSUE_DATA_BYTE(0x59);
|
||||
ISSUE_DATA_BYTE(0xCF);
|
||||
ISSUE_DATA_BYTE(0xCF);
|
||||
ISSUE_DATA_BYTE(0x35);
|
||||
ISSUE_DATA_BYTE(0x37);
|
||||
ISSUE_DATA_BYTE(0x8F);
|
||||
|
||||
// SET_GAMMA4
|
||||
ISSUE_CMD_BYTE(0xF3);
|
||||
ISSUE_DATA_BYTE(0x58);
|
||||
ISSUE_DATA_BYTE(0xCF);
|
||||
ISSUE_DATA_BYTE(0xCF);
|
||||
ISSUE_DATA_BYTE(0x35);
|
||||
ISSUE_DATA_BYTE(0x37);
|
||||
ISSUE_DATA_BYTE(0x8F);
|
||||
}
|
||||
|
||||
void tf15411a_rotate(int degrees) {
|
||||
uint16_t shift = 0;
|
||||
|
||||
#define RGB (1 << 3)
|
||||
#define ML (1 << 4) // vertical refresh order
|
||||
#define MH (1 << 2) // horizontal refresh order
|
||||
#define MV (1 << 5)
|
||||
#define MX (1 << 6)
|
||||
#define MY (1 << 7)
|
||||
// MADCTL: Memory Data Access Control - reference:
|
||||
// section 9.3 in the ILI9341 manual
|
||||
// section 6.2.18 in the GC9307 manual
|
||||
// section 8.12 in the ST7789V manual
|
||||
uint8_t display_command_parameter = 0;
|
||||
switch (degrees) {
|
||||
case 0:
|
||||
display_command_parameter = 0;
|
||||
break;
|
||||
case 90:
|
||||
display_command_parameter = MV | MX | MH | ML;
|
||||
shift = 1;
|
||||
break;
|
||||
case 180:
|
||||
display_command_parameter = MX | MY | MH | ML;
|
||||
shift = 1;
|
||||
break;
|
||||
case 270:
|
||||
display_command_parameter = MV | MY;
|
||||
break;
|
||||
}
|
||||
|
||||
display_command_parameter ^= RGB | MY; // XOR RGB and MY settings
|
||||
|
||||
ISSUE_CMD_BYTE(0x36);
|
||||
ISSUE_DATA_BYTE(display_command_parameter);
|
||||
|
||||
if (shift) {
|
||||
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
|
||||
// gate 80.; gate scan direction 319 -> 0
|
||||
ISSUE_CMD_BYTE(0xE4);
|
||||
ISSUE_DATA_BYTE(0x1D);
|
||||
ISSUE_DATA_BYTE(0x00);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
} else {
|
||||
// GATECTRL: Gate Control; NL = 240 gate lines, first scan line is
|
||||
// gate 80.; gate scan direction 319 -> 0
|
||||
ISSUE_CMD_BYTE(0xE4);
|
||||
ISSUE_DATA_BYTE(0x1D);
|
||||
ISSUE_DATA_BYTE(0x0A);
|
||||
ISSUE_DATA_BYTE(0x11);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 TF15411A_H_
|
||||
#define TF15411A_H_
|
||||
|
||||
// GC9307 IC controller
|
||||
|
||||
void tf15411a_init_seq(void);
|
||||
void tf15411a_rotate(int degrees);
|
||||
|
||||
#endif
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include TREZOR_BOARD
|
||||
#include STM32_HAL_H
|
||||
|
||||
#include "display_internal.h"
|
||||
#include "ili9341_spi.h"
|
||||
#include "xdisplay.h"
|
||||
|
||||
#if (DISPLAY_RESX != 240) || (DISPLAY_RESY != 320)
|
||||
#error "Incompatible display resolution"
|
||||
#endif
|
||||
|
||||
// Display driver context.
|
||||
typedef struct {
|
||||
// Current display orientation (0, 90, 180, 270)
|
||||
int orientation_angle;
|
||||
// Current backlight level ranging from 0 to 255
|
||||
int backlight_level;
|
||||
} display_driver_t;
|
||||
|
||||
// Display driver instance
|
||||
static display_driver_t g_display_driver;
|
||||
|
||||
void display_init(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
memset(drv, 0, sizeof(display_driver_t));
|
||||
|
||||
// Initialize LTDC controller
|
||||
BSP_LCD_Init();
|
||||
// Initialize external display controller
|
||||
ili9341_init();
|
||||
}
|
||||
|
||||
void display_reinit(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
memset(drv, 0, sizeof(display_driver_t));
|
||||
}
|
||||
|
||||
void display_finish_actions(void) {
|
||||
// Not used and intentionally left empty
|
||||
}
|
||||
|
||||
int display_set_backlight(int level) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
// Just emulation, not doing anything
|
||||
drv->backlight_level = level;
|
||||
return level;
|
||||
}
|
||||
|
||||
int display_get_backlight(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
return drv->backlight_level;
|
||||
}
|
||||
|
||||
int display_set_orientation(int angle) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
if (angle == 0 || angle == 90 || angle == 180 || angle == 270) {
|
||||
// Just emulation, not doing anything
|
||||
drv->orientation_angle = angle;
|
||||
}
|
||||
|
||||
return drv->orientation_angle;
|
||||
}
|
||||
|
||||
int display_get_orientation(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
return drv->orientation_angle;
|
||||
}
|
||||
|
||||
void *display_get_frame_addr(void) { return (void *)FRAME_BUFFER_ADDR; }
|
||||
|
||||
void display_refresh(void) {
|
||||
// Do nothing as using just a single frame buffer
|
||||
}
|
||||
|
||||
const char *display_save(const char *prefix) { return NULL; }
|
||||
|
||||
void display_clear_save(void) {}
|
||||
|
||||
void display_set_compatible_settings() {}
|
||||
|
||||
// Functions for drawing on display
|
||||
/*
|
||||
|
||||
// Fills a rectangle with a specified color
|
||||
void display_fill(gdc_dma2d_t *dp);
|
||||
|
||||
// Copies an RGB565 bitmap to specified rectangle
|
||||
void display_copy_rgb565(gdc_dma2d_t *dp);
|
||||
|
||||
// Copies a MONO4 bitmap to specified rectangle
|
||||
void display_copy_mono4(gdc_dma2d_t *dp);
|
||||
|
||||
// Copies a MONO1P bitmap to specified rectangle
|
||||
void display_copy_mono1p(gdc_dma2d_t *dp);
|
||||
*/
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TREZORHAL_DISPLAY_INTERNAL_H
|
||||
#define TREZORHAL_DISPLAY_INTERNAL_H
|
||||
|
||||
#include TREZOR_BOARD
|
||||
#include STM32_HAL_H
|
||||
|
||||
#include "sdram.h"
|
||||
|
||||
// Frame buffer address in external SDRAM
|
||||
#define FRAME_BUFFER_ADDR ((uint32_t)SDRAM_DEVICE_ADDR)
|
||||
// Frame buffer size (16-bit per pixel RGB565)
|
||||
#define FRAME_BUFFER_SIZE (DISPLAY_RESX * DISPLAY_RESY * 2)
|
||||
|
||||
// Initializes LTDC controller and I/O pins
|
||||
void BSP_LCD_Init(void);
|
||||
|
||||
#endif // TREZORHAL_DISPLAY_INTERNAL_H
|
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include TREZOR_BOARD
|
||||
#include STM32_HAL_H
|
||||
|
||||
#include "display_internal.h"
|
||||
#include "ili9341_spi.h"
|
||||
#include "xdisplay.h"
|
||||
|
||||
#define MAX_LAYER_NUMBER 2
|
||||
|
||||
LTDC_HandleTypeDef LtdcHandler;
|
||||
static RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
|
||||
|
||||
/* Default LCD configuration with LCD Layer 1 */
|
||||
uint32_t ActiveLayer = 0;
|
||||
|
||||
/**
|
||||
* @brief Initializes the LCD layers.
|
||||
* @param LayerIndex: the layer foreground or background.
|
||||
* @param FB_Address: the layer frame buffer.
|
||||
*/
|
||||
void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address) {
|
||||
LTDC_LayerCfgTypeDef Layercfg;
|
||||
|
||||
/* Layer Init */
|
||||
Layercfg.WindowX0 = 0;
|
||||
Layercfg.WindowX1 = DISPLAY_RESX;
|
||||
Layercfg.WindowY0 = 0;
|
||||
Layercfg.WindowY1 = DISPLAY_RESY;
|
||||
Layercfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
|
||||
Layercfg.FBStartAdress = FB_Address;
|
||||
Layercfg.Alpha = 255;
|
||||
Layercfg.Alpha0 = 0;
|
||||
Layercfg.Backcolor.Blue = 0;
|
||||
Layercfg.Backcolor.Green = 0;
|
||||
Layercfg.Backcolor.Red = 0;
|
||||
Layercfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
|
||||
Layercfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
|
||||
Layercfg.ImageWidth = DISPLAY_RESX;
|
||||
Layercfg.ImageHeight = DISPLAY_RESY;
|
||||
|
||||
HAL_LTDC_ConfigLayer(&LtdcHandler, &Layercfg, LayerIndex);
|
||||
|
||||
// DrawProp[LayerIndex].BackColor = LCD_COLOR_WHITE;
|
||||
// DrawProp[LayerIndex].pFont = &Font24;
|
||||
// DrawProp[LayerIndex].TextColor = LCD_COLOR_BLACK;
|
||||
|
||||
/* Dithering activation */
|
||||
HAL_LTDC_EnableDither(&LtdcHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Selects the LCD Layer.
|
||||
* @param LayerIndex: the Layer foreground or background.
|
||||
*/
|
||||
void BSP_LCD_SelectLayer(uint32_t LayerIndex) { ActiveLayer = LayerIndex; }
|
||||
|
||||
/**
|
||||
* @brief Sets a LCD Layer visible.
|
||||
* @param LayerIndex: the visible Layer.
|
||||
* @param state: new state of the specified layer.
|
||||
* This parameter can be: ENABLE or DISABLE.
|
||||
*/
|
||||
void BSP_LCD_SetLayerVisible(uint32_t LayerIndex, FunctionalState state) {
|
||||
if (state == ENABLE) {
|
||||
__HAL_LTDC_LAYER_ENABLE(&LtdcHandler, LayerIndex);
|
||||
} else {
|
||||
__HAL_LTDC_LAYER_DISABLE(&LtdcHandler, LayerIndex);
|
||||
}
|
||||
__HAL_LTDC_RELOAD_CONFIG(&LtdcHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets an LCD Layer visible without reloading.
|
||||
* @param LayerIndex: Visible Layer
|
||||
* @param State: New state of the specified layer
|
||||
* This parameter can be one of the following values:
|
||||
* @arg ENABLE
|
||||
* @arg DISABLE
|
||||
* @retval None
|
||||
*/
|
||||
void BSP_LCD_SetLayerVisible_NoReload(uint32_t LayerIndex,
|
||||
FunctionalState State) {
|
||||
if (State == ENABLE) {
|
||||
__HAL_LTDC_LAYER_ENABLE(&LtdcHandler, LayerIndex);
|
||||
} else {
|
||||
__HAL_LTDC_LAYER_DISABLE(&LtdcHandler, LayerIndex);
|
||||
}
|
||||
/* Do not Sets the Reload */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configures the Transparency.
|
||||
* @param LayerIndex: the Layer foreground or background.
|
||||
* @param Transparency: the Transparency,
|
||||
* This parameter must range from 0x00 to 0xFF.
|
||||
*/
|
||||
void BSP_LCD_SetTransparency(uint32_t LayerIndex, uint8_t Transparency) {
|
||||
HAL_LTDC_SetAlpha(&LtdcHandler, Transparency, LayerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configures the transparency without reloading.
|
||||
* @param LayerIndex: Layer foreground or background.
|
||||
* @param Transparency: Transparency
|
||||
* This parameter must be a number between Min_Data = 0x00 and
|
||||
* Max_Data = 0xFF
|
||||
* @retval None
|
||||
*/
|
||||
void BSP_LCD_SetTransparency_NoReload(uint32_t LayerIndex,
|
||||
uint8_t Transparency) {
|
||||
HAL_LTDC_SetAlpha_NoReload(&LtdcHandler, Transparency, LayerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a LCD layer frame buffer address.
|
||||
* @param LayerIndex: specifies the Layer foreground or background
|
||||
* @param Address: new LCD frame buffer value
|
||||
*/
|
||||
void BSP_LCD_SetLayerAddress(uint32_t LayerIndex, uint32_t Address) {
|
||||
HAL_LTDC_SetAddress(&LtdcHandler, Address, LayerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets an LCD layer frame buffer address without reloading.
|
||||
* @param LayerIndex: Layer foreground or background
|
||||
* @param Address: New LCD frame buffer value
|
||||
* @retval None
|
||||
*/
|
||||
void BSP_LCD_SetLayerAddress_NoReload(uint32_t LayerIndex, uint32_t Address) {
|
||||
HAL_LTDC_SetAddress_NoReload(&LtdcHandler, Address, LayerIndex);
|
||||
}
|
||||
|
||||
void BSP_LCD_Init(void) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure = {0};
|
||||
|
||||
/* Enable the LTDC and DMA2D Clock */
|
||||
__HAL_RCC_LTDC_CLK_ENABLE();
|
||||
__HAL_RCC_DMA2D_CLK_ENABLE();
|
||||
|
||||
/* Enable GPIOs clock */
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOB_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOC_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOD_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOF_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOG_CLK_ENABLE();
|
||||
|
||||
/* GPIOs Configuration */
|
||||
/*
|
||||
+------------------------+-----------------------+----------------------------+
|
||||
+ LCD pins assignment +
|
||||
+------------------------+-----------------------+----------------------------+
|
||||
| LCD_TFT R2 <-> PC.10 | LCD_TFT G2 <-> PA.06 | LCD_TFT B2 <-> PD.06 | |
|
||||
LCD_TFT R3 <-> PB.00 | LCD_TFT G3 <-> PG.10 | LCD_TFT B3 <-> PG.11 |
|
||||
| LCD_TFT R4 <-> PA.11 | LCD_TFT G4 <-> PB.10 | LCD_TFT B4 <-> PG.12 | |
|
||||
LCD_TFT R5 <-> PA.12 | LCD_TFT G5 <-> PB.11 | LCD_TFT B5 <-> PA.03 |
|
||||
| LCD_TFT R6 <-> PB.01 | LCD_TFT G6 <-> PC.07 | LCD_TFT B6 <-> PB.08 | |
|
||||
LCD_TFT R7 <-> PG.06 | LCD_TFT G7 <-> PD.03 | LCD_TFT B7 <-> PB.09 |
|
||||
-------------------------------------------------------------------------------
|
||||
| LCD_TFT HSYNC <-> PC.06 | LCDTFT VSYNC <-> PA.04 |
|
||||
| LCD_TFT CLK <-> PG.07 | LCD_TFT DE <-> PF.10 |
|
||||
-----------------------------------------------------
|
||||
*/
|
||||
|
||||
/* GPIOA configuration */
|
||||
GPIO_InitStructure.Pin =
|
||||
GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_11 | GPIO_PIN_12;
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
|
||||
GPIO_InitStructure.Alternate = GPIO_AF14_LTDC;
|
||||
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
|
||||
|
||||
/* GPIOB configuration */
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11;
|
||||
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
|
||||
|
||||
/* GPIOC configuration */
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_10;
|
||||
HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);
|
||||
|
||||
/* GPIOD configuration */
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_3 | GPIO_PIN_6;
|
||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
|
||||
|
||||
/* GPIOF configuration */
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_10;
|
||||
HAL_GPIO_Init(GPIOF, &GPIO_InitStructure);
|
||||
|
||||
/* GPIOG configuration */
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_11;
|
||||
HAL_GPIO_Init(GPIOG, &GPIO_InitStructure);
|
||||
|
||||
/* GPIOB configuration */
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_0 | GPIO_PIN_1;
|
||||
GPIO_InitStructure.Alternate = GPIO_AF9_LTDC;
|
||||
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
|
||||
|
||||
/* GPIOG configuration */
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_10 | GPIO_PIN_12;
|
||||
HAL_GPIO_Init(GPIOG, &GPIO_InitStructure);
|
||||
|
||||
/* On STM32F429I-DISCO, it is not possible to read ILI9341 ID because */
|
||||
/* PIN EXTC is not connected to VDD and then LCD_READ_ID4 is not accessible.
|
||||
*/
|
||||
/* In this case, ReadID function is bypassed.*/
|
||||
/*if(ili9341_drv.ReadID() == ILI9341_ID)*/
|
||||
|
||||
/* LTDC Configuration ----------------------------------------------------*/
|
||||
LtdcHandler.Instance = LTDC;
|
||||
|
||||
/* Timing configuration (Typical configuration from ILI9341 datasheet)
|
||||
HSYNC=10 (9+1)
|
||||
HBP=20 (29-10+1)
|
||||
ActiveW=240 (269-20-10+1)
|
||||
HFP=10 (279-240-20-10+1)
|
||||
|
||||
VSYNC=2 (1+1)
|
||||
VBP=2 (3-2+1)
|
||||
ActiveH=320 (323-2-2+1)
|
||||
VFP=4 (327-320-2-2+1)
|
||||
*/
|
||||
|
||||
/* Configure horizontal synchronization width */
|
||||
LtdcHandler.Init.HorizontalSync = ILI9341_HSYNC;
|
||||
/* Configure vertical synchronization height */
|
||||
LtdcHandler.Init.VerticalSync = ILI9341_VSYNC;
|
||||
/* Configure accumulated horizontal back porch */
|
||||
LtdcHandler.Init.AccumulatedHBP = ILI9341_HBP;
|
||||
/* Configure accumulated vertical back porch */
|
||||
LtdcHandler.Init.AccumulatedVBP = ILI9341_VBP;
|
||||
/* Configure accumulated active width */
|
||||
LtdcHandler.Init.AccumulatedActiveW = 269;
|
||||
/* Configure accumulated active height */
|
||||
LtdcHandler.Init.AccumulatedActiveH = 323;
|
||||
/* Configure total width */
|
||||
LtdcHandler.Init.TotalWidth = 279;
|
||||
/* Configure total height */
|
||||
LtdcHandler.Init.TotalHeigh = 327;
|
||||
|
||||
/* Configure R,G,B component values for LCD background color */
|
||||
LtdcHandler.Init.Backcolor.Red = 0;
|
||||
LtdcHandler.Init.Backcolor.Blue = 0;
|
||||
LtdcHandler.Init.Backcolor.Green = 0;
|
||||
|
||||
/* LCD clock configuration */
|
||||
/* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */
|
||||
/* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */
|
||||
/* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/4 = 48 Mhz */
|
||||
/* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_8 = 48/4 = 6Mhz */
|
||||
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
|
||||
PeriphClkInitStruct.PLLSAI.PLLSAIN = 192;
|
||||
PeriphClkInitStruct.PLLSAI.PLLSAIR = 4;
|
||||
PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_8;
|
||||
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
|
||||
|
||||
/* Polarity */
|
||||
LtdcHandler.Init.HSPolarity = LTDC_HSPOLARITY_AL;
|
||||
LtdcHandler.Init.VSPolarity = LTDC_VSPOLARITY_AL;
|
||||
LtdcHandler.Init.DEPolarity = LTDC_DEPOLARITY_AL;
|
||||
LtdcHandler.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
|
||||
|
||||
HAL_LTDC_Init(&LtdcHandler);
|
||||
|
||||
/* Initialize the LCD Layers */
|
||||
BSP_LCD_LayerDefaultInit(1, FRAME_BUFFER_ADDR);
|
||||
|
||||
memset((void *)FRAME_BUFFER_ADDR, 0, FRAME_BUFFER_SIZE);
|
||||
}
|
@ -0,0 +1,512 @@
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include TREZOR_BOARD
|
||||
#include "ili9341_spi.h"
|
||||
#include STM32_HAL_H
|
||||
|
||||
/**
|
||||
* @brief ILI9341 chip IDs
|
||||
*/
|
||||
#define ILI9341_ID 0x9341
|
||||
|
||||
/**
|
||||
* @brief ILI9341 Size
|
||||
*/
|
||||
#define ILI9341_LCD_PIXEL_WIDTH ((uint16_t)240)
|
||||
#define ILI9341_LCD_PIXEL_HEIGHT ((uint16_t)320)
|
||||
|
||||
/**
|
||||
* @brief ILI9341 Timing
|
||||
*/
|
||||
/* Timing configuration (Typical configuration from ILI9341 datasheet)
|
||||
HSYNC=10 (9+1)
|
||||
HBP=20 (29-10+1)
|
||||
ActiveW=240 (269-20-10+1)
|
||||
HFP=10 (279-240-20-10+1)
|
||||
|
||||
VSYNC=2 (1+1)
|
||||
VBP=2 (3-2+1)
|
||||
ActiveH=320 (323-2-2+1)
|
||||
VFP=4 (327-320-2-2+1)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief ILI9341 Registers
|
||||
*/
|
||||
|
||||
/* Level 1 Commands */
|
||||
#define LCD_SWRESET 0x01 /* Software Reset */
|
||||
#define LCD_READ_DISPLAY_ID 0x04 /* Read display identification information */
|
||||
#define LCD_RDDST 0x09 /* Read Display Status */
|
||||
#define LCD_RDDPM 0x0A /* Read Display Power Mode */
|
||||
#define LCD_RDDMADCTL 0x0B /* Read Display MADCTL */
|
||||
#define LCD_RDDCOLMOD 0x0C /* Read Display Pixel Format */
|
||||
#define LCD_RDDIM 0x0D /* Read Display Image Format */
|
||||
#define LCD_RDDSM 0x0E /* Read Display Signal Mode */
|
||||
#define LCD_RDDSDR 0x0F /* Read Display Self-Diagnostic Result */
|
||||
#define LCD_SPLIN 0x10 /* Enter Sleep Mode */
|
||||
#define LCD_SLEEP_OUT 0x11 /* Sleep out register */
|
||||
#define LCD_PTLON 0x12 /* Partial Mode ON */
|
||||
#define LCD_NORMAL_MODE_ON 0x13 /* Normal Display Mode ON */
|
||||
#define LCD_DINVOFF 0x20 /* Display Inversion OFF */
|
||||
#define LCD_DINVON 0x21 /* Display Inversion ON */
|
||||
#define LCD_GAMMA 0x26 /* Gamma register */
|
||||
#define LCD_DISPLAY_OFF 0x28 /* Display off register */
|
||||
#define LCD_DISPLAY_ON 0x29 /* Display on register */
|
||||
#define LCD_COLUMN_ADDR 0x2A /* Colomn address register */
|
||||
#define LCD_PAGE_ADDR 0x2B /* Page address register */
|
||||
#define LCD_GRAM 0x2C /* GRAM register */
|
||||
#define LCD_RGBSET 0x2D /* Color SET */
|
||||
#define LCD_RAMRD 0x2E /* Memory Read */
|
||||
#define LCD_PLTAR 0x30 /* Partial Area */
|
||||
#define LCD_VSCRDEF 0x33 /* Vertical Scrolling Definition */
|
||||
#define LCD_TEOFF 0x34 /* Tearing Effect Line OFF */
|
||||
#define LCD_TEON 0x35 /* Tearing Effect Line ON */
|
||||
#define LCD_MAC 0x36 /* Memory Access Control register*/
|
||||
#define LCD_VSCRSADD 0x37 /* Vertical Scrolling Start Address */
|
||||
#define LCD_IDMOFF 0x38 /* Idle Mode OFF */
|
||||
#define LCD_IDMON 0x39 /* Idle Mode ON */
|
||||
#define LCD_PIXEL_FORMAT 0x3A /* Pixel Format register */
|
||||
#define LCD_WRITE_MEM_CONTINUE 0x3C /* Write Memory Continue */
|
||||
#define LCD_READ_MEM_CONTINUE 0x3E /* Read Memory Continue */
|
||||
#define LCD_SET_TEAR_SCANLINE 0x44 /* Set Tear Scanline */
|
||||
#define LCD_GET_SCANLINE 0x45 /* Get Scanline */
|
||||
#define LCD_WDB 0x51 /* Write Brightness Display register */
|
||||
#define LCD_RDDISBV 0x52 /* Read Display Brightness */
|
||||
#define LCD_WCD 0x53 /* Write Control Display register*/
|
||||
#define LCD_RDCTRLD 0x54 /* Read CTRL Display */
|
||||
#define LCD_WRCABC 0x55 /* Write Content Adaptive Brightness Control */
|
||||
#define LCD_RDCABC 0x56 /* Read Content Adaptive Brightness Control */
|
||||
#define LCD_WRITE_CABC 0x5E /* Write CABC Minimum Brightness */
|
||||
#define LCD_READ_CABC 0x5F /* Read CABC Minimum Brightness */
|
||||
#define LCD_READ_ID1 0xDA /* Read ID1 */
|
||||
#define LCD_READ_ID2 0xDB /* Read ID2 */
|
||||
#define LCD_READ_ID3 0xDC /* Read ID3 */
|
||||
|
||||
/* Level 2 Commands */
|
||||
#define LCD_RGB_INTERFACE 0xB0 /* RGB Interface Signal Control */
|
||||
#define LCD_FRMCTR1 0xB1 /* Frame Rate Control (In Normal Mode) */
|
||||
#define LCD_FRMCTR2 0xB2 /* Frame Rate Control (In Idle Mode) */
|
||||
#define LCD_FRMCTR3 0xB3 /* Frame Rate Control (In Partial Mode) */
|
||||
#define LCD_INVTR 0xB4 /* Display Inversion Control */
|
||||
#define LCD_BPC 0xB5 /* Blanking Porch Control register */
|
||||
#define LCD_DFC 0xB6 /* Display Function Control register */
|
||||
#define LCD_ETMOD 0xB7 /* Entry Mode Set */
|
||||
#define LCD_BACKLIGHT1 0xB8 /* Backlight Control 1 */
|
||||
#define LCD_BACKLIGHT2 0xB9 /* Backlight Control 2 */
|
||||
#define LCD_BACKLIGHT3 0xBA /* Backlight Control 3 */
|
||||
#define LCD_BACKLIGHT4 0xBB /* Backlight Control 4 */
|
||||
#define LCD_BACKLIGHT5 0xBC /* Backlight Control 5 */
|
||||
#define LCD_BACKLIGHT7 0xBE /* Backlight Control 7 */
|
||||
#define LCD_BACKLIGHT8 0xBF /* Backlight Control 8 */
|
||||
#define LCD_POWER1 0xC0 /* Power Control 1 register */
|
||||
#define LCD_POWER2 0xC1 /* Power Control 2 register */
|
||||
#define LCD_VCOM1 0xC5 /* VCOM Control 1 register */
|
||||
#define LCD_VCOM2 0xC7 /* VCOM Control 2 register */
|
||||
#define LCD_NVMWR 0xD0 /* NV Memory Write */
|
||||
#define LCD_NVMPKEY 0xD1 /* NV Memory Protection Key */
|
||||
#define LCD_RDNVM 0xD2 /* NV Memory Status Read */
|
||||
#define LCD_READ_ID4 0xD3 /* Read ID4 */
|
||||
#define LCD_PGAMMA 0xE0 /* Positive Gamma Correction register */
|
||||
#define LCD_NGAMMA 0xE1 /* Negative Gamma Correction register */
|
||||
#define LCD_DGAMCTRL1 0xE2 /* Digital Gamma Control 1 */
|
||||
#define LCD_DGAMCTRL2 0xE3 /* Digital Gamma Control 2 */
|
||||
#define LCD_INTERFACE 0xF6 /* Interface control register */
|
||||
|
||||
/* Extend register commands */
|
||||
#define LCD_POWERA 0xCB /* Power control A register */
|
||||
#define LCD_POWERB 0xCF /* Power control B register */
|
||||
#define LCD_DTCA 0xE8 /* Driver timing control A */
|
||||
#define LCD_DTCB 0xEA /* Driver timing control B */
|
||||
#define LCD_POWER_SEQ 0xED /* Power on sequence register */
|
||||
#define LCD_3GAMMA_EN 0xF2 /* 3 Gamma enable register */
|
||||
#define LCD_PRC 0xF7 /* Pump ratio control register */
|
||||
|
||||
/* Size of read registers */
|
||||
#define LCD_READ_ID4_SIZE 3 /* Size of Read ID4 */
|
||||
|
||||
/*############################### SPIx #######################################*/
|
||||
#define DISCOVERY_SPIx SPI5
|
||||
#define DISCOVERY_SPIx_CLK_ENABLE() __HAL_RCC_SPI5_CLK_ENABLE()
|
||||
#define DISCOVERY_SPIx_GPIO_PORT GPIOF /* GPIOF */
|
||||
#define DISCOVERY_SPIx_AF GPIO_AF5_SPI5
|
||||
#define DISCOVERY_SPIx_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
|
||||
#define DISCOVERY_SPIx_GPIO_CLK_DISABLE() __HAL_RCC_GPIOF_CLK_DISABLE()
|
||||
#define DISCOVERY_SPIx_SCK_PIN GPIO_PIN_7 /* PF.07 */
|
||||
#define DISCOVERY_SPIx_MISO_PIN GPIO_PIN_8 /* PF.08 */
|
||||
#define DISCOVERY_SPIx_MOSI_PIN GPIO_PIN_9 /* PF.09 */
|
||||
/* Maximum Timeout values for flags waiting loops. These timeouts are not based
|
||||
on accurate values, they just guarantee that the application will not remain
|
||||
stuck if the SPI communication is corrupted.
|
||||
You may modify these timeout values depending on CPU frequency and
|
||||
application conditions (interrupts routines ...). */
|
||||
#define SPIx_TIMEOUT_MAX ((uint32_t)0x1000)
|
||||
|
||||
/*################################ LCD #######################################*/
|
||||
/* Chip Select macro definition */
|
||||
#define LCD_CS_LOW() \
|
||||
HAL_GPIO_WritePin(LCD_NCS_GPIO_PORT, LCD_NCS_PIN, GPIO_PIN_RESET)
|
||||
#define LCD_CS_HIGH() \
|
||||
HAL_GPIO_WritePin(LCD_NCS_GPIO_PORT, LCD_NCS_PIN, GPIO_PIN_SET)
|
||||
|
||||
/* Set WRX High to send data */
|
||||
#define LCD_WRX_LOW() \
|
||||
HAL_GPIO_WritePin(LCD_WRX_GPIO_PORT, LCD_WRX_PIN, GPIO_PIN_RESET)
|
||||
#define LCD_WRX_HIGH() \
|
||||
HAL_GPIO_WritePin(LCD_WRX_GPIO_PORT, LCD_WRX_PIN, GPIO_PIN_SET)
|
||||
|
||||
/* Set WRX High to send data */
|
||||
#define LCD_RDX_LOW() \
|
||||
HAL_GPIO_WritePin(LCD_RDX_GPIO_PORT, LCD_RDX_PIN, GPIO_PIN_RESET)
|
||||
#define LCD_RDX_HIGH() \
|
||||
HAL_GPIO_WritePin(LCD_RDX_GPIO_PORT, LCD_RDX_PIN, GPIO_PIN_SET)
|
||||
|
||||
/**
|
||||
* @brief LCD Control pin
|
||||
*/
|
||||
#define LCD_NCS_PIN GPIO_PIN_2
|
||||
#define LCD_NCS_GPIO_PORT GPIOC
|
||||
#define LCD_NCS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
|
||||
#define LCD_NCS_GPIO_CLK_DISABLE() __HAL_RCC_GPIOC_CLK_DISABLE()
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
/**
|
||||
* @brief LCD Command/data pin
|
||||
*/
|
||||
#define LCD_WRX_PIN GPIO_PIN_13
|
||||
#define LCD_WRX_GPIO_PORT GPIOD
|
||||
#define LCD_WRX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
|
||||
#define LCD_WRX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOD_CLK_DISABLE()
|
||||
|
||||
#define LCD_RDX_PIN GPIO_PIN_12
|
||||
#define LCD_RDX_GPIO_PORT GPIOD
|
||||
#define LCD_RDX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
|
||||
#define LCD_RDX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOD_CLK_DISABLE()
|
||||
|
||||
static SPI_HandleTypeDef SpiHandle;
|
||||
uint32_t SpixTimeout =
|
||||
SPIx_TIMEOUT_MAX; /*<! Value of Timeout when SPI communication fails */
|
||||
|
||||
/* SPIx bus function */
|
||||
static void SPIx_Init(void);
|
||||
static void ili9341_Write(uint16_t Value);
|
||||
static uint32_t ili9341_Read(uint8_t ReadSize);
|
||||
static void ili9341_Error(void);
|
||||
|
||||
/**
|
||||
* @brief SPIx Bus initialization
|
||||
*/
|
||||
static void SPIx_Init(void) {
|
||||
if (HAL_SPI_GetState(&SpiHandle) == HAL_SPI_STATE_RESET) {
|
||||
/* SPI configuration -----------------------------------------------------*/
|
||||
SpiHandle.Instance = DISCOVERY_SPIx;
|
||||
/* SPI baudrate is set to 5.6 MHz (PCLK2/SPI_BaudRatePrescaler = 90/16
|
||||
= 5.625 MHz) to verify these constraints:
|
||||
- ILI9341 LCD SPI interface max baudrate is 10MHz for write and 6.66MHz
|
||||
for read
|
||||
- l3gd20 SPI interface max baudrate is 10MHz for write/read
|
||||
- PCLK2 frequency is set to 90 MHz
|
||||
*/
|
||||
SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
|
||||
|
||||
/* On STM32F429I-Discovery, LCD ID cannot be read then keep a common
|
||||
* configuration */
|
||||
/* for LCD and GYRO (SPI_DIRECTION_2LINES) */
|
||||
/* Note: To read a register a LCD, SPI_DIRECTION_1LINE should be set */
|
||||
SpiHandle.Init.Direction = SPI_DIRECTION_2LINES;
|
||||
SpiHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
|
||||
SpiHandle.Init.CLKPolarity = SPI_POLARITY_LOW;
|
||||
SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
|
||||
SpiHandle.Init.CRCPolynomial = 7;
|
||||
SpiHandle.Init.DataSize = SPI_DATASIZE_8BIT;
|
||||
SpiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
|
||||
SpiHandle.Init.NSS = SPI_NSS_SOFT;
|
||||
SpiHandle.Init.TIMode = SPI_TIMODE_DISABLED;
|
||||
SpiHandle.Init.Mode = SPI_MODE_MASTER;
|
||||
|
||||
HAL_SPI_Init(&SpiHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SPIx error treatment function.
|
||||
*/
|
||||
static void ili9341_Error(void) {
|
||||
/* De-initialize the SPI communication BUS */
|
||||
HAL_SPI_DeInit(&SpiHandle);
|
||||
|
||||
/* Re- Initialize the SPI communication BUS */
|
||||
SPIx_Init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads 4 bytes from device.
|
||||
* @param ReadSize: Number of bytes to read (max 4 bytes)
|
||||
* @retval Value read on the SPI
|
||||
*/
|
||||
static uint32_t ili9341_Read(uint8_t ReadSize) {
|
||||
HAL_StatusTypeDef status = HAL_OK;
|
||||
uint32_t readvalue;
|
||||
|
||||
status =
|
||||
HAL_SPI_Receive(&SpiHandle, (uint8_t*)&readvalue, ReadSize, SpixTimeout);
|
||||
|
||||
/* Check the communication status */
|
||||
if (status != HAL_OK) {
|
||||
/* Re-Initialize the BUS */
|
||||
ili9341_Error();
|
||||
}
|
||||
|
||||
return readvalue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a byte to device.
|
||||
* @param Value: value to be written
|
||||
*/
|
||||
static void ili9341_Write(uint16_t Value) {
|
||||
HAL_StatusTypeDef status = HAL_OK;
|
||||
|
||||
status = HAL_SPI_Transmit(&SpiHandle, (uint8_t*)&Value, 1, SpixTimeout);
|
||||
|
||||
/* Check the communication status */
|
||||
if (status != HAL_OK) {
|
||||
/* Re-Initialize the BUS */
|
||||
ili9341_Error();
|
||||
}
|
||||
}
|
||||
|
||||
void ili9341_spi_init(void) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
|
||||
/* Configure NCS in Output Push-Pull mode */
|
||||
LCD_WRX_GPIO_CLK_ENABLE();
|
||||
GPIO_InitStructure.Pin = LCD_WRX_PIN;
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
|
||||
HAL_GPIO_Init(LCD_WRX_GPIO_PORT, &GPIO_InitStructure);
|
||||
|
||||
LCD_RDX_GPIO_CLK_ENABLE();
|
||||
GPIO_InitStructure.Pin = LCD_RDX_PIN;
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
|
||||
HAL_GPIO_Init(LCD_RDX_GPIO_PORT, &GPIO_InitStructure);
|
||||
|
||||
/* Configure the LCD Control pins ----------------------------------------*/
|
||||
LCD_NCS_GPIO_CLK_ENABLE();
|
||||
|
||||
/* Configure NCS in Output Push-Pull mode */
|
||||
GPIO_InitStructure.Pin = LCD_NCS_PIN;
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
|
||||
HAL_GPIO_Init(LCD_NCS_GPIO_PORT, &GPIO_InitStructure);
|
||||
|
||||
/* Set or Reset the control line */
|
||||
LCD_CS_LOW();
|
||||
LCD_CS_HIGH();
|
||||
|
||||
/* Enable SPIx clock */
|
||||
DISCOVERY_SPIx_CLK_ENABLE();
|
||||
|
||||
/* Enable DISCOVERY_SPI GPIO clock */
|
||||
DISCOVERY_SPIx_GPIO_CLK_ENABLE();
|
||||
|
||||
/* configure SPI SCK, MOSI and MISO */
|
||||
GPIO_InitStructure.Pin = (DISCOVERY_SPIx_SCK_PIN | DISCOVERY_SPIx_MOSI_PIN |
|
||||
DISCOVERY_SPIx_MISO_PIN);
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_MEDIUM;
|
||||
GPIO_InitStructure.Alternate = DISCOVERY_SPIx_AF;
|
||||
HAL_GPIO_Init(DISCOVERY_SPIx_GPIO_PORT, &GPIO_InitStructure);
|
||||
|
||||
SPIx_Init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes register value.
|
||||
*/
|
||||
void ili9341_WriteData(uint16_t RegValue) {
|
||||
/* Set WRX to send data */
|
||||
LCD_WRX_HIGH();
|
||||
|
||||
/* Reset LCD control line(/CS) and Send data */
|
||||
LCD_CS_LOW();
|
||||
ili9341_Write(RegValue);
|
||||
|
||||
/* Deselect: Chip Select high */
|
||||
LCD_CS_HIGH();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes register address.
|
||||
*/
|
||||
void ili9341_WriteReg(uint8_t Reg) {
|
||||
/* Reset WRX to send command */
|
||||
LCD_WRX_LOW();
|
||||
|
||||
/* Reset LCD control line(/CS) and Send command */
|
||||
LCD_CS_LOW();
|
||||
ili9341_Write(Reg);
|
||||
|
||||
/* Deselect: Chip Select high */
|
||||
LCD_CS_HIGH();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads register value.
|
||||
* @param RegValue Address of the register to read
|
||||
* @param ReadSize Number of bytes to read
|
||||
* @retval Content of the register value
|
||||
*/
|
||||
uint32_t ili9341_ReadData(uint16_t RegValue, uint8_t ReadSize) {
|
||||
uint32_t readvalue = 0;
|
||||
|
||||
/* Select: Chip Select low */
|
||||
LCD_CS_LOW();
|
||||
|
||||
/* Reset WRX to send command */
|
||||
LCD_WRX_LOW();
|
||||
|
||||
ili9341_Write(RegValue);
|
||||
|
||||
readvalue = ili9341_Read(ReadSize);
|
||||
|
||||
/* Set WRX to send data */
|
||||
LCD_WRX_HIGH();
|
||||
|
||||
/* Deselect: Chip Select high */
|
||||
LCD_CS_HIGH();
|
||||
|
||||
return readvalue;
|
||||
}
|
||||
|
||||
void ili9341_init(void) {
|
||||
/* Initialize ILI9341 low level bus layer ----------------------------------*/
|
||||
ili9341_spi_init();
|
||||
|
||||
ili9341_WriteReg(LCD_DISPLAY_OFF);
|
||||
|
||||
/* Configure LCD */
|
||||
ili9341_WriteReg(0xCA);
|
||||
ili9341_WriteData(0xC3);
|
||||
ili9341_WriteData(0x08);
|
||||
ili9341_WriteData(0x50);
|
||||
ili9341_WriteReg(LCD_POWERB);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0xC1);
|
||||
ili9341_WriteData(0x30);
|
||||
ili9341_WriteReg(LCD_POWER_SEQ);
|
||||
ili9341_WriteData(0x64);
|
||||
ili9341_WriteData(0x03);
|
||||
ili9341_WriteData(0x12);
|
||||
ili9341_WriteData(0x81);
|
||||
ili9341_WriteReg(LCD_DTCA);
|
||||
ili9341_WriteData(0x85);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x78);
|
||||
ili9341_WriteReg(LCD_POWERA);
|
||||
ili9341_WriteData(0x39);
|
||||
ili9341_WriteData(0x2C);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x34);
|
||||
ili9341_WriteData(0x02);
|
||||
ili9341_WriteReg(LCD_PRC);
|
||||
ili9341_WriteData(0x20);
|
||||
ili9341_WriteReg(LCD_DTCB);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteReg(LCD_FRMCTR1);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x1B);
|
||||
ili9341_WriteReg(LCD_DFC);
|
||||
ili9341_WriteData(0x0A);
|
||||
ili9341_WriteData(0xA2);
|
||||
ili9341_WriteReg(LCD_POWER1);
|
||||
ili9341_WriteData(0x10);
|
||||
ili9341_WriteReg(LCD_POWER2);
|
||||
ili9341_WriteData(0x10);
|
||||
ili9341_WriteReg(LCD_VCOM1);
|
||||
ili9341_WriteData(0x45);
|
||||
ili9341_WriteData(0x15);
|
||||
ili9341_WriteReg(LCD_VCOM2);
|
||||
ili9341_WriteData(0x90);
|
||||
ili9341_WriteReg(LCD_MAC);
|
||||
ili9341_WriteData(0xC8);
|
||||
ili9341_WriteReg(LCD_3GAMMA_EN);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteReg(LCD_RGB_INTERFACE);
|
||||
ili9341_WriteData(0xC2);
|
||||
ili9341_WriteReg(LCD_DFC);
|
||||
ili9341_WriteData(0x0A);
|
||||
ili9341_WriteData(0xA7);
|
||||
ili9341_WriteData(0x27);
|
||||
ili9341_WriteData(0x04);
|
||||
|
||||
/* Colomn address set */
|
||||
ili9341_WriteReg(LCD_COLUMN_ADDR);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0xEF);
|
||||
/* Page address set */
|
||||
ili9341_WriteReg(LCD_PAGE_ADDR);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x01);
|
||||
ili9341_WriteData(0x3F);
|
||||
ili9341_WriteReg(LCD_INTERFACE);
|
||||
ili9341_WriteData(0x01);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x06);
|
||||
|
||||
ili9341_WriteReg(LCD_GRAM);
|
||||
HAL_Delay(200);
|
||||
|
||||
ili9341_WriteReg(LCD_GAMMA);
|
||||
ili9341_WriteData(0x01);
|
||||
|
||||
ili9341_WriteReg(LCD_PGAMMA);
|
||||
ili9341_WriteData(0x0F);
|
||||
ili9341_WriteData(0x29);
|
||||
ili9341_WriteData(0x24);
|
||||
ili9341_WriteData(0x0C);
|
||||
ili9341_WriteData(0x0E);
|
||||
ili9341_WriteData(0x09);
|
||||
ili9341_WriteData(0x4E);
|
||||
ili9341_WriteData(0x78);
|
||||
ili9341_WriteData(0x3C);
|
||||
ili9341_WriteData(0x09);
|
||||
ili9341_WriteData(0x13);
|
||||
ili9341_WriteData(0x05);
|
||||
ili9341_WriteData(0x17);
|
||||
ili9341_WriteData(0x11);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteReg(LCD_NGAMMA);
|
||||
ili9341_WriteData(0x00);
|
||||
ili9341_WriteData(0x16);
|
||||
ili9341_WriteData(0x1B);
|
||||
ili9341_WriteData(0x04);
|
||||
ili9341_WriteData(0x11);
|
||||
ili9341_WriteData(0x07);
|
||||
ili9341_WriteData(0x31);
|
||||
ili9341_WriteData(0x33);
|
||||
ili9341_WriteData(0x42);
|
||||
ili9341_WriteData(0x05);
|
||||
ili9341_WriteData(0x0C);
|
||||
ili9341_WriteData(0x0A);
|
||||
ili9341_WriteData(0x28);
|
||||
ili9341_WriteData(0x2F);
|
||||
ili9341_WriteData(0x0F);
|
||||
|
||||
ili9341_WriteReg(LCD_SLEEP_OUT);
|
||||
HAL_Delay(200);
|
||||
ili9341_WriteReg(LCD_DISPLAY_ON);
|
||||
/* GRAM start writing */
|
||||
ili9341_WriteReg(LCD_GRAM);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
#ifndef _ILI9341_SPI_H
|
||||
#define _ILI9341_SPI_H
|
||||
|
||||
#define ILI9341_HSYNC ((uint32_t)9) /* Horizontal synchronization */
|
||||
#define ILI9341_HBP ((uint32_t)29) /* Horizontal back porch */
|
||||
#define ILI9341_HFP ((uint32_t)2) /* Horizontal front porch */
|
||||
#define ILI9341_VSYNC ((uint32_t)1) /* Vertical synchronization */
|
||||
#define ILI9341_VBP ((uint32_t)3) /* Vertical back porch */
|
||||
#define ILI9341_VFP ((uint32_t)2) /* Vertical front porch */
|
||||
|
||||
void ili9341_init(void);
|
||||
|
||||
#endif //_ILI9341_SPI_H
|
@ -0,0 +1,351 @@
|
||||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include TREZOR_BOARD
|
||||
#include STM32_HAL_H
|
||||
|
||||
#include "xdisplay.h"
|
||||
|
||||
#ifdef USE_CONSUMPTION_MASK
|
||||
#include "consumption_mask.h"
|
||||
#endif
|
||||
|
||||
#if (DISPLAY_RESX != 128) || (DISPLAY_RESY != 64)
|
||||
#error "Incompatible display resolution"
|
||||
#endif
|
||||
|
||||
// This file implements display driver for monochromatic display V-2864KSWEG01
|
||||
// with 128x64 resolution connected to CPU via SPI interface.
|
||||
//
|
||||
// This type of display is used with T3T1 model (Trezor TS3)
|
||||
|
||||
// Display driver context.
|
||||
typedef struct {
|
||||
// SPI driver instance
|
||||
SPI_HandleTypeDef spi;
|
||||
// Frame buffer (8-bit Mono)
|
||||
uint8_t framebuf[DISPLAY_RESX * DISPLAY_RESY];
|
||||
// Current display orientation (0 or 180)
|
||||
int orientation_angle;
|
||||
// Current backlight level ranging from 0 to 255
|
||||
int backlight_level;
|
||||
} display_driver_t;
|
||||
|
||||
// Display driver instance
|
||||
static display_driver_t g_display_driver;
|
||||
|
||||
// Display controller registers
|
||||
#define OLED_SETCONTRAST 0x81
|
||||
#define OLED_DISPLAYALLON_RESUME 0xA4
|
||||
#define OLED_DISPLAYALLON 0xA5
|
||||
#define OLED_NORMALDISPLAY 0xA6
|
||||
#define OLED_INVERTDISPLAY 0xA7
|
||||
#define OLED_DISPLAYOFF 0xAE
|
||||
#define OLED_DISPLAYON 0xAF
|
||||
#define OLED_SETDISPLAYOFFSET 0xD3
|
||||
#define OLED_SETCOMPINS 0xDA
|
||||
#define OLED_SETVCOMDETECT 0xDB
|
||||
#define OLED_SETDISPLAYCLOCKDIV 0xD5
|
||||
#define OLED_SETPRECHARGE 0xD9
|
||||
#define OLED_SETMULTIPLEX 0xA8
|
||||
#define OLED_SETLOWCOLUMN 0x00
|
||||
#define OLED_SETHIGHCOLUMN 0x10
|
||||
#define OLED_SETSTARTLINE 0x40
|
||||
#define OLED_MEMORYMODE 0x20
|
||||
#define OLED_COMSCANINC 0xC0
|
||||
#define OLED_COMSCANDEC 0xC8
|
||||
#define OLED_SEGREMAP 0xA0
|
||||
#define OLED_CHARGEPUMP 0x8D
|
||||
|
||||
// Display controller initialization sequence
|
||||
static const uint8_t vg_2864ksweg01_init_seq[] = {OLED_DISPLAYOFF,
|
||||
OLED_SETDISPLAYCLOCKDIV,
|
||||
0x80,
|
||||
OLED_SETMULTIPLEX,
|
||||
0x3F, // 128x64
|
||||
OLED_SETDISPLAYOFFSET,
|
||||
0x00,
|
||||
OLED_SETSTARTLINE | 0x00,
|
||||
OLED_CHARGEPUMP,
|
||||
0x14,
|
||||
OLED_MEMORYMODE,
|
||||
0x00,
|
||||
OLED_SEGREMAP | 0x01,
|
||||
OLED_COMSCANDEC,
|
||||
OLED_SETCOMPINS,
|
||||
0x12, // 128x64
|
||||
OLED_SETCONTRAST,
|
||||
0xCF,
|
||||
OLED_SETPRECHARGE,
|
||||
0xF1,
|
||||
OLED_SETVCOMDETECT,
|
||||
0x40,
|
||||
OLED_DISPLAYALLON_RESUME,
|
||||
OLED_NORMALDISPLAY,
|
||||
OLED_DISPLAYON};
|
||||
|
||||
// Configures SPI driver/controller
|
||||
static bool display_init_spi(display_driver_t *drv) {
|
||||
drv->spi.Instance = OLED_SPI;
|
||||
drv->spi.State = HAL_SPI_STATE_RESET;
|
||||
drv->spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
|
||||
drv->spi.Init.Direction = SPI_DIRECTION_2LINES;
|
||||
drv->spi.Init.CLKPhase = SPI_PHASE_1EDGE;
|
||||
drv->spi.Init.CLKPolarity = SPI_POLARITY_LOW;
|
||||
drv->spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
|
||||
drv->spi.Init.CRCPolynomial = 7;
|
||||
drv->spi.Init.DataSize = SPI_DATASIZE_8BIT;
|
||||
drv->spi.Init.FirstBit = SPI_FIRSTBIT_MSB;
|
||||
drv->spi.Init.NSS = SPI_NSS_HARD_OUTPUT;
|
||||
drv->spi.Init.TIMode = SPI_TIMODE_DISABLE;
|
||||
drv->spi.Init.Mode = SPI_MODE_MASTER;
|
||||
|
||||
return (HAL_OK == HAL_SPI_Init(&drv->spi)) ? true : false;
|
||||
}
|
||||
|
||||
// Sends specified number of bytes to the display via SPI interface
|
||||
static void display_send_bytes(display_driver_t *drv, const uint8_t *data,
|
||||
size_t len) {
|
||||
volatile int32_t timeout = 1000; // !@# why???
|
||||
for (int i = 0; i < timeout; i++)
|
||||
;
|
||||
|
||||
if (HAL_OK != HAL_SPI_Transmit(&drv->spi, (uint8_t *)data, len, 1000)) {
|
||||
// TODO: error
|
||||
return;
|
||||
}
|
||||
while (HAL_SPI_STATE_READY != HAL_SPI_GetState(&drv->spi)) {
|
||||
}
|
||||
}
|
||||
|
||||
#define COLLECT_ROW_BYTE(src) \
|
||||
(0 | (*(src + (0 * DISPLAY_RESX)) ? 128 : 0) | \
|
||||
(*(src + (1 * DISPLAY_RESX)) ? 64 : 0) | \
|
||||
(*(src + (2 * DISPLAY_RESX)) ? 32 : 0) | \
|
||||
(*(src + (3 * DISPLAY_RESX)) ? 16 : 0) | \
|
||||
(*(src + (4 * DISPLAY_RESX)) ? 8 : 0) | \
|
||||
(*(src + (5 * DISPLAY_RESX)) ? 4 : 0) | \
|
||||
(*(src + (6 * DISPLAY_RESX)) ? 2 : 0) | \
|
||||
(*(src + (7 * DISPLAY_RESX)) ? 1 : 0))
|
||||
|
||||
// Copies the framebuffer to the display via SPI interface
|
||||
static void display_sync_with_fb(display_driver_t *drv) {
|
||||
static const uint8_t cursor_set_seq[3] = {OLED_SETLOWCOLUMN | 0x00,
|
||||
OLED_SETHIGHCOLUMN | 0x00,
|
||||
OLED_SETSTARTLINE | 0x00};
|
||||
|
||||
// SPI select
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
|
||||
// Set the cursor to the screen top-left corner
|
||||
display_send_bytes(drv, &cursor_set_seq[0], sizeof(cursor_set_seq));
|
||||
|
||||
// SPI deselect
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
|
||||
// Set to DATA
|
||||
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_SET);
|
||||
// SPI select
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
|
||||
|
||||
// Send whole framebuffer to the display
|
||||
for (int y = 0; y < DISPLAY_RESY / 8; y++) {
|
||||
uint8_t buff[DISPLAY_RESX];
|
||||
uint8_t *src = &drv->framebuf[y * DISPLAY_RESX * 8];
|
||||
|
||||
if (drv->orientation_angle == 0) {
|
||||
for (int x = 0; x < DISPLAY_RESX; x++) {
|
||||
buff[x] = COLLECT_ROW_BYTE(src);
|
||||
src++;
|
||||
}
|
||||
} else {
|
||||
for (int x = DISPLAY_RESX - 1; x >= 0; x--) {
|
||||
buff[x] = COLLECT_ROW_BYTE(src);
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
if (HAL_OK != HAL_SPI_Transmit(&drv->spi, &buff[0], sizeof(buff), 1000)) {
|
||||
// TODO: error
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (HAL_SPI_STATE_READY != HAL_SPI_GetState(&drv->spi)) {
|
||||
}
|
||||
|
||||
// SPI deselect
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
|
||||
// Set to CMD
|
||||
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
void display_init(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
memset(drv, 0, sizeof(display_driver_t));
|
||||
drv->backlight_level = 255;
|
||||
|
||||
OLED_DC_CLK_ENA();
|
||||
OLED_CS_CLK_ENA();
|
||||
OLED_RST_CLK_ENA();
|
||||
OLED_SPI_SCK_CLK_ENA();
|
||||
OLED_SPI_MOSI_CLK_ENA();
|
||||
OLED_SPI_CLK_ENA();
|
||||
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
|
||||
// Set GPIO for OLED display
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
GPIO_InitStructure.Alternate = 0;
|
||||
GPIO_InitStructure.Pin = OLED_CS_PIN;
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
|
||||
HAL_GPIO_Init(OLED_CS_PORT, &GPIO_InitStructure);
|
||||
GPIO_InitStructure.Pin = OLED_DC_PIN;
|
||||
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET);
|
||||
HAL_GPIO_Init(OLED_DC_PORT, &GPIO_InitStructure);
|
||||
GPIO_InitStructure.Pin = OLED_RST_PIN;
|
||||
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET);
|
||||
HAL_GPIO_Init(OLED_RST_PORT, &GPIO_InitStructure);
|
||||
|
||||
// Enable SPI 1 for OLED display
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
GPIO_InitStructure.Alternate = OLED_SPI_AF;
|
||||
GPIO_InitStructure.Pin = OLED_SPI_SCK_PIN;
|
||||
HAL_GPIO_Init(OLED_SPI_SCK_PORT, &GPIO_InitStructure);
|
||||
GPIO_InitStructure.Pin = OLED_SPI_MOSI_PIN;
|
||||
HAL_GPIO_Init(OLED_SPI_MOSI_PORT, &GPIO_InitStructure);
|
||||
|
||||
// Initialize SPI controller
|
||||
display_init_spi(drv);
|
||||
|
||||
// Set to CMD
|
||||
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET);
|
||||
// SPI deselect
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
|
||||
|
||||
// Reset the LCD
|
||||
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET);
|
||||
HAL_Delay(1);
|
||||
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET);
|
||||
HAL_Delay(1);
|
||||
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET);
|
||||
|
||||
// SPI select
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET);
|
||||
// Send initialization command sequence
|
||||
display_send_bytes(drv, &vg_2864ksweg01_init_seq[0],
|
||||
sizeof(vg_2864ksweg01_init_seq));
|
||||
// SPI deselect
|
||||
HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET);
|
||||
|
||||
// Clear display internal framebuffer
|
||||
display_sync_with_fb(drv);
|
||||
}
|
||||
|
||||
void display_reinit(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
memset(drv, 0, sizeof(display_driver_t));
|
||||
drv->backlight_level = 255;
|
||||
|
||||
display_init_spi(drv);
|
||||
}
|
||||
|
||||
void display_finish_actions(void) {
|
||||
/// Not used and intentionally left empty
|
||||
}
|
||||
|
||||
int display_set_backlight(int level) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
drv->backlight_level = 255;
|
||||
return drv->backlight_level;
|
||||
}
|
||||
|
||||
int display_get_backlight(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
return drv->backlight_level;
|
||||
}
|
||||
|
||||
int display_set_orientation(int angle) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
if (angle != drv->orientation_angle) {
|
||||
if (angle == 0 || angle == 180) {
|
||||
drv->orientation_angle = angle;
|
||||
display_sync_with_fb(drv);
|
||||
}
|
||||
}
|
||||
|
||||
return drv->orientation_angle;
|
||||
}
|
||||
|
||||
int display_get_orientation(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
return drv->orientation_angle;
|
||||
}
|
||||
|
||||
void *display_get_frame_addr(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
return &drv->framebuf[0];
|
||||
}
|
||||
|
||||
void display_refresh(void) {
|
||||
display_driver_t *drv = &g_display_driver;
|
||||
|
||||
#if defined USE_CONSUMPTION_MASK && !defined BOARDLOADER
|
||||
// This is an intentional randomization of the consumption masking algorithm
|
||||
// after every change on the display
|
||||
consumption_mask_randomize();
|
||||
#endif
|
||||
|
||||
// Sends the current frame buffer to the display
|
||||
display_sync_with_fb(drv);
|
||||
}
|
||||
|
||||
const char *display_save(const char *prefix) { return NULL; }
|
||||
|
||||
void display_clear_save(void) {}
|
||||
|
||||
void display_set_compatible_settings() {}
|
||||
|
||||
/*
|
||||
|
||||
// Fills a rectangle with a specified color
|
||||
void display_fill(dma2d_params_t *dp) {
|
||||
|
||||
}
|
||||
|
||||
// Copies a MONO1P bitmap to specified rectangle
|
||||
void display_copy_mono1p(dma2d_params_t *dp) {
|
||||
|
||||
}
|
||||
|
||||
*/
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue