358 lines
12 KiB
C
358 lines
12 KiB
C
/* source/draw/sgl_draw_text.c
|
|
*
|
|
* MIT License
|
|
*
|
|
* Copyright(c) 2023-present All contributors of SGL
|
|
* Document reference link: https://sgl-docs.readthedocs.io
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <sgl_core.h>
|
|
#include <sgl_log.h>
|
|
#include <sgl_draw.h>
|
|
#include <sgl_math.h>
|
|
|
|
|
|
#if (CONFIG_SGL_FONT_COMPRESSED)
|
|
/**
|
|
* @brief RLE state, refrence LVGL
|
|
*/
|
|
typedef enum {
|
|
RLE_STATE_SINGLE = 0,
|
|
RLE_STATE_REPEATED,
|
|
RLE_STATE_COUNTER,
|
|
} sgl_font_rle_state_t;
|
|
|
|
/**
|
|
* @brief RLE decompress information structure
|
|
*/
|
|
typedef struct {
|
|
uint32_t rdp;
|
|
const uint8_t * in;
|
|
uint8_t bpp;
|
|
uint8_t prev_v;
|
|
uint8_t count;
|
|
sgl_font_rle_state_t state;
|
|
} sgl_font_rle_t;
|
|
|
|
static sgl_font_rle_t font_rle = {
|
|
.rdp = 0,
|
|
.in = NULL,
|
|
.bpp = 0,
|
|
.prev_v = 0,
|
|
.count = 0,
|
|
.state = RLE_STATE_SINGLE,
|
|
};
|
|
|
|
/**
|
|
* @brief Get bits from a byte array
|
|
* @param in the byte array
|
|
* @param bit_pos the bit position
|
|
* @param len the bit length
|
|
* @return the bits of a byte
|
|
*/
|
|
static inline uint8_t get_bits(const uint8_t * in, uint32_t bit_pos, uint8_t len)
|
|
{
|
|
uint8_t bit_mask;
|
|
switch(len) {
|
|
case 1:
|
|
bit_mask = 0x1;
|
|
break;
|
|
case 2:
|
|
bit_mask = 0x3;
|
|
break;
|
|
case 4:
|
|
bit_mask = 0xF;
|
|
break;
|
|
default:
|
|
bit_mask = (uint16_t)((uint16_t) 1 << len) - 1;
|
|
}
|
|
|
|
uint32_t byte_pos = bit_pos >> 3;
|
|
bit_pos = bit_pos & 0x7;
|
|
|
|
if(bit_pos + len >= 8) {
|
|
uint16_t in16 = (in[byte_pos] << 8) + in[byte_pos + 1];
|
|
return (in16 >> (16 - bit_pos - len)) & bit_mask;
|
|
}
|
|
else {
|
|
return (in[byte_pos] >> (8 - bit_pos - len)) & bit_mask;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Decompress a line of RLE data
|
|
* @param out the decompressed data
|
|
* @param w the width of the decompressed data
|
|
* @return none
|
|
*/
|
|
static inline void decompress_line(uint8_t *out, int32_t w)
|
|
{
|
|
int32_t i;
|
|
uint8_t v = 0;
|
|
uint8_t ret = 0;
|
|
sgl_font_rle_t *rle = &font_rle;
|
|
|
|
for(i = 0; i < w; i++) {
|
|
if(rle->state == RLE_STATE_SINGLE) {
|
|
ret = get_bits(rle->in, rle->rdp, rle->bpp);
|
|
if(rle->rdp != 0 && rle->prev_v == ret) {
|
|
rle->count = 0;
|
|
rle->state = RLE_STATE_REPEATED;
|
|
}
|
|
|
|
rle->prev_v = ret;
|
|
rle->rdp += rle->bpp;
|
|
}
|
|
else if(rle->state == RLE_STATE_REPEATED) {
|
|
v = get_bits(rle->in, rle->rdp, 1);
|
|
rle->count++;
|
|
rle->rdp += 1;
|
|
if(v == 1) {
|
|
ret = rle->prev_v;
|
|
if(rle->count == 11) {
|
|
rle->count = get_bits(rle->in, rle->rdp, 6);
|
|
rle->rdp += 6;
|
|
if(rle->count != 0) {
|
|
rle->state = RLE_STATE_COUNTER;
|
|
}
|
|
else {
|
|
ret = get_bits(rle->in, rle->rdp, rle->bpp);
|
|
rle->prev_v = ret;
|
|
rle->rdp += rle->bpp;
|
|
rle->state = RLE_STATE_SINGLE;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ret = get_bits(rle->in, rle->rdp, rle->bpp);
|
|
rle->prev_v = ret;
|
|
rle->rdp += rle->bpp;
|
|
rle->state = RLE_STATE_SINGLE;
|
|
}
|
|
}
|
|
else if(rle->state == RLE_STATE_COUNTER) {
|
|
ret = rle->prev_v;
|
|
rle->count--;
|
|
if(rle->count == 0) {
|
|
ret = get_bits(rle->in, rle->rdp, rle->bpp);
|
|
rle->prev_v = ret;
|
|
rle->rdp += rle->bpp;
|
|
rle->state = RLE_STATE_SINGLE;
|
|
}
|
|
}
|
|
if (out != NULL) {
|
|
out[i] = ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize the RLE decompression state
|
|
* @param in Pointer to the input data
|
|
* @param bpp Bits per pixel of the input data
|
|
* @return none
|
|
*/
|
|
static inline void font_rle_init(const uint8_t * in, uint8_t bpp)
|
|
{
|
|
font_rle.in = in;
|
|
font_rle.bpp = bpp;
|
|
font_rle.state = RLE_STATE_SINGLE;
|
|
font_rle.rdp = 0;
|
|
font_rle.prev_v = 0;
|
|
font_rle.count = 0;
|
|
}
|
|
#endif // (!CONFIG_SGL_FONT_COMPRESSED)
|
|
|
|
|
|
/**
|
|
* @brief Draw a character on the surface with alpha blending
|
|
* @param surf Pointer to the surface where the character will be drawn
|
|
* @param area Pointer to the area where the character will be drawn
|
|
* @param x X coordinate where the character will be drawn
|
|
* @param y Y coordinate where the character will be drawn
|
|
* @param ch_index Index of the character in the font table
|
|
* @param color Foreground color of the character
|
|
* @param alpha Alpha value for blending
|
|
* @param font Pointer to the font structure containing character data
|
|
* @return none
|
|
* @note this function is only support bpp:4
|
|
*/
|
|
void sgl_draw_character(sgl_surf_t *surf, sgl_area_t *area, int16_t x, int16_t y, uint32_t ch_index, sgl_color_t color, uint8_t alpha, const sgl_font_t *font)
|
|
{
|
|
int offset_y2 = font->font_height - font->table[ch_index].ofs_y - font->base_line;
|
|
const uint8_t *dot = &font->bitmap[font->table[ch_index].bitmap_index];
|
|
const uint8_t font_w = font->table[ch_index].box_w;
|
|
const uint8_t font_h = font->table[ch_index].box_h;
|
|
|
|
uint8_t shift = 0;
|
|
uint32_t pixel_index, rel_x, rel_y;
|
|
uint16_t byte_index, alpha_dot = 0;
|
|
sgl_color_t color_mix, *buf = NULL, *blend = NULL;
|
|
sgl_area_t clip;
|
|
|
|
sgl_area_t text_rect = {
|
|
.x1 = x + font->table[ch_index].ofs_x,
|
|
.x2 = x + font->table[ch_index].ofs_x + font_w - 1,
|
|
.y1 = y + offset_y2 - font_h,
|
|
.y2 = y + offset_y2 - 1,
|
|
};
|
|
|
|
if (!sgl_surf_clip(surf, &text_rect, &clip)) {
|
|
return;
|
|
}
|
|
|
|
if (!sgl_area_selfclip(&clip, area)) {
|
|
return;
|
|
}
|
|
|
|
buf = sgl_surf_get_buf(surf, clip.x1 - surf->x1, clip.y1 - surf->y1);
|
|
#if (CONFIG_SGL_FONT_COMPRESSED)
|
|
if (font->compress == 0) {
|
|
#endif // (!CONFIG_SGL_FONT_COMPRESSED == 0)
|
|
for (int y = clip.y1; y <= clip.y2; y++) {
|
|
blend = buf;
|
|
rel_y = y - text_rect.y1;
|
|
|
|
for (int x = clip.x1; x <= clip.x2; x++) {
|
|
rel_x = x - text_rect.x1;
|
|
pixel_index = rel_y * font_w + rel_x;
|
|
|
|
if (font->bpp == 4) {
|
|
byte_index = pixel_index >> 1;
|
|
alpha_dot = sgl_opa4_table[(pixel_index & 1) ? (dot[byte_index] & 0x0F) : (dot[byte_index] >> 4)];
|
|
}
|
|
else if (font->bpp == 2) {
|
|
byte_index = pixel_index >> 2;
|
|
shift = (3 - (pixel_index & 0x3)) * 2;
|
|
alpha_dot = sgl_opa2_table[(dot[byte_index] >> shift) & 0x03];
|
|
}
|
|
else if (font->bpp == 1) {
|
|
byte_index = pixel_index >> 3;
|
|
shift = 7 - (pixel_index & 0x7);
|
|
alpha_dot = ((dot[byte_index] >> shift) & 0x01) ? SGL_ALPHA_MAX : SGL_ALPHA_MIN;
|
|
}
|
|
|
|
color_mix = sgl_color_mixer(color, *blend, alpha_dot);
|
|
*blend = sgl_color_mixer(color_mix, *blend, alpha);
|
|
blend++;
|
|
}
|
|
buf += surf->w;
|
|
}
|
|
#if (CONFIG_SGL_FONT_COMPRESSED)
|
|
} /* support compressed font */
|
|
else {
|
|
uint8_t line_buf[128] = {0};
|
|
font_rle_init(dot, font->bpp);
|
|
|
|
for (int y = text_rect.y1; y < clip.y1; y++) {
|
|
decompress_line(NULL, font_w);
|
|
}
|
|
|
|
for (int y = clip.y1; y <= clip.y2; y++) {
|
|
blend = buf;
|
|
decompress_line(line_buf, font_w);
|
|
|
|
for (int x = clip.x1; x <= clip.x2; x++) {
|
|
if (font->bpp == 4) {
|
|
color_mix = sgl_color_mixer(color, *blend, sgl_opa4_table[line_buf[x - text_rect.x1]]);
|
|
}
|
|
else if (font->bpp == 2) {
|
|
color_mix = sgl_color_mixer(color, *blend, sgl_opa2_table[line_buf[x - text_rect.x1]]);
|
|
}
|
|
else if (font->bpp == 1) {
|
|
color_mix = sgl_color_mixer(color, *blend, line_buf[x - text_rect.x1] ? SGL_ALPHA_MAX : SGL_ALPHA_MIN);
|
|
}
|
|
*blend = sgl_color_mixer(color_mix, *blend, alpha);
|
|
blend++;
|
|
}
|
|
buf += surf->w;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Draw a string on the surface with alpha blending
|
|
* @param surf Pointer to the surface where the string will be drawn
|
|
* @param area Pointer to the area where the string will be drawn
|
|
* @param x X coordinate of the top-left corner of the string
|
|
* @param y Y coordinate of the top-left corner of the string
|
|
* @param str Pointer to the string to be drawn
|
|
* @param color Foreground color of the string
|
|
* @param alpha Alpha value for blending
|
|
* @param font Pointer to the font structure containing character data
|
|
* @return none
|
|
*/
|
|
void sgl_draw_string(sgl_surf_t *surf, sgl_area_t *area, int16_t x, int16_t y, const char *str, sgl_color_t color, uint8_t alpha, const sgl_font_t *font)
|
|
{
|
|
uint32_t ch_index;
|
|
uint32_t unicode = 0;
|
|
|
|
while (*str) {
|
|
str += sgl_utf8_to_unicode(str, &unicode);
|
|
ch_index = sgl_search_unicode_ch_index(font, unicode);
|
|
sgl_draw_character(surf, area, x, y, ch_index, color, alpha, font);
|
|
x += (font->table[ch_index].adv_w >> 4);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Draw a string on the surface with alpha blending and multiple lines
|
|
* @param surf Pointer to the surface where the string will be drawn
|
|
* @param area Pointer to the area where the string will be drawn
|
|
* @param x X coordinate of the top-left corner of the string
|
|
* @param y Y coordinate of the top-left corner of the string
|
|
* @param str Pointer to the string to be drawn
|
|
* @param color Foreground color of the string
|
|
* @param alpha Alpha value for blending
|
|
* @param font Pointer to the font structure containing character data
|
|
* @param line_margin Margin between lines
|
|
* @return none
|
|
*/
|
|
void sgl_draw_string_mult_line(sgl_surf_t *surf, sgl_area_t *area, int16_t x, int16_t y, const char *str, sgl_color_t color, uint8_t alpha, const sgl_font_t *font, uint8_t line_margin)
|
|
{
|
|
int16_t ch_index, ch_width;
|
|
int16_t x_off = x;
|
|
uint32_t unicode = 0;
|
|
|
|
while (*str) {
|
|
if (*str == '\n') {
|
|
x_off = x;
|
|
y += (font->font_height + line_margin);
|
|
str ++;
|
|
continue;
|
|
}
|
|
|
|
str += sgl_utf8_to_unicode(str, &unicode);
|
|
ch_index = sgl_search_unicode_ch_index(font, unicode);
|
|
|
|
ch_width = (font->table[ch_index].adv_w >> 4);
|
|
|
|
if ((x_off + ch_width) > area->x2) {
|
|
x_off = x;
|
|
y += (font->font_height + line_margin);
|
|
}
|
|
|
|
sgl_draw_character(surf, area, x_off, y, ch_index, color, alpha, font);
|
|
x_off += ch_width;
|
|
}
|
|
}
|