Ken Van Hoeylandt 7ea8c346b0
Various improvements (#673)
Graphics & rendering

- Implement `binfont-module` to generate bitmap fonts on the device itself
- Remove lodepng support from LVGL library and create `Modules/lodepng-module` to make the feature re-usable in non-LVGL apps too. Add binding functions for LVGL support.
- Implement large icons for boot screen (Tactility and USB logos)
- Remove unused asset files in `Data/`

Boot

- Split up `Tactility.cpp` and its `run()` function into various `Init*.cpp` files.
- Re-implement `Boot.cpp` app as non-LVGL app
- Boot app now has a `BootSequence.cpp` for the init code.
- LVGL is now started when the Boot app finishes
- Move more init logic into Boot app

BLE

- Don't allocate scan result buffers unless BLE is on
- Allocate scan results in external memory if possible

UX & UI

- USB Settings app: remove border from the button container
2026-10-04 18:09:21 +02:00

127 lines
4.4 KiB
C++

// SPDX-License-Identifier: Apache-2.0
#include <binfont/render.h>
#include <tactility/memory.h>
namespace {
constexpr size_t STACK_BITMAP_SIZE = 1024;
uint16_t blend_rgb565(uint16_t foreground, uint16_t background, uint8_t alpha) {
const uint32_t inverse = 255U - alpha;
const uint32_t r = (((foreground >> 11) & 0x1FU) * alpha + ((background >> 11) & 0x1FU) * inverse + 127U) / 255U;
const uint32_t g = (((foreground >> 5) & 0x3FU) * alpha + ((background >> 5) & 0x3FU) * inverse + 127U) / 255U;
const uint32_t b = ((foreground & 0x1FU) * alpha + (background & 0x1FU) * inverse + 127U) / 255U;
return static_cast<uint16_t>((r << 11) | (g << 5) | b);
}
void draw_glyph(PixelBuffer* buffer, int x, int baselineY, BinFont* font, const BinFontGlyph& glyph, uint16_t color, PixelBufferConversion conversion) {
const size_t pixel_count = static_cast<size_t>(glyph.width) * glyph.height;
if (pixel_count == 0) {
return;
}
uint8_t stack_bitmap[STACK_BITMAP_SIZE];
uint8_t* allocated = nullptr;
uint8_t* bitmap = stack_bitmap;
if (pixel_count > sizeof(stack_bitmap)) {
allocated = static_cast<uint8_t*>(memory_alloc(pixel_count));
if (allocated == nullptr) {
return;
}
bitmap = allocated;
}
if (binfont_get_glyph_bitmap(font, &glyph, bitmap, glyph.width) == ERROR_NONE) {
const int left = x + glyph.x;
const int top = baselineY - glyph.y - glyph.height;
for (int row = 0; row < glyph.height; row++) {
const uint8_t* line = bitmap + row * glyph.width;
for (int column = 0; column < glyph.width; column++) {
const uint8_t alpha = line[column];
if (alpha == 0) {
continue;
}
const int pixel_x = left + column;
const int pixel_y = top + row;
const uint16_t pixel = (alpha == 255) ? color : blend_rgb565(color, pixel_buffer_get_pixel_rgb565(buffer, pixel_x, pixel_y), alpha);
pixel_buffer_set_pixel_rgb565(buffer, pixel_x, pixel_y, pixel, conversion);
}
}
}
memory_free(allocated);
}
/** Decodes one UTF-8 codepoint and advances text. Invalid sequences decode as U+FFFD. */
uint32_t next_codepoint(const char*& text) {
const auto* bytes = reinterpret_cast<const uint8_t*>(text);
const uint8_t lead = bytes[0];
int length;
uint32_t codepoint;
if (lead < 0x80) {
length = 1;
codepoint = lead;
} else if ((lead & 0xE0) == 0xC0) {
length = 2;
codepoint = lead & 0x1FU;
} else if ((lead & 0xF0) == 0xE0) {
length = 3;
codepoint = lead & 0x0FU;
} else if ((lead & 0xF8) == 0xF0) {
length = 4;
codepoint = lead & 0x07U;
} else {
text++;
return 0xFFFD;
}
for (int i = 1; i < length; i++) {
if ((bytes[i] & 0xC0) != 0x80) {
text += i;
return 0xFFFD;
}
codepoint = (codepoint << 6) | (bytes[i] & 0x3FU);
}
text += length;
return codepoint;
}
} // namespace
extern "C" {
int binfont_draw_glyph_rgb565(PixelBuffer* buffer, int x, int baseline_y, BinFont* font, uint32_t codepoint, uint16_t color, PixelBufferConversion conversion) {
BinFontGlyph glyph;
if (!binfont_get_glyph(font, codepoint, &glyph)) {
return 0;
}
draw_glyph(buffer, x, baseline_y, font, glyph, color, conversion);
return static_cast<int>((glyph.advance_x16 + 8) >> 4);
}
int binfont_draw_text_rgb565(PixelBuffer* buffer, int x, int baseline_y, BinFont* font, const char* text, uint16_t color, PixelBufferConversion conversion) {
// When the file can't be kept open, every glyph lookup opens it by itself
const bool session = binfont_begin(font) == ERROR_NONE;
int32_t pen_x16 = 0;
uint32_t previous_glyph_id = 0;
while (*text != '\0') {
BinFontGlyph glyph;
if (!binfont_get_glyph(font, next_codepoint(text), &glyph)) {
previous_glyph_id = 0;
continue;
}
if (previous_glyph_id != 0) {
pen_x16 += binfont_get_kerning_x16(font, previous_glyph_id, glyph.glyph_id);
}
draw_glyph(buffer, x + ((pen_x16 + 8) >> 4), baseline_y, font, glyph, color, conversion);
pen_x16 += static_cast<int32_t>(glyph.advance_x16);
previous_glyph_id = glyph.glyph_id;
}
if (session) {
binfont_end(font);
}
return (pen_x16 + 8) >> 4;
}
}