kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Porównaj commity
8 Commity
fc0e6d66d2
...
2a8be11945
Autor | SHA1 | Data |
---|---|---|
Philip Howard | 2a8be11945 | |
Phil Howard | 88bd71e56f | |
Phil Howard | 7cd9187d80 | |
Phil Howard | 21e9817347 | |
Phil Howard | 4ff7a93e3e | |
Phil Howard | 0174ca57a5 | |
Phil Howard | 223fb0d291 | |
Phil Howard | 0717c57fe7 |
|
@ -0,0 +1,16 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern "C" {
|
||||
void* fileio_open(const char* filename);
|
||||
|
||||
void fileio_close(void* fhandle);
|
||||
|
||||
size_t fileio_read(void* fhandle, void *buf, size_t len);
|
||||
|
||||
int fileio_getc(void* fhandle);
|
||||
|
||||
size_t fileio_tell(void* fhandle);
|
||||
|
||||
size_t fileio_seek(void* fhandle, size_t pos);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
extern "C" {
|
||||
void *af_malloc(size_t size);
|
||||
void *af_realloc(void *p, size_t size);
|
||||
void af_free(void *p);
|
||||
void af_debug(const char *fmt, ...);
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
|
||||
Alright Fonts 🖍 - a font format for embedded and low resource platforms.
|
||||
|
||||
Jonathan Williamson, August 2022
|
||||
Examples, source, and more: https://github.com/lowfatcode/pretty-poly
|
||||
MIT License https://github.com/lowfatcode/pretty-poly/blob/main/LICENSE
|
||||
|
||||
An easy way to render high quality text in embedded applications running
|
||||
on resource constrained microcontrollers such as the Cortex M0 and up.
|
||||
|
||||
- OTF and TTF support: generate efficient packed fonts easily
|
||||
- Minimal data: ~4kB (40 bytes per char) for printable ASCII set (Roboto)
|
||||
- Tunable: trade off file size, contour complexity, and visual quality
|
||||
- Metrics: advance and bounding box for fast layout
|
||||
- UTF-8 or ASCII: support for non ASCII like Kanji or Cyrillic
|
||||
- Fixed scale: coords scaled to ^2 bounds for fast scaling (no divide)
|
||||
- C17 header only library: simply copy the header file into your project
|
||||
- Customised font packs: include only the characters you need
|
||||
- Simple outlines: all paths are simply polylines for easy rendering
|
||||
- Easy antialiasing: combine with Pretty Poly for quick results!
|
||||
|
||||
*/
|
||||
|
||||
#ifndef AF_INCLUDE_H
|
||||
#define AF_INCLUDE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#ifdef AF_MALLOC
|
||||
#ifndef PP_MALLOC
|
||||
#define PP_MALLOC(size) AF_MALLOC(size)
|
||||
#define PP_REALLOC(p, size) AF_REALLOC(p, size)
|
||||
#define PP_FREE(p) AF_FREE(p)
|
||||
#endif // PP_MALLOC
|
||||
#endif // AF_MALLOC
|
||||
|
||||
#ifndef AF_MALLOC
|
||||
#define AF_MALLOC(size) malloc(size)
|
||||
#define AF_REALLOC(p, size) realloc(p, size)
|
||||
#define AF_FREE(p) free(p)
|
||||
#endif // AF_MALLOC
|
||||
|
||||
#ifndef AF_FILE
|
||||
#define AF_FILE FILE*
|
||||
#define AF_FREAD(p, size, nmemb, stream) fread(p, size, nmemb, stream)
|
||||
#define AF_FGETC(stream) fgetc(stream)
|
||||
#endif
|
||||
|
||||
#ifndef AF_DEBUG
|
||||
#define AF_DEBUG(...)
|
||||
#endif
|
||||
|
||||
#include "pretty-poly.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int8_t x, y;
|
||||
} af_point_t;
|
||||
pp_point_t af_point_transform(pp_point_t *p, pp_mat3_t *m);
|
||||
|
||||
typedef struct {
|
||||
uint8_t point_count;
|
||||
af_point_t *points;
|
||||
} af_path_t;
|
||||
|
||||
typedef struct {
|
||||
char codepoint;
|
||||
int8_t x, y, w, h;
|
||||
int8_t advance;
|
||||
uint8_t path_count;
|
||||
af_path_t *paths;
|
||||
} af_glyph_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t flags;
|
||||
uint16_t glyph_count;
|
||||
af_glyph_t *glyphs;
|
||||
} af_face_t;
|
||||
|
||||
typedef enum {
|
||||
AF_H_ALIGN_LEFT = 0, AF_H_ALIGN_CENTER = 1, AF_H_ALIGN_RIGHT = 2,
|
||||
AF_H_ALIGN_JUSTIFY = 4,
|
||||
AF_V_ALIGN_TOP = 8, AF_V_ALIGN_MIDDLE = 16, AF_V_ALIGN_BOTTOM = 32
|
||||
} af_align_t;
|
||||
|
||||
typedef struct {
|
||||
af_face_t *face; // font
|
||||
float size; // text size in pixels
|
||||
float line_height; // spacing between lines (%)
|
||||
float letter_spacing; // spacing between characters (%)
|
||||
float word_spacing; // spacing between words (%)
|
||||
af_align_t align; // horizontal and vertical alignment
|
||||
pp_mat3_t *transform; // arbitrary transformation
|
||||
} af_text_metrics_t;
|
||||
|
||||
bool af_load_font_file(AF_FILE file, af_face_t *face);
|
||||
void af_render_character(af_face_t *face, const char codepoint, af_text_metrics_t *tm);
|
||||
void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm);
|
||||
pp_rect_t af_measure(af_face_t *face, const char *text, af_text_metrics_t *tm);
|
||||
|
||||
#ifdef AF_USE_PRETTY_POLY
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef AF_IMPLEMENTATION
|
||||
|
||||
|
||||
/*
|
||||
helper functions
|
||||
*/
|
||||
|
||||
// big endian file reading helpers
|
||||
uint16_t ru16(AF_FILE file) {uint8_t w[2]; AF_FREAD((char *) w, 1, 2, file); return (uint16_t)w[0] << 8 | w[1];}
|
||||
int16_t rs16(AF_FILE file) {uint8_t w[2]; AF_FREAD((char *) w, 1, 2, file); return (uint16_t)w[0] << 8 | w[1];}
|
||||
uint32_t ru32(AF_FILE file) {uint8_t dw[4]; AF_FREAD((char *)dw, 1, 4, file); return (uint32_t)dw[0] << 24 | (uint32_t)dw[1] << 16 | (uint32_t)dw[2] << 8 | dw[3];}
|
||||
uint8_t ru8(AF_FILE file) {return AF_FGETC(file);}
|
||||
int8_t rs8(AF_FILE file) {return AF_FGETC(file);}
|
||||
|
||||
bool af_load_font_file(AF_FILE file, af_face_t *face) {
|
||||
// check header magic bytes are present
|
||||
char marker[4]; AF_FREAD(marker, 1, 4, file);
|
||||
if(memcmp(marker, "af!?", 4) != 0) {
|
||||
return false; // doesn't start with magic marker
|
||||
}
|
||||
|
||||
// extract flags and ensure none set
|
||||
face->flags = ru16(file);
|
||||
if(face->flags != 0) {
|
||||
return false; // unknown flags set
|
||||
}
|
||||
|
||||
// number of glyphs, paths, and points in font
|
||||
uint16_t glyph_count = ru16(file);
|
||||
uint16_t path_count = ru16(file);
|
||||
uint16_t point_count = ru16(file);
|
||||
|
||||
// allocate buffer to store font glyph, path, and point data
|
||||
void *buffer = AF_MALLOC(sizeof(af_glyph_t) * glyph_count + \
|
||||
sizeof( af_path_t) * path_count + \
|
||||
sizeof(af_point_t) * point_count);
|
||||
|
||||
if(!buffer) {
|
||||
return false; // failed memory allocation
|
||||
}
|
||||
|
||||
af_glyph_t *glyphs = (af_glyph_t *) buffer;
|
||||
af_path_t *paths = ( af_path_t *)(glyphs + (sizeof(af_glyph_t) * glyph_count));
|
||||
af_point_t *points = (af_point_t *)( paths + (sizeof( af_path_t) * path_count));
|
||||
|
||||
// load glyph dictionary
|
||||
face->glyph_count = glyph_count;
|
||||
face->glyphs = glyphs;
|
||||
for(int i = 0; i < glyph_count; i++) {
|
||||
af_glyph_t *glyph = &face->glyphs[i];
|
||||
glyph->codepoint = ru16(file);
|
||||
glyph->x = rs8(file);
|
||||
glyph->y = rs8(file);
|
||||
glyph->w = ru8(file);
|
||||
glyph->h = ru8(file);
|
||||
glyph->advance = ru8(file);
|
||||
glyph->path_count = ru8(file);
|
||||
glyph->paths = paths;
|
||||
paths += sizeof(af_path_t) * glyph->path_count;
|
||||
}
|
||||
|
||||
// load the glyph paths
|
||||
for(int i = 0; i < glyph_count; i++) {
|
||||
af_glyph_t *glyph = &face->glyphs[i];
|
||||
for(int j = 0; j < glyph->path_count; j++) {
|
||||
af_path_t *path = &glyph->paths[j];
|
||||
path->point_count = ru8(file);
|
||||
path->points = points;
|
||||
points += sizeof(af_point_t) * path->point_count;
|
||||
}
|
||||
}
|
||||
|
||||
// load the glyph points
|
||||
for(int i = 0; i < glyph_count; i++) {
|
||||
af_glyph_t *glyph = &face->glyphs[i];
|
||||
for(int j = 0; j < glyph->path_count; j++) {
|
||||
af_path_t *path = &glyph->paths[j];
|
||||
for(int k = 0; k < path->point_count; k++) {
|
||||
af_point_t *point = &path->points[k];
|
||||
point->x = ru8(file);
|
||||
point->y = ru8(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
af_glyph_t *find_glyph(af_face_t *face, char c) {
|
||||
for(int i = 0; i < face->glyph_count; i++) {
|
||||
if(face->glyphs[i].codepoint == c) {
|
||||
return &face->glyphs[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void af_render_glyph(af_glyph_t* glyph, af_text_metrics_t *tm) {
|
||||
assert(glyph != NULL);
|
||||
|
||||
pp_poly_t poly;
|
||||
poly.count = glyph->path_count;
|
||||
poly.paths = (pp_path_t *)AF_MALLOC(poly.count * sizeof(pp_path_t));
|
||||
for(uint32_t i = 0; i < poly.count; i++) {
|
||||
pp_path_t *path = &poly.paths[i];
|
||||
path->count = glyph->paths[i].point_count;
|
||||
path->points = (pp_point_t *)AF_MALLOC(glyph->paths[i].point_count * sizeof(pp_point_t));
|
||||
for(uint32_t j = 0; j < path->count; j++) {
|
||||
pp_point_t *point = &path->points[j];
|
||||
point->x = glyph->paths[i].points[j].x;
|
||||
point->y = glyph->paths[i].points[j].y;
|
||||
}
|
||||
}
|
||||
|
||||
pp_render(&poly);
|
||||
|
||||
for(uint32_t i = 0; i < poly.count; i++) {
|
||||
pp_path_t *path = &poly.paths[i];
|
||||
AF_FREE(path->points);
|
||||
}
|
||||
AF_FREE(poly.paths);
|
||||
}
|
||||
|
||||
void af_render_character(af_face_t *face, const char c, af_text_metrics_t *tm) {
|
||||
af_glyph_t *glyph = find_glyph(face, c);
|
||||
if(!glyph) {
|
||||
return;
|
||||
}
|
||||
af_render_glyph(glyph, tm);
|
||||
}
|
||||
|
||||
int get_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) {
|
||||
int line_width = 0;
|
||||
char *end = strchr(text, '\n');
|
||||
for(char c = *text; text < end; text++, c = *text) {
|
||||
af_glyph_t *glyph = find_glyph(face, c);
|
||||
if(!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == L' ') {
|
||||
line_width += (glyph->advance * tm->word_spacing) / 100.0f;
|
||||
} else {
|
||||
line_width += (glyph->advance * tm->letter_spacing) / 100.0f;
|
||||
}
|
||||
}
|
||||
return line_width;
|
||||
}
|
||||
|
||||
int get_max_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) {
|
||||
int max_width = 0;
|
||||
|
||||
char *end = strchr(text, '\n');
|
||||
while(end) {
|
||||
int width = get_line_width(face, text, tm);
|
||||
max_width = max_width < width ? width : max_width;
|
||||
text = end + 1;
|
||||
end = strchr(text, '\n');
|
||||
}
|
||||
|
||||
return max_width;
|
||||
}
|
||||
|
||||
|
||||
void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) {
|
||||
pp_mat3_t *old = pp_transform(NULL);
|
||||
|
||||
float line_height = (tm->line_height * 128.0f) / 100.0f;
|
||||
float scale = tm->size / 128.0f;
|
||||
|
||||
// find maximum line length
|
||||
int max_line_width = get_max_line_width(face, text, tm);
|
||||
|
||||
struct {
|
||||
float x, y;
|
||||
} caret;
|
||||
|
||||
caret.x = 0;
|
||||
caret.y = 0;
|
||||
|
||||
char *end = strchr(text, '\n');
|
||||
while(end) {
|
||||
int line_width = get_line_width(face, text, tm);
|
||||
|
||||
for(char c = *text; text < end; text++, c = *text) {
|
||||
af_glyph_t *glyph = find_glyph(face, c);
|
||||
if(!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pp_mat3_t caret_transform = *tm->transform;
|
||||
pp_mat3_scale(&caret_transform, scale, scale);
|
||||
pp_mat3_translate(&caret_transform, caret.x, caret.y);
|
||||
|
||||
if(tm->align == AF_H_ALIGN_CENTER) {
|
||||
pp_mat3_translate(&caret_transform, (max_line_width - line_width) / 2, 0);
|
||||
}
|
||||
|
||||
if(tm->align == AF_H_ALIGN_RIGHT) {
|
||||
pp_mat3_translate(&caret_transform, (max_line_width - line_width), 0);
|
||||
}
|
||||
|
||||
pp_transform(&caret_transform);
|
||||
|
||||
af_render_glyph(glyph, tm);
|
||||
|
||||
if(c == L' ') {
|
||||
caret.x += (glyph->advance * tm->word_spacing) / 100.0f;
|
||||
} else {
|
||||
caret.x += (glyph->advance * tm->letter_spacing) / 100.0f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
text = end + 1;
|
||||
end = strchr(text, '\n');
|
||||
|
||||
caret.x = 0;
|
||||
caret.y += line_height;
|
||||
}
|
||||
|
||||
|
||||
|
||||
pp_transform(old);
|
||||
}
|
||||
|
||||
pp_rect_t af_measure(af_face_t *face, const char *text, af_text_metrics_t *tm) {
|
||||
pp_rect_t result;
|
||||
bool first = true;
|
||||
pp_mat3_t t = *tm->transform;
|
||||
|
||||
for(size_t i = 0; i < strlen(text); i++) {
|
||||
af_glyph_t *glyph = find_glyph(face, text[i]);
|
||||
if(!glyph) {
|
||||
continue;
|
||||
}
|
||||
pp_rect_t r = {glyph->x, glyph->y, glyph->x + glyph->w, glyph->y + glyph->h};
|
||||
r = pp_rect_transform(&r, &t);
|
||||
pp_mat3_translate(&t, glyph->advance, 0);
|
||||
|
||||
if(first) {
|
||||
result = r;
|
||||
first = false;
|
||||
}else{
|
||||
result = pp_rect_merge(&result, &r);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // AF_IMPLEMENTATION
|
||||
|
||||
#endif // AF_INCLUDE_H
|
|
@ -6,7 +6,7 @@
|
|||
#include <optional>
|
||||
#include <map>
|
||||
|
||||
#include "pretty_poly/pretty-poly.h"
|
||||
#include "pretty-poly.h"
|
||||
#include "file_io.hpp"
|
||||
|
||||
namespace alright_fonts {
|
||||
|
|
|
@ -9,4 +9,10 @@ add_library(pico_vector
|
|||
|
||||
target_include_directories(pico_vector INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
target_link_libraries(pico_vector pico_graphics pico_stdlib hardware_interp)
|
||||
target_link_libraries(pico_vector pico_graphics pico_stdlib hardware_interp)
|
||||
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_LIST_DIR}/pico_vector.cpp
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
"-Wno-narrowing"
|
||||
)
|
|
@ -1,4 +1,5 @@
|
|||
#define PP_IMPLEMENTATION
|
||||
#define AF_IMPLEMENTATION
|
||||
#include "pico_vector.hpp"
|
||||
#include <vector>
|
||||
|
||||
|
@ -17,9 +18,9 @@ namespace pimoroni {
|
|||
|
||||
void PicoVector::rotate(pp_path_t *path, pp_point_t origin, float angle) {
|
||||
pp_mat3_t t = pp_mat3_identity();
|
||||
pp_mat3_translate(&t, -origin.x, -origin.y);
|
||||
pp_mat3_rotate(&t, angle);
|
||||
pp_mat3_translate(&t, origin.x, origin.y);
|
||||
pp_mat3_rotate(&t, angle);
|
||||
pp_mat3_translate(&t, -origin.x, -origin.y);
|
||||
transform(path, &t);
|
||||
}
|
||||
|
||||
|
@ -37,9 +38,9 @@ namespace pimoroni {
|
|||
|
||||
void PicoVector::rotate(pp_poly_t *poly, pp_point_t origin, float angle) {
|
||||
pp_mat3_t t = pp_mat3_identity();
|
||||
pp_mat3_translate(&t, -origin.x, -origin.y);
|
||||
pp_mat3_rotate(&t, angle);
|
||||
pp_mat3_translate(&t, origin.x, origin.y);
|
||||
pp_mat3_rotate(&t, angle);
|
||||
pp_mat3_translate(&t, -origin.x, -origin.y);
|
||||
transform(poly, &t);
|
||||
}
|
||||
|
||||
|
@ -55,9 +56,15 @@ namespace pimoroni {
|
|||
}
|
||||
}
|
||||
|
||||
pp_point_t PicoVector::text(std::string_view text, pp_point_t offset, pp_mat3_t *t) {
|
||||
pp_point_t PicoVector::text(std::string_view text, pp_mat3_t *t) {
|
||||
pp_point_t caret = {0, 0};
|
||||
|
||||
text_metrics.transform = t;
|
||||
|
||||
af_render(text_metrics.face, text.data(), &text_metrics);
|
||||
|
||||
return caret;
|
||||
/*
|
||||
// Align text from the bottom left
|
||||
caret.y += (PP_COORD_TYPE)text_metrics.line_height;
|
||||
|
||||
|
@ -68,7 +75,9 @@ namespace pimoroni {
|
|||
pp_point_t space;
|
||||
pp_point_t carriage_return = {0, -(PP_COORD_TYPE)text_metrics.line_height};
|
||||
|
||||
space.x = alright_fonts::measure_character(text_metrics, ' ').w;
|
||||
char spc = ' ';
|
||||
|
||||
space.x = af_measure(text_metrics.face, &spc, &text_metrics).w;
|
||||
if (space.x == 0) {
|
||||
space.x = text_metrics.word_spacing;
|
||||
}
|
||||
|
@ -97,7 +106,7 @@ namespace pimoroni {
|
|||
|
||||
uint16_t word_width = 0;
|
||||
for(size_t j = i; j < next_break; j++) {
|
||||
word_width += alright_fonts::measure_character(text_metrics, text[j]).w;
|
||||
word_width += af_measure(text_metrics.face, &text[j], &text_metrics).w;
|
||||
word_width += text_metrics.letter_spacing;
|
||||
}
|
||||
|
||||
|
@ -114,10 +123,15 @@ namespace pimoroni {
|
|||
caret = pp_point_add(&caret, &space);
|
||||
carriage_return = pp_point_add(&carriage_return, &space);
|
||||
} else {
|
||||
alright_fonts::render_character(text_metrics, text[j], caret, t);
|
||||
// apply the caret offset...
|
||||
pp_mat3_t pos = pp_mat3_identity();
|
||||
pp_mat3_mul(&pos, t);
|
||||
pp_mat3_translate(&pos, caret.x, caret.y);
|
||||
text_metrics.transform = &pos;
|
||||
af_render_character(text_metrics.face, text[j], &text_metrics);
|
||||
}
|
||||
pp_point_t advance = {
|
||||
(PP_COORD_TYPE)alright_fonts::measure_character(text_metrics, text[j]).w + text_metrics.letter_spacing,
|
||||
(PP_COORD_TYPE)af_measure(text_metrics.face, &text[j], &text_metrics).w + text_metrics.letter_spacing,
|
||||
(PP_COORD_TYPE)0
|
||||
};
|
||||
advance = pp_point_transform(&advance, t);
|
||||
|
@ -129,5 +143,6 @@ namespace pimoroni {
|
|||
}
|
||||
|
||||
return {caret.x, caret.y};
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -1,5 +1,23 @@
|
|||
#include "pretty_poly/pretty-poly.h"
|
||||
#include "alright_fonts.hpp"
|
||||
|
||||
#include "af-file-io.h"
|
||||
#include "af-memory.h"
|
||||
|
||||
#define AF_FILE void*
|
||||
#define AF_FREAD(p, size, nmemb, stream) fileio_read(stream, p, nmemb)
|
||||
#define AF_FGETC(stream) fileio_getc(stream)
|
||||
|
||||
#define AF_MALLOC(size) af_malloc(size)
|
||||
#define AF_REALLOC(p, size) af_realloc(p, size)
|
||||
#define AF_FREE(p) af_free(p)
|
||||
|
||||
#define PP_MALLOC(size) af_malloc(size)
|
||||
#define PP_REALLOC(p, size) af_realloc(p, size)
|
||||
#define PP_FREE(p) af_free(p)
|
||||
|
||||
#define AF_DEBUG(...) af_debug(__VA_ARGS__)
|
||||
|
||||
#include "pretty-poly.h"
|
||||
#include "alright-fonts.h"
|
||||
#include "pico_graphics.hpp"
|
||||
|
||||
pp_rect_t pp_contour_bounds(const pp_path_t *c);
|
||||
|
@ -12,7 +30,7 @@ namespace pimoroni {
|
|||
class PicoVector {
|
||||
private:
|
||||
static PicoGraphics *graphics;
|
||||
alright_fonts::text_metrics_t text_metrics;
|
||||
af_text_metrics_t text_metrics;
|
||||
static constexpr uint8_t alpha_map[4] {0, 128, 192, 255};
|
||||
|
||||
public:
|
||||
|
@ -24,6 +42,15 @@ namespace pimoroni {
|
|||
pp_antialias(graphics->supports_alpha_blend() ? PP_AA_X4 : PP_AA_NONE);
|
||||
|
||||
pp_clip(graphics->clip.x, graphics->clip.y, graphics->clip.w, graphics->clip.h);
|
||||
|
||||
text_metrics.align = AF_H_ALIGN_LEFT;
|
||||
text_metrics.line_height = 110;
|
||||
text_metrics.letter_spacing = 95;
|
||||
text_metrics.word_spacing = 200;
|
||||
text_metrics.size = 48;
|
||||
// Shoud be set before rendering chars
|
||||
//text_metrics.transform = (pp_mat3_t *)af_malloc(sizeof(pp_mat3_t));
|
||||
//*text_metrics.transform = pp_mat3_identity();
|
||||
}
|
||||
|
||||
static void tile_callback(const pp_tile_t *tile) {
|
||||
|
@ -66,18 +93,25 @@ namespace pimoroni {
|
|||
}
|
||||
|
||||
void set_font_size(unsigned int font_size) {
|
||||
text_metrics.set_size(font_size);
|
||||
text_metrics.size = font_size;
|
||||
}
|
||||
|
||||
bool set_font(std::string_view font_path, unsigned int font_size) {
|
||||
bool result = text_metrics.face.load(font_path);
|
||||
if(text_metrics.face) {
|
||||
af_free(text_metrics.face->glyphs);
|
||||
af_free(text_metrics.face);
|
||||
}
|
||||
text_metrics.face = (af_face_t *)af_malloc(sizeof(af_face_t));
|
||||
//bool result = text_metrics.face.load(font_path);
|
||||
void* font = fileio_open(font_path.data());
|
||||
bool result = af_load_font_file(font, text_metrics.face);
|
||||
|
||||
set_font_size(font_size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pp_point_t text(std::string_view text, pp_point_t origin, pp_mat3_t *t);
|
||||
pp_point_t text(std::string_view text, pp_mat3_t *t);
|
||||
|
||||
void transform(pp_path_t *path, pp_mat3_t *t);
|
||||
void transform(pp_poly_t *poly, pp_mat3_t *t);
|
||||
|
|
|
@ -0,0 +1,619 @@
|
|||
/*
|
||||
|
||||
Pretty Poly 🦜 - super-sampling polygon renderer for low resource platforms.
|
||||
|
||||
Jonathan Williamson, August 2022
|
||||
Examples, source, and more: https://github.com/lowfatcode/pretty-poly
|
||||
MIT License https://github.com/lowfatcode/pretty-poly/blob/main/LICENSE
|
||||
|
||||
An easy way to render high quality graphics in embedded applications running
|
||||
on resource constrained microcontrollers such as the Cortex M0 and up.
|
||||
|
||||
- Renders polygons: concave, self-intersecting, multi contour, holes, etc.
|
||||
- C11 header only library: simply copy the header file into your project
|
||||
- Tile based renderer: low memory footprint, cache coherency
|
||||
- Low memory usage: ~4kB of heap memory required
|
||||
- High speed on low resource platforms: optionally no floating point
|
||||
- Antialiasing modes: X1 (none), X4 and X16 super sampling
|
||||
- Bounds clipping: all results clipped to supplied clip rectangle
|
||||
- Pixel format agnostic: renders a "tile" to blend into your framebuffer
|
||||
- Support for hardware interpolators on rp2040 (thanks @MichaelBell!)
|
||||
|
||||
Contributor bwaaaaaarks! 🦜
|
||||
|
||||
@MichaelBell - lots of bug fixes, performance boosts, and suggestions.
|
||||
@gadgetoid - integrating into the PicoVector library and testing.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef PP_INCLUDE_H
|
||||
#define PP_INCLUDE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef PP_MALLOC
|
||||
#define PP_MALLOC(size) malloc(size)
|
||||
#define PP_REALLOC(p, size) realloc(p, size)
|
||||
#define PP_FREE(p) free(p)
|
||||
#endif
|
||||
|
||||
#ifndef PP_COORD_TYPE
|
||||
#define PP_COORD_TYPE float
|
||||
#endif
|
||||
|
||||
#ifndef PP_NODE_BUFFER_HEIGHT
|
||||
#define PP_NODE_BUFFER_HEIGHT 16
|
||||
#endif
|
||||
|
||||
#ifndef PP_MAX_NODES_PER_SCANLINE
|
||||
#define PP_MAX_NODES_PER_SCANLINE 16
|
||||
#endif
|
||||
|
||||
#ifndef PP_TILE_BUFFER_SIZE
|
||||
#define PP_TILE_BUFFER_SIZE 4096
|
||||
#endif
|
||||
|
||||
#if defined(PICO_ON_DEVICE) && PICO_ON_DEVICE
|
||||
#define USE_RP2040_INTERP
|
||||
#include "hardware/interp.h"
|
||||
#endif
|
||||
|
||||
#ifdef PP_DEBUG
|
||||
#define debug(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define debug(...)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 3x3 matrix type allows for optional transformation of polygon during render
|
||||
typedef struct {
|
||||
float v00, v10, v20, v01, v11, v21, v02, v12, v22;
|
||||
} pp_mat3_t;
|
||||
pp_mat3_t pp_mat3_identity();
|
||||
void pp_mat3_rotate(pp_mat3_t *m, float a);
|
||||
void pp_mat3_rotate_rad(pp_mat3_t *m, float a);
|
||||
void pp_mat3_translate(pp_mat3_t *m, float x, float y);
|
||||
void pp_mat3_scale(pp_mat3_t *m, float x, float y);
|
||||
void pp_mat3_mul(pp_mat3_t *m1, pp_mat3_t *m2);
|
||||
|
||||
// point type used to hold polygon vertex coordinates
|
||||
typedef struct __attribute__((__packed__)) pp_point_t {
|
||||
PP_COORD_TYPE x, y;
|
||||
} pp_point_t;
|
||||
pp_point_t pp_point_add(pp_point_t *p1, pp_point_t *p2);
|
||||
pp_point_t pp_point_sub(pp_point_t *p1, pp_point_t *p2);
|
||||
pp_point_t pp_point_mul(pp_point_t *p1, pp_point_t *p2);
|
||||
pp_point_t pp_point_div(pp_point_t *p1, pp_point_t *p2);
|
||||
pp_point_t pp_point_transform(pp_point_t *p, pp_mat3_t *m);
|
||||
|
||||
// rect type
|
||||
typedef struct {
|
||||
int32_t x, y, w, h;
|
||||
} pp_rect_t;
|
||||
bool pp_rect_empty(pp_rect_t *r);
|
||||
pp_rect_t pp_rect_intersection(pp_rect_t *r1, pp_rect_t *r2);
|
||||
pp_rect_t pp_rect_merge(pp_rect_t *r1, pp_rect_t *r2);
|
||||
pp_rect_t pp_rect_transform(pp_rect_t *r, pp_mat3_t *m);
|
||||
|
||||
// antialias levels
|
||||
typedef enum {PP_AA_NONE = 0, PP_AA_X4 = 1, PP_AA_X16 = 2} pp_antialias_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t x, y, w, h;
|
||||
uint32_t stride;
|
||||
uint8_t *data;
|
||||
} pp_tile_t;
|
||||
|
||||
typedef struct {
|
||||
pp_point_t *points;
|
||||
uint32_t count;
|
||||
} pp_path_t;
|
||||
|
||||
typedef struct {
|
||||
pp_path_t *paths;
|
||||
uint32_t count;
|
||||
} pp_poly_t;
|
||||
|
||||
// user settings
|
||||
typedef void (*pp_tile_callback_t)(const pp_tile_t *tile);
|
||||
|
||||
extern pp_rect_t _pp_clip;
|
||||
extern pp_tile_callback_t _pp_tile_callback;
|
||||
extern pp_antialias_t _pp_antialias;
|
||||
extern pp_mat3_t *_pp_transform;
|
||||
|
||||
void pp_clip(int32_t x, int32_t y, int32_t w, int32_t h);
|
||||
void pp_tile_callback(pp_tile_callback_t callback);
|
||||
void pp_antialias(pp_antialias_t antialias);
|
||||
pp_mat3_t *pp_transform(pp_mat3_t *transform);
|
||||
void pp_render(pp_poly_t *polygon);
|
||||
|
||||
pp_rect_t pp_contour_bounds(const pp_path_t *c);
|
||||
pp_rect_t pp_polygon_bounds(pp_poly_t *p);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PP_IMPLEMENTATION
|
||||
|
||||
pp_rect_t _pp_clip = (pp_rect_t){0, 0, 320, 240};
|
||||
pp_tile_callback_t _pp_tile_callback = NULL;
|
||||
pp_antialias_t _pp_antialias = PP_AA_X4;
|
||||
pp_mat3_t *_pp_transform = NULL;
|
||||
|
||||
int _pp_max(int a, int b) { return a > b ? a : b; }
|
||||
int _pp_min(int a, int b) { return a < b ? a : b; }
|
||||
int _pp_sign(int v) {return (v > 0) - (v < 0);}
|
||||
void _pp_swap(int *a, int *b) {int t = *a; *a = *b; *b = t;}
|
||||
|
||||
// pp_mat3_t implementation
|
||||
pp_mat3_t pp_mat3_identity() {
|
||||
pp_mat3_t m; memset(&m, 0, sizeof(pp_mat3_t)); m.v00 = m.v11 = m.v22 = 1.0f; return m;}
|
||||
void pp_mat3_rotate(pp_mat3_t *m, float a) {
|
||||
pp_mat3_rotate_rad(m, a * M_PI / 180.0f);}
|
||||
void pp_mat3_rotate_rad(pp_mat3_t *m, float a) {
|
||||
float c = cosf(a), s = sinf(a); pp_mat3_t r = pp_mat3_identity();
|
||||
r.v00 = c; r.v01 = -s; r.v10 = s; r.v11 = c; pp_mat3_mul(m, &r); }
|
||||
void pp_mat3_translate(pp_mat3_t *m, float x, float y) {
|
||||
pp_mat3_t r = pp_mat3_identity(); r.v02 = x; r.v12 = y; pp_mat3_mul(m, &r);}
|
||||
void pp_mat3_scale(pp_mat3_t *m, float x, float y) {
|
||||
pp_mat3_t r = pp_mat3_identity(); r.v00 = x; r.v11 = y; pp_mat3_mul(m, &r);}
|
||||
void pp_mat3_mul(pp_mat3_t *m1, pp_mat3_t *m2) {
|
||||
pp_mat3_t r;
|
||||
r.v00 = m1->v00 * m2->v00 + m1->v01 * m2->v10 + m1->v02 * m2->v20;
|
||||
r.v01 = m1->v00 * m2->v01 + m1->v01 * m2->v11 + m1->v02 * m2->v21;
|
||||
r.v02 = m1->v00 * m2->v02 + m1->v01 * m2->v12 + m1->v02 * m2->v22;
|
||||
r.v10 = m1->v10 * m2->v00 + m1->v11 * m2->v10 + m1->v12 * m2->v20;
|
||||
r.v11 = m1->v10 * m2->v01 + m1->v11 * m2->v11 + m1->v12 * m2->v21;
|
||||
r.v12 = m1->v10 * m2->v02 + m1->v11 * m2->v12 + m1->v12 * m2->v22;
|
||||
r.v20 = m1->v20 * m2->v00 + m1->v21 * m2->v10 + m1->v22 * m2->v20;
|
||||
r.v21 = m1->v20 * m2->v01 + m1->v21 * m2->v11 + m1->v22 * m2->v21;
|
||||
r.v22 = m1->v20 * m2->v02 + m1->v21 * m2->v12 + m1->v22 * m2->v22;
|
||||
*m1 = r;
|
||||
}
|
||||
|
||||
// pp_point_t implementation
|
||||
pp_point_t pp_point_add(pp_point_t *p1, pp_point_t *p2) {
|
||||
return (pp_point_t){.x = p1->x + p2->x, .y = p1->y + p2->y};
|
||||
}
|
||||
pp_point_t pp_point_sub(pp_point_t *p1, pp_point_t *p2) {
|
||||
return (pp_point_t){.x = p1->x - p2->x, .y = p1->y - p2->y};
|
||||
}
|
||||
pp_point_t pp_point_mul(pp_point_t *p1, pp_point_t *p2) {
|
||||
return (pp_point_t){.x = p1->x * p2->x, .y = p1->y * p2->y};
|
||||
}
|
||||
pp_point_t pp_point_div(pp_point_t *p1, pp_point_t *p2) {
|
||||
return (pp_point_t){.x = p1->x / p2->x, .y = p1->y / p2->y};
|
||||
}
|
||||
pp_point_t pp_point_transform(pp_point_t *p, pp_mat3_t *m) {
|
||||
return (pp_point_t){
|
||||
.x = (m->v00 * p->x + m->v01 * p->y + m->v02),
|
||||
.y = (m->v10 * p->x + m->v11 * p->y + m->v12)
|
||||
};
|
||||
}
|
||||
|
||||
// pp_rect_t implementation
|
||||
bool pp_rect_empty(pp_rect_t *r) {
|
||||
return r->w == 0 || r->h == 0;
|
||||
}
|
||||
pp_rect_t pp_rect_intersection(pp_rect_t *r1, pp_rect_t *r2) {
|
||||
return (pp_rect_t){
|
||||
.x = _pp_max(r1->x, r2->x), .y = _pp_max(r1->y, r2->y),
|
||||
.w = _pp_max(0, _pp_min(r1->x + r1->w, r2->x + r2->w) - _pp_max(r1->x, r2->x)),
|
||||
.h = _pp_max(0, _pp_min(r1->y + r1->h, r2->y + r2->h) - _pp_max(r1->y, r2->y))
|
||||
};
|
||||
}
|
||||
pp_rect_t pp_rect_merge(pp_rect_t *r1, pp_rect_t *r2) {
|
||||
return (pp_rect_t){
|
||||
.x = _pp_min(r1->x, r2->x),
|
||||
.y = _pp_min(r1->y, r2->y),
|
||||
.w = _pp_max(r1->x + r1->w, r2->x + r2->w) - _pp_min(r1->x, r2->x),
|
||||
.h = _pp_max(r1->y + r1->h, r2->y + r2->h) - _pp_min(r1->y, r2->y)
|
||||
};
|
||||
}
|
||||
pp_rect_t pp_rect_transform(pp_rect_t *r, pp_mat3_t *m) {
|
||||
pp_point_t tl = {.x = (PP_COORD_TYPE)r->x, .y = (PP_COORD_TYPE)r->y};
|
||||
pp_point_t tr = {.x = (PP_COORD_TYPE)r->x + (PP_COORD_TYPE)r->w, .y = (PP_COORD_TYPE)r->y};
|
||||
pp_point_t bl = {.x = (PP_COORD_TYPE)r->x, .y = (PP_COORD_TYPE)r->y + (PP_COORD_TYPE)r->h};
|
||||
pp_point_t br = {.x = (PP_COORD_TYPE)r->x + (PP_COORD_TYPE)r->w, .y = (PP_COORD_TYPE)r->y + (PP_COORD_TYPE)r->h};
|
||||
|
||||
tl = pp_point_transform(&tl, m);
|
||||
tr = pp_point_transform(&tr, m);
|
||||
bl = pp_point_transform(&bl, m);
|
||||
br = pp_point_transform(&br, m);
|
||||
|
||||
PP_COORD_TYPE minx = _pp_min(tl.x, _pp_min(tr.x, _pp_min(bl.x, br.x)));
|
||||
PP_COORD_TYPE miny = _pp_min(tl.y, _pp_min(tr.y, _pp_min(bl.y, br.y)));
|
||||
PP_COORD_TYPE maxx = _pp_max(tl.x, _pp_max(tr.x, _pp_max(bl.x, br.x)));
|
||||
PP_COORD_TYPE maxy = _pp_max(tl.y, _pp_max(tr.y, _pp_max(bl.y, br.y)));
|
||||
|
||||
return (pp_rect_t){
|
||||
.x = (int32_t)minx,
|
||||
.y = (int32_t)miny,
|
||||
.w = (int32_t)(maxx - minx),
|
||||
.h = (int32_t)(maxy - miny)
|
||||
};
|
||||
}
|
||||
|
||||
// pp_tile_t implementation
|
||||
uint8_t pp_tile_get(const pp_tile_t *tile, const int32_t x, const int32_t y) {
|
||||
return tile->data[(x - tile->x) + (y - tile->y) * tile->stride] * (255 >> _pp_antialias >> _pp_antialias);
|
||||
}
|
||||
|
||||
// pp_contour_t implementation
|
||||
pp_rect_t pp_contour_bounds(const pp_path_t *c) {
|
||||
int minx = c->points[0].x, maxx = minx;
|
||||
int miny = c->points[0].y, maxy = miny;
|
||||
for(uint32_t i = 1; i < c->count; i++) {
|
||||
minx = _pp_min(minx, c->points[i].x);
|
||||
miny = _pp_min(miny, c->points[i].y);
|
||||
maxx = _pp_max(maxx, c->points[i].x);
|
||||
maxy = _pp_max(maxy, c->points[i].y);
|
||||
}
|
||||
return (pp_rect_t){.x = minx, .y = miny, .w = maxx - minx, .h = maxy - miny};
|
||||
}
|
||||
|
||||
pp_rect_t pp_polygon_bounds(pp_poly_t *p) {
|
||||
if(p->count == 0) {return (pp_rect_t){};}
|
||||
pp_rect_t b = pp_contour_bounds(&p->paths[0]);
|
||||
for(uint32_t i = 1; i < p->count; i++) {
|
||||
pp_rect_t cb = pp_contour_bounds(&p->paths[i]);
|
||||
b = pp_rect_merge(&b, &cb);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// buffer that each tile is rendered into before callback
|
||||
// allocate one extra byte to allow a small optimization in the row renderer
|
||||
const uint32_t tile_buffer_size = PP_TILE_BUFFER_SIZE;
|
||||
uint8_t tile_buffer[PP_TILE_BUFFER_SIZE + 1];
|
||||
|
||||
// polygon node buffer handles at most 16 line intersections per scanline
|
||||
// is this enough for cjk/emoji? (requires a 2kB buffer)
|
||||
int32_t nodes[PP_NODE_BUFFER_HEIGHT][PP_MAX_NODES_PER_SCANLINE * 2];
|
||||
uint32_t node_counts[PP_NODE_BUFFER_HEIGHT];
|
||||
|
||||
|
||||
void pp_clip(int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||
_pp_clip = (pp_rect_t){.x = x, .y = y, .w = w, .h = h};
|
||||
}
|
||||
|
||||
void pp_tile_callback(pp_tile_callback_t callback) {
|
||||
_pp_tile_callback = callback;
|
||||
}
|
||||
|
||||
// maximum tile bounds determined by antialias level
|
||||
uint32_t _pp_tile_width, _pp_tile_height;
|
||||
void pp_antialias(pp_antialias_t antialias) {
|
||||
_pp_antialias = antialias;
|
||||
// recalculate the tile size for rendering based on antialiasing level
|
||||
_pp_tile_height = PP_NODE_BUFFER_HEIGHT >> _pp_antialias;
|
||||
_pp_tile_width = (int)(tile_buffer_size / _pp_tile_height);
|
||||
}
|
||||
|
||||
pp_mat3_t *pp_transform(pp_mat3_t *transform) {
|
||||
pp_mat3_t *old = _pp_transform;
|
||||
_pp_transform = transform;
|
||||
return old;
|
||||
}
|
||||
|
||||
// write out the tile bits
|
||||
void debug_tile(const pp_tile_t *tile) {
|
||||
debug(" - tile %d, %d (%d x %d)\n", tile->x, tile->y, tile->w, tile->h);
|
||||
for(int32_t y = 0; y < tile->h; y++) {
|
||||
debug("[%3d]: ", y);
|
||||
for(int32_t x = 0; x < tile->w; x++) {
|
||||
debug("%02x", pp_tile_get(tile, x, y));
|
||||
}
|
||||
debug("\n");
|
||||
}
|
||||
debug("-----------------------\n");
|
||||
}
|
||||
|
||||
void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) {
|
||||
int32_t sx = start.x, sy = start.y, ex = end.x, ey = end.y;
|
||||
|
||||
if(ey < sy) {
|
||||
// swap endpoints if line "pointing up", we do this because we
|
||||
// alway skip the last scanline (so that polygons can but cleanly
|
||||
// up against each other without overlap)
|
||||
int32_t ty = sy; sy = ey; ey = ty;
|
||||
int32_t tx = sx; sx = ex; ex = tx;
|
||||
}
|
||||
|
||||
// Early out if line is completely outside the tile, or has no lines
|
||||
if (ey < 0 || sy >= (int)PP_NODE_BUFFER_HEIGHT || sy == ey) return;
|
||||
|
||||
debug(" + line segment from %d, %d to %d, %d\n", sx, sy, ex, ey);
|
||||
|
||||
// Determine how many in-bounds lines to render
|
||||
int y = _pp_max(0, sy);
|
||||
int count = _pp_min((int)PP_NODE_BUFFER_HEIGHT, ey) - y;
|
||||
|
||||
// Handle cases where x is completely off to one side or other
|
||||
if (_pp_max(sx, ex) <= 0) {
|
||||
while (count--) {
|
||||
nodes[y][node_counts[y]++] = 0;
|
||||
++y;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const int full_tile_width = (_pp_tile_width << _pp_antialias);
|
||||
if (_pp_min(sx, ex) >= full_tile_width) {
|
||||
while (count--) {
|
||||
nodes[y][node_counts[y]++] = full_tile_width;
|
||||
++y;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal case
|
||||
int x = sx;
|
||||
int e = 0;
|
||||
|
||||
const int xinc = _pp_sign(ex - sx);
|
||||
const int einc = abs(ex - sx) + 1;
|
||||
const int dy = ey - sy;
|
||||
|
||||
// If sy < 0 jump to the start, note this does use a divide
|
||||
// but potentially saves many wasted loops below, so is likely worth it.
|
||||
if (sy < 0) {
|
||||
e = einc * -sy;
|
||||
int xjump = e / dy;
|
||||
e -= dy * xjump;
|
||||
x += xinc * xjump;
|
||||
}
|
||||
|
||||
#ifdef USE_RP2040_INTERP
|
||||
interp1->base[1] = full_tile_width;
|
||||
interp1->accum[0] = x;
|
||||
|
||||
// loop over scanlines
|
||||
while(count--) {
|
||||
// consume accumulated error
|
||||
while(e > dy) {e -= dy; interp1->add_raw[0] = xinc;}
|
||||
|
||||
// clamp node x value to tile bounds
|
||||
const int nx = interp1->peek[0];
|
||||
debug(" + adding node at %d, %d\n", x, y);
|
||||
// add node to node list
|
||||
nodes[y][node_counts[y]++] = nx;
|
||||
|
||||
// step to next scanline and accumulate error
|
||||
y++;
|
||||
e += einc;
|
||||
}
|
||||
#else
|
||||
// loop over scanlines
|
||||
while(count--) {
|
||||
// consume accumulated error
|
||||
while(e > dy) {e -= dy; x += xinc;}
|
||||
|
||||
// clamp node x value to tile bounds
|
||||
int nx = _pp_max(_pp_min(x, full_tile_width), 0);
|
||||
debug(" + adding node at %d, %d\n", x, y);
|
||||
// add node to node list
|
||||
nodes[y][node_counts[y]++] = nx;
|
||||
|
||||
// step to next scanline and accumulate error
|
||||
y++;
|
||||
e += einc;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void build_nodes(pp_path_t *contour, pp_rect_t *bounds) {
|
||||
PP_COORD_TYPE aa_scale = (PP_COORD_TYPE)(1 << _pp_antialias);
|
||||
|
||||
pp_point_t tile_origin = (pp_point_t) {
|
||||
.x = bounds->x * aa_scale,
|
||||
.y = bounds->y * aa_scale
|
||||
};
|
||||
|
||||
// start with the last point to close the loop
|
||||
pp_point_t last = {
|
||||
.x = (contour->points[contour->count - 1].x),
|
||||
.y = (contour->points[contour->count - 1].y)
|
||||
};
|
||||
|
||||
if(_pp_transform) {
|
||||
last = pp_point_transform(&last, _pp_transform);
|
||||
}
|
||||
|
||||
last.x *= aa_scale;
|
||||
last.y *= aa_scale;
|
||||
|
||||
last = pp_point_sub(&last, &tile_origin);
|
||||
|
||||
for(uint32_t i = 0; i < contour->count; i++) {
|
||||
pp_point_t point = {
|
||||
.x = (contour->points[i].x),
|
||||
.y = (contour->points[i].y)
|
||||
};
|
||||
|
||||
if(_pp_transform) {
|
||||
point = pp_point_transform(&point, _pp_transform);
|
||||
}
|
||||
|
||||
point.x *= aa_scale;
|
||||
point.y *= aa_scale;
|
||||
|
||||
point = pp_point_sub(&point, &tile_origin);
|
||||
|
||||
add_line_segment_to_nodes(last, point);
|
||||
|
||||
last = point;
|
||||
}
|
||||
}
|
||||
|
||||
int compare_nodes(const void* a, const void* b) {
|
||||
return *((int*)a) - *((int*)b);
|
||||
}
|
||||
|
||||
pp_rect_t render_nodes(uint8_t *buffer, pp_rect_t *tb) {
|
||||
int maxy = -1;
|
||||
|
||||
pp_rect_t rb; // render bounds
|
||||
rb.y = 0;
|
||||
rb.x = tb->w;
|
||||
int maxx = 0;
|
||||
PP_COORD_TYPE aa_scale = (PP_COORD_TYPE)(1 << _pp_antialias);
|
||||
int anitialias_mask = (1 << _pp_antialias) - 1;
|
||||
|
||||
for(int32_t y = 0; y < PP_NODE_BUFFER_HEIGHT; y++) {
|
||||
if(node_counts[y] == 0) {
|
||||
if (y == rb.y) ++rb.y;
|
||||
continue;
|
||||
}
|
||||
|
||||
qsort(&nodes[y][0], node_counts[y], sizeof(int), compare_nodes);
|
||||
|
||||
unsigned char* row_data = &buffer[(y >> _pp_antialias) * _pp_tile_width];
|
||||
bool rendered_any = false;
|
||||
for(uint32_t i = 0; i < node_counts[y]; i += 2) {
|
||||
int sx = nodes[y][i + 0];
|
||||
int ex = nodes[y][i + 1];
|
||||
|
||||
if(sx == ex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rendered_any = true;
|
||||
|
||||
maxx = _pp_max((ex - 1) >> _pp_antialias, maxx);
|
||||
|
||||
debug(" - render span at %d from %d to %d\n", y, sx, ex);
|
||||
|
||||
if (_pp_antialias) {
|
||||
int ax = sx / aa_scale;
|
||||
const int aex = ex / aa_scale;
|
||||
|
||||
rb.x = _pp_min(ax, rb.x);
|
||||
|
||||
if (ax == aex) {
|
||||
row_data[ax] += ex - sx;
|
||||
continue;
|
||||
}
|
||||
|
||||
row_data[ax] += aa_scale - (sx & anitialias_mask);
|
||||
for(ax++; ax < aex; ax++) {
|
||||
row_data[ax] += aa_scale;
|
||||
}
|
||||
|
||||
// This might add 0 to the byte after the end of the row, we pad the tile data
|
||||
// by 1 byte to ensure that is OK
|
||||
row_data[ax] += ex & anitialias_mask;
|
||||
} else {
|
||||
rb.x = _pp_min(sx, rb.x);
|
||||
for(int x = sx; x < ex; x++) {
|
||||
row_data[x]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rendered_any) {
|
||||
debug(" - rendered line %d\n", y);
|
||||
maxy = y;
|
||||
}
|
||||
else if (y == rb.y) {
|
||||
debug(" - render nothing on line %d\n", y);
|
||||
++rb.y;
|
||||
}
|
||||
}
|
||||
|
||||
rb.y >>= _pp_antialias;
|
||||
maxy >>= _pp_antialias;
|
||||
rb.w = (maxx >= rb.x) ? maxx + 1 - rb.x : 0;
|
||||
rb.h = (maxy >= rb.y) ? maxy + 1 - rb.y : 0;
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
void pp_render(pp_poly_t *polygon) {
|
||||
|
||||
debug("> draw polygon with %u contours\n", polygon->count);
|
||||
|
||||
if(polygon->count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// determine extreme bounds
|
||||
pp_rect_t polygon_bounds = pp_polygon_bounds(polygon);
|
||||
|
||||
if(_pp_transform) {
|
||||
polygon_bounds = pp_rect_transform(&polygon_bounds, _pp_transform);
|
||||
}
|
||||
|
||||
debug(" - bounds %d, %d (%d x %d)\n", polygon_bounds.x, polygon_bounds.y, polygon_bounds.w, polygon_bounds.h);
|
||||
debug(" - clip %d, %d (%d x %d)\n", _pp_clip.x, _pp_clip.y, _pp_clip.w, _pp_clip.h);
|
||||
|
||||
#ifdef USE_RP2040_INTERP
|
||||
interp_hw_save_t interp1_save;
|
||||
interp_save(interp1, &interp1_save);
|
||||
|
||||
interp_config cfg = interp_default_config();
|
||||
interp_config_set_clamp(&cfg, true);
|
||||
interp_config_set_signed(&cfg, true);
|
||||
interp_set_config(interp1, 0, &cfg);
|
||||
interp1->base[0] = 0;
|
||||
#endif
|
||||
|
||||
// iterate over tiles
|
||||
debug(" - processing tiles\n");
|
||||
for(int32_t y = polygon_bounds.y; y < polygon_bounds.y + polygon_bounds.h; y += _pp_tile_height) {
|
||||
for(int32_t x = polygon_bounds.x; x < polygon_bounds.x + polygon_bounds.w; x += _pp_tile_width) {
|
||||
pp_rect_t tb = (pp_rect_t){.x = x, .y = y, .w = _pp_tile_width, .h = _pp_tile_height};
|
||||
tb = pp_rect_intersection(&tb, &_pp_clip);
|
||||
debug(" : %d, %d (%d x %d)\n", tb.x, tb.y, tb.w, tb.h);
|
||||
|
||||
// if no intersection then skip tile
|
||||
if(pp_rect_empty(&tb)) { debug(" : empty when clipped, skipping\n"); continue; }
|
||||
|
||||
// clear existing tile data and nodes
|
||||
memset(node_counts, 0, sizeof(node_counts));
|
||||
memset(tile_buffer, 0, tile_buffer_size);
|
||||
|
||||
// build the nodes for each pp_path_t
|
||||
for(uint32_t i = 0; i < polygon->count; i++) {
|
||||
pp_path_t pp_path_t = polygon->paths[i];
|
||||
debug(" : build nodes for path\n");
|
||||
build_nodes(&pp_path_t, &tb);
|
||||
}
|
||||
|
||||
debug(" : render the tile\n");
|
||||
// render the tile
|
||||
|
||||
pp_rect_t rb = render_nodes(tile_buffer, &tb);
|
||||
tb.x += rb.x; tb.y += rb.y; tb.w = rb.w; tb.h = rb.h;
|
||||
|
||||
debug(" - adjusted render tile bounds %d, %d (%d x %d)\n", rb.x, rb.y, rb.w, rb.h);
|
||||
|
||||
if(pp_rect_empty(&tb)) { debug(" : empty after rendering, skipping\n"); continue; }
|
||||
|
||||
pp_tile_t tile = {
|
||||
.x = tb.x, .y = tb.y, .w = tb.w, .h = tb.h,
|
||||
.stride = (uint32_t)_pp_tile_width,
|
||||
.data = tile_buffer + rb.x + _pp_tile_width * rb.y
|
||||
};
|
||||
|
||||
_pp_tile_callback(&tile);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_RP2040_INTERP
|
||||
interp_restore(interp1, &interp1_save);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // PP_IMPLEMENTATION
|
||||
|
||||
#endif // PP_INCLUDE_H
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a5a1e2d8ab118220527cb578fbb6ce15e8d55fdf
|
|
@ -24,5 +24,5 @@ target_link_libraries(usermod INTERFACE usermod_picovector)
|
|||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_vector/pico_vector.cpp
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
"-Wno-sign-compare -Wno-narrowing"
|
||||
"-Wno-narrowing"
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ extern "C" {
|
|||
#include "py/stream.h"
|
||||
#include "py/reader.h"
|
||||
#include "extmod/vfs.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef struct _ModPicoGraphics_obj_t {
|
||||
mp_obj_base_t base;
|
||||
|
@ -29,10 +30,48 @@ typedef struct _PATH_obj_t {
|
|||
pp_path_t path;
|
||||
} _PATH_obj_t;
|
||||
|
||||
file_io::file_io(std::string_view filename) {
|
||||
mp_obj_t fn = mp_obj_new_str(filename.data(), (mp_uint_t)filename.size());
|
||||
void __printf_debug_flush() {
|
||||
for(auto i = 0u; i < 10; i++) {
|
||||
sleep_ms(1);
|
||||
mp_event_handle_nowait();
|
||||
}
|
||||
}
|
||||
|
||||
//mp_printf(&mp_plat_print, "Opening file %s\n", filename.data());
|
||||
int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args);
|
||||
|
||||
void af_debug(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int ret = mp_vprintf(&mp_plat_print, fmt, ap);
|
||||
va_end(ap);
|
||||
__printf_debug_flush();
|
||||
(void)ret;
|
||||
}
|
||||
|
||||
void *af_malloc(size_t size) {
|
||||
//mp_printf(&mp_plat_print, "af_malloc %lu\n", size);
|
||||
//__printf_debug_flush();
|
||||
void *addr = m_tracked_calloc(sizeof(uint8_t), size);
|
||||
//mp_printf(&mp_plat_print, "addr %lu\n", addr);
|
||||
//__printf_debug_flush();
|
||||
return addr;
|
||||
}
|
||||
|
||||
void *af_realloc(void *p, size_t size) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void af_free(void *p) {
|
||||
//mp_printf(&mp_plat_print, "af_free\n");
|
||||
//__printf_debug_flush();
|
||||
m_tracked_free(p);
|
||||
}
|
||||
|
||||
void* fileio_open(const char *filename) {
|
||||
mp_obj_t fn = mp_obj_new_str(filename, (mp_uint_t)strlen(filename));
|
||||
|
||||
//mp_printf(&mp_plat_print, "Opening file %s\n", filename);
|
||||
//__printf_debug_flush();
|
||||
|
||||
mp_obj_t args[2] = {
|
||||
fn,
|
||||
|
@ -41,36 +80,44 @@ file_io::file_io(std::string_view filename) {
|
|||
|
||||
// Stat the file to get its size
|
||||
// example tuple response: (32768, 0, 0, 0, 0, 0, 5153, 1654709815, 1654709815, 1654709815)
|
||||
mp_obj_t stat = mp_vfs_stat(fn);
|
||||
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(stat, mp_obj_tuple_t);
|
||||
filesize = mp_obj_get_int(tuple->items[6]);
|
||||
//mp_obj_t stat = mp_vfs_stat(fn);
|
||||
//mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(stat, mp_obj_tuple_t);
|
||||
//int filesize = mp_obj_get_int(tuple->items[6]);
|
||||
//mp_printf(&mp_plat_print, "Size %lu\n", filesize);
|
||||
|
||||
mp_obj_t fhandle = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map);
|
||||
|
||||
this->state = (void *)fhandle;
|
||||
return (void*)fhandle;
|
||||
}
|
||||
|
||||
file_io::~file_io() {
|
||||
mp_stream_close((mp_obj_t)this->state);
|
||||
void fileio_close(void* fhandle) {
|
||||
mp_stream_close((mp_obj_t)fhandle);
|
||||
}
|
||||
|
||||
size_t file_io::read(void *buf, size_t len) {
|
||||
size_t fileio_read(void* fhandle, void *buf, size_t len) {
|
||||
//mp_printf(&mp_plat_print, "Reading %lu bytes\n", len);
|
||||
mp_obj_t fhandle = this->state;
|
||||
//__printf_debug_flush();
|
||||
int error;
|
||||
return mp_stream_read_exactly(fhandle, buf, len, &error);
|
||||
return mp_stream_read_exactly((mp_obj_t)fhandle, buf, len, &error);
|
||||
}
|
||||
|
||||
size_t file_io::tell() {
|
||||
mp_obj_t fhandle = this->state;
|
||||
int fileio_getc(void* fhandle) {
|
||||
unsigned char buf;
|
||||
//mp_printf(&mp_plat_print, "Reading char\n");
|
||||
//__printf_debug_flush();
|
||||
fileio_read(fhandle, (void *)&buf, 1);
|
||||
return (int)buf;
|
||||
}
|
||||
|
||||
size_t fileio_tell(void* fhandle) {
|
||||
struct mp_stream_seek_t seek_s;
|
||||
seek_s.offset = 0;
|
||||
seek_s.whence = SEEK_CUR;
|
||||
|
||||
const mp_stream_p_t *stream_p = mp_get_stream(fhandle);
|
||||
const mp_stream_p_t *stream_p = mp_get_stream((mp_obj_t)fhandle);
|
||||
|
||||
int error;
|
||||
mp_uint_t res = stream_p->ioctl(fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
|
||||
mp_uint_t res = stream_p->ioctl((mp_obj_t)fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
|
||||
if (res == MP_STREAM_ERROR) {
|
||||
mp_raise_OSError(error);
|
||||
}
|
||||
|
@ -78,21 +125,16 @@ size_t file_io::tell() {
|
|||
return seek_s.offset;
|
||||
}
|
||||
|
||||
bool file_io::fail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Re-implementation of stream.c/STATIC mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args)
|
||||
size_t file_io::seek(size_t pos) {
|
||||
mp_obj_t fhandle = this->state;
|
||||
size_t fileio_seek(void* fhandle, size_t pos) {
|
||||
struct mp_stream_seek_t seek_s;
|
||||
seek_s.offset = pos;
|
||||
seek_s.whence = SEEK_SET;
|
||||
|
||||
const mp_stream_p_t *stream_p = mp_get_stream(fhandle);
|
||||
const mp_stream_p_t *stream_p = mp_get_stream((mp_obj_t)fhandle);
|
||||
|
||||
int error;
|
||||
mp_uint_t res = stream_p->ioctl(fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
|
||||
mp_uint_t res = stream_p->ioctl((mp_obj_t)fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
|
||||
if (res == MP_STREAM_ERROR) {
|
||||
mp_raise_OSError(error);
|
||||
}
|
||||
|
@ -347,7 +389,8 @@ mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) {
|
|||
|
||||
if (mp_obj_is_str(font)) {
|
||||
// TODO: Implement when Alright Fonts rewrite is ready
|
||||
//result = self->vector->set_font(mp_obj_to_string_r(font), font_size);
|
||||
GET_STR_DATA_LEN(font, str, str_len);
|
||||
result = self->vector->set_font((const char*)str, font_size);
|
||||
}
|
||||
else {
|
||||
|
||||
|
@ -362,7 +405,7 @@ mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) {
|
|||
int font_size = mp_obj_get_int(size);
|
||||
(void)font_size;
|
||||
// TODO: Implement when Alright Fonts rewrite is ready
|
||||
//self->vector->set_font_size(font_size);
|
||||
self->vector->set_font_size(font_size);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
@ -395,20 +438,26 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
|
|||
|
||||
GET_STR_DATA_LEN(text_obj, str, str_len);
|
||||
|
||||
const std::string_view t((const char*)str, str_len);
|
||||
const std::string_view t((const char *)str, str_len);
|
||||
|
||||
int x = args[ARG_x].u_int;
|
||||
int y = args[ARG_y].u_int;
|
||||
(void)x;
|
||||
(void)y;
|
||||
|
||||
if(args[ARG_angle].u_obj == mp_const_none) {
|
||||
// TODO: Implement when Alright Fonts rewrite is ready
|
||||
//self->vector->text(t, Point(x, y));
|
||||
} else {
|
||||
//self->vector->text(t, Point(x, y), mp_obj_get_float(args[ARG_angle].u_obj));
|
||||
pp_mat3_t tt = pp_mat3_identity();
|
||||
|
||||
if(args[ARG_angle].u_obj != mp_const_none) {
|
||||
pp_mat3_rotate(&tt, mp_obj_get_float(args[ARG_angle].u_obj));
|
||||
}
|
||||
|
||||
pp_mat3_translate(&tt, (float)x, (float)y);
|
||||
|
||||
//mp_printf(&mp_plat_print, "self->vector->text()\n");
|
||||
//__printf_debug_flush();
|
||||
|
||||
self->vector->text(t, &tt);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue