mirror of
https://github.com/CTCaer/hekate
synced 2024-11-16 08:59:26 +00:00
185526d134
BDK will allow developers to use the full collection of drivers, with limited editing, if any, for making payloads for Nintendo Switch. Using a single source for everything will also help decoupling Switch specific code and easily port it to other Tegra X1/X1+ platforms. And maybe even to lower targets. Everything is now centrilized into bdk folder. Every module or project can utilize it by simply including it. This is just the start and it will continue to improve.
691 lines
24 KiB
C
691 lines
24 KiB
C
/**
|
|
* @file lv_vdraw.c
|
|
*
|
|
*/
|
|
|
|
#include "lv_draw_vbasic.h"
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "../lv_hal/lv_hal_disp.h"
|
|
#include "../lv_misc/lv_area.h"
|
|
#include "../lv_misc/lv_font.h"
|
|
#include "../lv_misc/lv_color.h"
|
|
#include "../lv_misc/lv_log.h"
|
|
|
|
#if LV_VDB_SIZE != 0
|
|
|
|
#include <stddef.h>
|
|
#include "../lv_core/lv_vdb.h"
|
|
#include "lv_draw.h"
|
|
|
|
/*********************
|
|
* INCLUDES
|
|
*********************/
|
|
|
|
/*********************
|
|
* DEFINES
|
|
*********************/
|
|
#define VFILL_HW_ACC_SIZE_LIMIT 50 /*Always fill < 50 px with 'sw_color_fill' because of the hw. init overhead*/
|
|
|
|
#ifndef LV_ATTRIBUTE_MEM_ALIGN
|
|
#define LV_ATTRIBUTE_MEM_ALIGN
|
|
#endif
|
|
|
|
/**********************
|
|
* TYPEDEFS
|
|
**********************/
|
|
|
|
/**********************
|
|
* STATIC PROTOTYPES
|
|
**********************/
|
|
static void sw_mem_blend(lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa);
|
|
static void sw_color_fill(lv_area_t * mem_area, lv_color_t * mem, const lv_area_t * fill_area, lv_color_t color, lv_opa_t opa);
|
|
|
|
#if LV_COLOR_SCREEN_TRANSP
|
|
static inline lv_color_t color_mix_2_alpha(lv_color_t bg_color, lv_opa_t bg_opa, lv_color_t fg_color, lv_opa_t fg_opa);
|
|
#endif
|
|
|
|
/**********************
|
|
* STATIC VARIABLES
|
|
**********************/
|
|
|
|
/**********************
|
|
* MACROS
|
|
**********************/
|
|
|
|
/**********************
|
|
* GLOBAL FUNCTIONS
|
|
**********************/
|
|
|
|
/**
|
|
* Put a pixel in the Virtual Display Buffer
|
|
* @param x pixel x coordinate
|
|
* @param y pixel y coordinate
|
|
* @param mask_p fill only on this mask (truncated to VDB area)
|
|
* @param color pixel color
|
|
* @param opa opacity of the area (0..255)
|
|
*/
|
|
void lv_vpx(lv_coord_t x, lv_coord_t y, const lv_area_t * mask_p, lv_color_t color, lv_opa_t opa)
|
|
{
|
|
if(opa < LV_OPA_MIN) return;
|
|
if(opa > LV_OPA_MAX) opa = LV_OPA_COVER;
|
|
|
|
lv_vdb_t * vdb_p = lv_vdb_get();
|
|
if(!vdb_p) {
|
|
LV_LOG_WARN("Invalid VDB pointer");
|
|
return;
|
|
}
|
|
|
|
/*Pixel out of the mask*/
|
|
if(x < mask_p->x1 || x > mask_p->x2 ||
|
|
y < mask_p->y1 || y > mask_p->y2) {
|
|
return;
|
|
}
|
|
|
|
uint32_t vdb_width = lv_area_get_width(&vdb_p->area);
|
|
|
|
/*Make the coordinates relative to VDB*/
|
|
x -= vdb_p->area.x1;
|
|
y -= vdb_p->area.y1;
|
|
|
|
lv_disp_t * disp = lv_disp_get_active();
|
|
if(disp->driver.vdb_wr) {
|
|
disp->driver.vdb_wr((uint8_t *)vdb_p->buf, vdb_width, x, y, color, opa);
|
|
} else {
|
|
lv_color_t * vdb_px_p = vdb_p->buf + y * vdb_width + x;
|
|
#if LV_COLOR_SCREEN_TRANSP == 0
|
|
if(opa == LV_OPA_COVER) {
|
|
*vdb_px_p = color;
|
|
} else {
|
|
*vdb_px_p = lv_color_mix(color, *vdb_px_p, opa);
|
|
}
|
|
#else
|
|
*vdb_px_p = color_mix_2_alpha(*vdb_px_p, (*vdb_px_p).alpha, color, opa);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Fill an area in the Virtual Display Buffer
|
|
* @param cords_p coordinates of the area to fill
|
|
* @param mask_p fill only o this mask (truncated to VDB area)
|
|
* @param color fill color
|
|
* @param opa opacity of the area (0..255)
|
|
*/
|
|
void lv_vfill(const lv_area_t * cords_p, const lv_area_t * mask_p,
|
|
lv_color_t color, lv_opa_t opa)
|
|
{
|
|
if(opa < LV_OPA_MIN) return;
|
|
if(opa > LV_OPA_MAX) opa = LV_OPA_COVER;
|
|
|
|
lv_area_t res_a;
|
|
bool union_ok;
|
|
lv_vdb_t * vdb_p = lv_vdb_get();
|
|
if(!vdb_p) {
|
|
LV_LOG_WARN("Invalid VDB pointer");
|
|
return;
|
|
}
|
|
|
|
/*Get the union of cord and mask*/
|
|
/* The mask is already truncated to the vdb size
|
|
* in 'lv_refr_area_with_vdb' function */
|
|
union_ok = lv_area_intersect(&res_a, cords_p, mask_p);
|
|
|
|
/*If there are common part of the three area then draw to the vdb*/
|
|
if(union_ok == false) return;
|
|
|
|
lv_area_t vdb_rel_a; /*Stores relative coordinates on vdb*/
|
|
vdb_rel_a.x1 = res_a.x1 - vdb_p->area.x1;
|
|
vdb_rel_a.y1 = res_a.y1 - vdb_p->area.y1;
|
|
vdb_rel_a.x2 = res_a.x2 - vdb_p->area.x1;
|
|
vdb_rel_a.y2 = res_a.y2 - vdb_p->area.y1;
|
|
|
|
lv_color_t * vdb_buf_tmp = vdb_p->buf;
|
|
uint32_t vdb_width = lv_area_get_width(&vdb_p->area);
|
|
/*Move the vdb_tmp to the first row*/
|
|
vdb_buf_tmp += vdb_width * vdb_rel_a.y1;
|
|
|
|
|
|
#if USE_LV_GPU
|
|
static LV_ATTRIBUTE_MEM_ALIGN lv_color_t color_array_tmp[LV_HOR_RES]; /*Used by 'lv_disp_mem_blend'*/
|
|
static lv_coord_t last_width = -1;
|
|
|
|
lv_coord_t w = lv_area_get_width(&vdb_rel_a);
|
|
/*Don't use hw. acc. for every small fill (because of the init overhead)*/
|
|
if(w < VFILL_HW_ACC_SIZE_LIMIT) {
|
|
sw_color_fill(&vdb_p->area, vdb_p->buf, &vdb_rel_a, color, opa);
|
|
}
|
|
/*Not opaque fill*/
|
|
else if(opa == LV_OPA_COVER) {
|
|
/*Use hw fill if present*/
|
|
if(lv_disp_is_mem_fill_supported()) {
|
|
lv_coord_t row;
|
|
for(row = vdb_rel_a.y1; row <= vdb_rel_a.y2; row++) {
|
|
lv_disp_mem_fill(&vdb_buf_tmp[vdb_rel_a.x1], w, color);
|
|
vdb_buf_tmp += vdb_width;
|
|
}
|
|
}
|
|
/*Use hw blend if present and the area is not too small*/
|
|
else if(lv_area_get_height(&vdb_rel_a) > VFILL_HW_ACC_SIZE_LIMIT &&
|
|
lv_disp_is_mem_blend_supported()) {
|
|
/*Fill a one line sized buffer with a color and blend this later*/
|
|
if(color_array_tmp[0].full != color.full || last_width != w) {
|
|
uint16_t i;
|
|
for(i = 0; i < w; i++) {
|
|
color_array_tmp[i].full = color.full;
|
|
}
|
|
last_width = w;
|
|
}
|
|
|
|
/*Blend the filled line to every line VDB line-by-line*/
|
|
lv_coord_t row;
|
|
for(row = vdb_rel_a.y1; row <= vdb_rel_a.y2; row++) {
|
|
lv_disp_mem_blend(&vdb_buf_tmp[vdb_rel_a.x1], color_array_tmp, w, opa);
|
|
vdb_buf_tmp += vdb_width;
|
|
}
|
|
|
|
}
|
|
/*Else use sw fill if no better option*/
|
|
else {
|
|
sw_color_fill(&vdb_p->area, vdb_p->buf, &vdb_rel_a, color, opa);
|
|
}
|
|
|
|
}
|
|
/*Fill with opacity*/
|
|
else {
|
|
/*Use hw blend if present*/
|
|
if(lv_disp_is_mem_blend_supported()) {
|
|
if(color_array_tmp[0].full != color.full || last_width != w) {
|
|
uint16_t i;
|
|
for(i = 0; i < w; i++) {
|
|
color_array_tmp[i].full = color.full;
|
|
}
|
|
|
|
last_width = w;
|
|
}
|
|
lv_coord_t row;
|
|
for(row = vdb_rel_a.y1; row <= vdb_rel_a.y2; row++) {
|
|
lv_disp_mem_blend(&vdb_buf_tmp[vdb_rel_a.x1], color_array_tmp, w, opa);
|
|
vdb_buf_tmp += vdb_width;
|
|
}
|
|
|
|
}
|
|
/*Use sw fill with opa if no better option*/
|
|
else {
|
|
sw_color_fill(&vdb_p->area, vdb_p->buf, &vdb_rel_a, color, opa);
|
|
}
|
|
|
|
}
|
|
#else
|
|
sw_color_fill(&vdb_p->area, vdb_p->buf, &vdb_rel_a, color, opa);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Draw a letter in the Virtual Display Buffer
|
|
* @param pos_p left-top coordinate of the latter
|
|
* @param mask_p the letter will be drawn only on this area (truncated to VDB area)
|
|
* @param font_p pointer to font
|
|
* @param letter a letter to draw
|
|
* @param color color of letter
|
|
* @param opa opacity of letter (0..255)
|
|
*/
|
|
void lv_vletter(const lv_point_t * pos_p, const lv_area_t * mask_p,
|
|
const lv_font_t * font_p, uint32_t letter,
|
|
lv_color_t color, lv_opa_t opa)
|
|
{
|
|
const uint8_t bpp1_opa_table[2] = {0, 255}; /*Opacity mapping with bpp = 1 (Just for compatibility)*/
|
|
const uint8_t bpp2_opa_table[4] = {0, 85, 170, 255}; /*Opacity mapping with bpp = 2*/
|
|
const uint8_t bpp4_opa_table[16] = {0, 17, 34, 51, /*Opacity mapping with bpp = 4*/
|
|
68, 85, 102, 119,
|
|
136, 153, 170, 187,
|
|
204, 221, 238, 255
|
|
};
|
|
if(opa < LV_OPA_MIN) return;
|
|
if(opa > LV_OPA_MAX) opa = LV_OPA_COVER;
|
|
|
|
if(font_p == NULL) {
|
|
LV_LOG_WARN("Font: character's bitmap not found");
|
|
return;
|
|
}
|
|
|
|
lv_coord_t pos_x = pos_p->x;
|
|
lv_coord_t pos_y = pos_p->y;
|
|
uint8_t letter_w = lv_font_get_real_width(font_p, letter);
|
|
uint8_t letter_h = lv_font_get_height(font_p);
|
|
uint8_t bpp = lv_font_get_bpp(font_p, letter); /*Bit per pixel (1,2, 4 or 8)*/
|
|
const uint8_t * bpp_opa_table;
|
|
uint8_t mask_init;
|
|
uint8_t mask;
|
|
|
|
if(lv_font_is_monospace(font_p, letter)) {
|
|
pos_x += (lv_font_get_width(font_p, letter) - letter_w) / 2;
|
|
}
|
|
|
|
|
|
switch(bpp) {
|
|
case 1:
|
|
bpp_opa_table = bpp1_opa_table;
|
|
mask_init = 0x80;
|
|
break;
|
|
case 2:
|
|
bpp_opa_table = bpp2_opa_table;
|
|
mask_init = 0xC0;
|
|
break;
|
|
case 4:
|
|
bpp_opa_table = bpp4_opa_table;
|
|
mask_init = 0xF0;
|
|
break;
|
|
case 8:
|
|
bpp_opa_table = NULL;
|
|
mask_init = 0xFF;
|
|
break; /*No opa table, pixel value will be used directly*/
|
|
default:
|
|
return; /*Invalid bpp. Can't render the letter*/
|
|
}
|
|
|
|
const uint8_t * map_p = lv_font_get_bitmap(font_p, letter);
|
|
|
|
if(map_p == NULL) return;
|
|
|
|
/*If the letter is completely out of mask don't draw it */
|
|
if(pos_x + letter_w < mask_p->x1 || pos_x > mask_p->x2 ||
|
|
pos_y + letter_h < mask_p->y1 || pos_y > mask_p->y2) return;
|
|
|
|
lv_vdb_t * vdb_p = lv_vdb_get();
|
|
if(!vdb_p) {
|
|
LV_LOG_WARN("Invalid VDB pointer");
|
|
return;
|
|
}
|
|
|
|
lv_coord_t vdb_width = lv_area_get_width(&vdb_p->area);
|
|
lv_color_t * vdb_buf_tmp = vdb_p->buf;
|
|
lv_coord_t col, row;
|
|
uint8_t col_bit;
|
|
uint8_t col_byte_cnt;
|
|
uint8_t width_byte_scr = letter_w >> 3; /*Width in bytes (on the screen finally) (e.g. w = 11 -> 2 bytes wide)*/
|
|
if(letter_w & 0x7) width_byte_scr++;
|
|
uint8_t width_byte_bpp = (letter_w * bpp) >> 3; /*Letter width in byte. Real width in the font*/
|
|
if((letter_w * bpp) & 0x7) width_byte_bpp++;
|
|
|
|
/* Calculate the col/row start/end on the map*/
|
|
lv_coord_t col_start = pos_x >= mask_p->x1 ? 0 : mask_p->x1 - pos_x;
|
|
lv_coord_t col_end = pos_x + letter_w <= mask_p->x2 ? letter_w : mask_p->x2 - pos_x + 1;
|
|
lv_coord_t row_start = pos_y >= mask_p->y1 ? 0 : mask_p->y1 - pos_y;
|
|
lv_coord_t row_end = pos_y + letter_h <= mask_p->y2 ? letter_h : mask_p->y2 - pos_y + 1;
|
|
|
|
/*Set a pointer on VDB to the first pixel of the letter*/
|
|
vdb_buf_tmp += ((pos_y - vdb_p->area.y1) * vdb_width)
|
|
+ pos_x - vdb_p->area.x1;
|
|
|
|
/*If the letter is partially out of mask the move there on VDB*/
|
|
vdb_buf_tmp += (row_start * vdb_width) + col_start;
|
|
|
|
/*Move on the map too*/
|
|
map_p += (row_start * width_byte_bpp) + ((col_start * bpp) >> 3);
|
|
|
|
lv_disp_t * disp = lv_disp_get_active();
|
|
|
|
uint8_t letter_px;
|
|
lv_opa_t px_opa;
|
|
for(row = row_start; row < row_end; row ++) {
|
|
col_byte_cnt = 0;
|
|
col_bit = (col_start * bpp) % 8;
|
|
mask = mask_init >> col_bit;
|
|
for(col = col_start; col < col_end; col ++) {
|
|
letter_px = (*map_p & mask) >> (8 - col_bit - bpp);
|
|
if(letter_px != 0) {
|
|
if(opa == LV_OPA_COVER) {
|
|
px_opa = bpp == 8 ? letter_px : bpp_opa_table[letter_px];
|
|
} else {
|
|
px_opa = bpp == 8 ?
|
|
(uint16_t)((uint16_t)letter_px * opa) >> 8 :
|
|
(uint16_t)((uint16_t)bpp_opa_table[letter_px] * opa) >> 8;
|
|
}
|
|
|
|
if(disp->driver.vdb_wr) {
|
|
disp->driver.vdb_wr((uint8_t *)vdb_p->buf, vdb_width,
|
|
(col + pos_x) - vdb_p->area.x1, (row + pos_y) - vdb_p->area.y1,
|
|
color, px_opa);
|
|
} else {
|
|
#if LV_COLOR_SCREEN_TRANSP == 0
|
|
*vdb_buf_tmp = lv_color_mix(color, *vdb_buf_tmp, px_opa);
|
|
#else
|
|
*vdb_buf_tmp = color_mix_2_alpha(*vdb_buf_tmp, (*vdb_buf_tmp).alpha, color, px_opa);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
vdb_buf_tmp++;
|
|
|
|
if(col_bit < 8 - bpp) {
|
|
col_bit += bpp;
|
|
mask = mask >> bpp;
|
|
} else {
|
|
col_bit = 0;
|
|
col_byte_cnt ++;
|
|
mask = mask_init;
|
|
map_p ++;
|
|
}
|
|
}
|
|
|
|
map_p += (width_byte_bpp) - col_byte_cnt;
|
|
vdb_buf_tmp += vdb_width - (col_end - col_start); /*Next row in VDB*/
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draw a color map to the display (image)
|
|
* @param cords_p coordinates the color map
|
|
* @param mask_p the map will drawn only on this area (truncated to VDB area)
|
|
* @param map_p pointer to a lv_color_t array
|
|
* @param opa opacity of the map
|
|
* @param chroma_keyed true: enable transparency of LV_IMG_LV_COLOR_TRANSP color pixels
|
|
* @param alpha_byte true: extra alpha byte is inserted for every pixel
|
|
* @param recolor mix the pixels with this color
|
|
* @param recolor_opa the intense of recoloring
|
|
*/
|
|
void lv_vmap(const lv_area_t * cords_p, const lv_area_t * mask_p,
|
|
const uint8_t * map_p, lv_opa_t opa, bool chroma_key, bool alpha_byte,
|
|
lv_color_t recolor, lv_opa_t recolor_opa)
|
|
{
|
|
|
|
if(opa < LV_OPA_MIN) return;
|
|
if(opa > LV_OPA_MAX) opa = LV_OPA_COVER;
|
|
|
|
lv_area_t masked_a;
|
|
bool union_ok;
|
|
lv_vdb_t * vdb_p = lv_vdb_get();
|
|
if(!vdb_p) {
|
|
LV_LOG_WARN("Invalid VDB pointer");
|
|
return;
|
|
}
|
|
|
|
/*Get the union of map size and mask*/
|
|
/* The mask is already truncated to the vdb size
|
|
* in 'lv_refr_area_with_vdb' function */
|
|
union_ok = lv_area_intersect(&masked_a, cords_p, mask_p);
|
|
|
|
/*If there are common part of the three area then draw to the vdb*/
|
|
if(union_ok == false) return;
|
|
|
|
/*The pixel size in byte is different if an alpha byte is added too*/
|
|
uint8_t px_size_byte = alpha_byte ? LV_IMG_PX_SIZE_ALPHA_BYTE : sizeof(lv_color_t);
|
|
|
|
/*If the map starts OUT of the masked area then calc. the first pixel*/
|
|
lv_coord_t map_width = lv_area_get_width(cords_p);
|
|
if(cords_p->y1 < masked_a.y1) {
|
|
map_p += (uint32_t) map_width * ((masked_a.y1 - cords_p->y1)) * px_size_byte;
|
|
}
|
|
if(cords_p->x1 < masked_a.x1) {
|
|
map_p += (masked_a.x1 - cords_p->x1) * px_size_byte;
|
|
}
|
|
|
|
/*Stores coordinates relative to the current VDB*/
|
|
masked_a.x1 = masked_a.x1 - vdb_p->area.x1;
|
|
masked_a.y1 = masked_a.y1 - vdb_p->area.y1;
|
|
masked_a.x2 = masked_a.x2 - vdb_p->area.x1;
|
|
masked_a.y2 = masked_a.y2 - vdb_p->area.y1;
|
|
|
|
lv_coord_t vdb_width = lv_area_get_width(&vdb_p->area);
|
|
lv_color_t * vdb_buf_tmp = vdb_p->buf;
|
|
vdb_buf_tmp += (uint32_t) vdb_width * masked_a.y1; /*Move to the first row*/
|
|
vdb_buf_tmp += (uint32_t) masked_a.x1; /*Move to the first col*/
|
|
|
|
lv_coord_t row;
|
|
lv_coord_t map_useful_w = lv_area_get_width(&masked_a);
|
|
|
|
lv_disp_t * disp = lv_disp_get_active();
|
|
|
|
/*The simplest case just copy the pixels into the VDB*/
|
|
if(chroma_key == false && alpha_byte == false && opa == LV_OPA_COVER && recolor_opa == LV_OPA_TRANSP) {
|
|
|
|
/*Use the custom VDB write function is exists*/
|
|
if(disp->driver.vdb_wr) {
|
|
lv_coord_t col;
|
|
for(row = masked_a.y1; row <= masked_a.y2; row++) {
|
|
for(col = 0; col < map_useful_w; col++) {
|
|
lv_color_t px_color = *((lv_color_t *)&map_p[(uint32_t)col * px_size_byte]);
|
|
disp->driver.vdb_wr((uint8_t *)vdb_p->buf, vdb_width, col + masked_a.x1, row, px_color, opa);
|
|
}
|
|
map_p += map_width * px_size_byte; /*Next row on the map*/
|
|
}
|
|
}
|
|
/*Normal native VDB*/
|
|
else {
|
|
for(row = masked_a.y1; row <= masked_a.y2; row++) {
|
|
#if USE_LV_GPU
|
|
if(lv_disp_is_mem_blend_supported() == false) {
|
|
sw_mem_blend(vdb_buf_tmp, (lv_color_t *)map_p, map_useful_w, opa);
|
|
} else {
|
|
lv_disp_mem_blend(vdb_buf_tmp, (lv_color_t *)map_p, map_useful_w, opa);
|
|
}
|
|
#else
|
|
sw_mem_blend(vdb_buf_tmp, (lv_color_t *)map_p, map_useful_w, opa);
|
|
#endif
|
|
map_p += map_width * px_size_byte; /*Next row on the map*/
|
|
vdb_buf_tmp += vdb_width; /*Next row on the VDB*/
|
|
}
|
|
}
|
|
}
|
|
|
|
/*In the other cases every pixel need to be checked one-by-one*/
|
|
else {
|
|
lv_color_t chroma_key_color = LV_COLOR_TRANSP;
|
|
lv_coord_t col;
|
|
lv_color_t last_img_px = LV_COLOR_BLACK;
|
|
lv_color_t recolored_px = lv_color_mix(recolor, last_img_px, recolor_opa);
|
|
for(row = masked_a.y1; row <= masked_a.y2; row++) {
|
|
for(col = 0; col < map_useful_w; col++) {
|
|
lv_opa_t opa_result = opa;
|
|
uint8_t * px_color_p = (uint8_t *) &map_p[(uint32_t)col * px_size_byte];
|
|
lv_color_t px_color;
|
|
|
|
/*Calculate with the pixel level alpha*/
|
|
if(alpha_byte) {
|
|
#if LV_COLOR_DEPTH == 8 || LV_COLOR_DEPTH == 1
|
|
px_color.full = px_color_p[0];
|
|
#elif LV_COLOR_DEPTH == 16
|
|
/*Because of Alpha byte 16 bit color can start on odd address which can cause crash*/
|
|
px_color.full = px_color_p[0] + (px_color_p[1] << 8);
|
|
#elif LV_COLOR_DEPTH == 32
|
|
px_color = *((lv_color_t *)px_color_p);
|
|
#endif
|
|
lv_opa_t px_opa = *(px_color_p + LV_IMG_PX_SIZE_ALPHA_BYTE - 1);
|
|
if(px_opa == LV_OPA_TRANSP) continue;
|
|
else if(px_opa != LV_OPA_COVER) opa_result = (uint32_t)((uint32_t)px_opa * opa_result) >> 8;
|
|
} else {
|
|
px_color = *((lv_color_t *)px_color_p);
|
|
}
|
|
|
|
/*Handle chroma key*/
|
|
if(chroma_key && px_color.full == chroma_key_color.full) continue;
|
|
|
|
/*Re-color the pixel if required*/
|
|
if(recolor_opa != LV_OPA_TRANSP) {
|
|
if(last_img_px.full != px_color.full) { /*Minor acceleration: calculate only for new colors (save the last)*/
|
|
last_img_px = px_color;
|
|
recolored_px = lv_color_mix(recolor, last_img_px, recolor_opa);
|
|
}
|
|
/*Handle custom VDB write is present*/
|
|
if(disp->driver.vdb_wr) {
|
|
disp->driver.vdb_wr((uint8_t *)vdb_p->buf, vdb_width, col + masked_a.x1, row, recolored_px, opa_result);
|
|
}
|
|
/*Normal native VDB write*/
|
|
else {
|
|
if(opa_result == LV_OPA_COVER) vdb_buf_tmp[col].full = recolored_px.full;
|
|
else vdb_buf_tmp[col] = lv_color_mix(recolored_px, vdb_buf_tmp[col], opa_result);
|
|
}
|
|
} else {
|
|
/*Handle custom VDB write is present*/
|
|
if(disp->driver.vdb_wr) {
|
|
disp->driver.vdb_wr((uint8_t *)vdb_p->buf, vdb_width, col + masked_a.x1, row, px_color, opa_result);
|
|
}
|
|
/*Normal native VDB write*/
|
|
else {
|
|
if(opa_result == LV_OPA_COVER) vdb_buf_tmp[col] = px_color;
|
|
else {
|
|
#if LV_COLOR_SCREEN_TRANSP == 0
|
|
vdb_buf_tmp[col] = lv_color_mix(px_color, vdb_buf_tmp[col], opa_result);
|
|
#else
|
|
vdb_buf_tmp[col] = color_mix_2_alpha(vdb_buf_tmp[col], vdb_buf_tmp[col].alpha, px_color, opa_result);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
map_p += map_width * px_size_byte; /*Next row on the map*/
|
|
vdb_buf_tmp += vdb_width; /*Next row on the VDB*/
|
|
}
|
|
}
|
|
}
|
|
|
|
/**********************
|
|
* STATIC FUNCTIONS
|
|
**********************/
|
|
|
|
/**
|
|
* Blend pixels to destination memory using opacity
|
|
* @param dest a memory address. Copy 'src' here.
|
|
* @param src pointer to pixel map. Copy it to 'dest'.
|
|
* @param length number of pixels in 'src'
|
|
* @param opa opacity (0, LV_OPA_TRANSP: transparent ... 255, LV_OPA_COVER, fully cover)
|
|
*/
|
|
static void sw_mem_blend(lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa)
|
|
{
|
|
if(opa == LV_OPA_COVER) {
|
|
memcpy(dest, src, length * sizeof(lv_color_t));
|
|
} else {
|
|
uint32_t col;
|
|
for(col = 0; col < length; col++) {
|
|
dest[col] = lv_color_mix(src[col], dest[col], opa);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param mem_area coordinates of 'mem' memory area
|
|
* @param mem a memory address. Considered to a rectangular window according to 'mem_area'
|
|
* @param fill_area coordinates of an area to fill. Relative to 'mem_area'.
|
|
* @param color fill color
|
|
* @param opa opacity (0, LV_OPA_TRANSP: transparent ... 255, LV_OPA_COVER, fully cover)
|
|
*/
|
|
static void sw_color_fill(lv_area_t * mem_area, lv_color_t * mem, const lv_area_t * fill_area, lv_color_t color, lv_opa_t opa)
|
|
{
|
|
/*Set all row in vdb to the given color*/
|
|
lv_coord_t row;
|
|
lv_coord_t col;
|
|
lv_coord_t mem_width = lv_area_get_width(mem_area);
|
|
|
|
lv_disp_t * disp = lv_disp_get_active();
|
|
if(disp->driver.vdb_wr) {
|
|
for(col = fill_area->x1; col <= fill_area->x2; col++) {
|
|
for(row = fill_area->y1; row <= fill_area->y2; row++) {
|
|
disp->driver.vdb_wr((uint8_t *)mem, mem_width, col, row, color, opa);
|
|
}
|
|
}
|
|
} else {
|
|
mem += fill_area->y1 * mem_width; /*Go to the first row*/
|
|
|
|
/*Run simpler function without opacity*/
|
|
if(opa == LV_OPA_COVER) {
|
|
|
|
/*Fill the first row with 'color'*/
|
|
for(col = fill_area->x1; col <= fill_area->x2; col++) {
|
|
mem[col] = color;
|
|
}
|
|
|
|
/*Copy the first row to all other rows*/
|
|
lv_color_t * mem_first = &mem[fill_area->x1];
|
|
lv_coord_t copy_size = (fill_area->x2 - fill_area->x1 + 1) * sizeof(lv_color_t);
|
|
mem += mem_width;
|
|
|
|
for(row = fill_area->y1 + 1; row <= fill_area->y2; row++) {
|
|
memcpy(&mem[fill_area->x1], mem_first, copy_size);
|
|
mem += mem_width;
|
|
}
|
|
}
|
|
/*Calculate with alpha too*/
|
|
else {
|
|
|
|
#if LV_COLOR_SCREEN_TRANSP == 0
|
|
lv_color_t bg_tmp = LV_COLOR_BLACK;
|
|
lv_color_t opa_tmp = lv_color_mix(color, bg_tmp, opa);
|
|
#endif
|
|
for(row = fill_area->y1; row <= fill_area->y2; row++) {
|
|
for(col = fill_area->x1; col <= fill_area->x2; col++) {
|
|
#if LV_COLOR_SCREEN_TRANSP == 0
|
|
/*If the bg color changed recalculate the result color*/
|
|
if(mem[col].full != bg_tmp.full) {
|
|
bg_tmp = mem[col];
|
|
opa_tmp = lv_color_mix(color, bg_tmp, opa);
|
|
}
|
|
|
|
mem[col] = opa_tmp;
|
|
|
|
#else
|
|
mem[col] = color_mix_2_alpha(mem[col], mem[col].alpha, color, opa);
|
|
#endif
|
|
}
|
|
mem += mem_width;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if LV_COLOR_SCREEN_TRANSP
|
|
|
|
/**
|
|
* Mix two colors. Both color can have alpha value. It requires ARGB888 colors.
|
|
* @param bg_color background color
|
|
* @param bg_opa alpha of the background color
|
|
* @param fg_color foreground color
|
|
* @param fg_opa alpha of the foreground color
|
|
* @return the mixed color. the alpha channel (color.alpha) contains the result alpha
|
|
*/
|
|
static inline lv_color_t color_mix_2_alpha(lv_color_t bg_color, lv_opa_t bg_opa, lv_color_t fg_color, lv_opa_t fg_opa)
|
|
{
|
|
/* Pick the foreground if it's fully opaque or the Background is fully transparent*/
|
|
if(fg_opa == LV_OPA_COVER && bg_opa <= LV_OPA_MIN) {
|
|
fg_color.alpha = fg_opa;
|
|
return fg_color;
|
|
}
|
|
/*Transparent foreground: use the Background*/
|
|
else if(fg_opa <= LV_OPA_MIN) {
|
|
return bg_color;
|
|
}
|
|
/*Opaque background: use simple mix*/
|
|
else if(bg_opa >= LV_OPA_MAX) {
|
|
return lv_color_mix(fg_color, bg_color, fg_opa);
|
|
}
|
|
/*Both colors have alpha. Expensive calculation need to be applied*/
|
|
else {
|
|
/*Save the parameters and the result. If they will be asked again don't compute again*/
|
|
static lv_opa_t fg_opa_save = 0;
|
|
static lv_opa_t bg_opa_save = 0;
|
|
static lv_color_t c = {{0}};
|
|
|
|
if(fg_opa != fg_opa_save || bg_opa != bg_opa_save) {
|
|
fg_opa_save = fg_opa;
|
|
bg_opa_save = bg_opa;
|
|
/*Info: https://en.wikipedia.org/wiki/Alpha_compositing#Analytical_derivation_of_the_over_operator*/
|
|
lv_opa_t alpha_res = 255 - ((uint16_t)((uint16_t)(255 - fg_opa) * (255 - bg_opa)) >> 8);
|
|
if(alpha_res == 0) {
|
|
while(1);
|
|
}
|
|
lv_opa_t ratio = (uint16_t)((uint16_t) fg_opa * 255) / alpha_res;
|
|
c = lv_color_mix(fg_color, bg_color, ratio);
|
|
c.alpha = alpha_res;
|
|
}
|
|
return c;
|
|
|
|
}
|
|
}
|
|
#endif /*LV_COLOR_SCREEN_TRANSP*/
|
|
|
|
#endif
|