From 2cbe061695aaade145d3aaeea58fbe3a2df17733 Mon Sep 17 00:00:00 2001 From: Jonathan Williamson Date: Sun, 24 Sep 2023 13:07:21 +0100 Subject: [PATCH] fixed node buffer height bug --- .vscode/settings.json | 3 ++- README.md | 16 ++++++------ examples/character.c | 10 ++++---- examples/helpers.h | 7 ++++++ examples/logo.c | 15 ++++++++--- pretty-poly.h | 58 ++++++++++++++++++++++++------------------- 6 files changed, 67 insertions(+), 42 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d8d88a9..76b2fe1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,7 @@ "string_view": "cpp", "unordered_map": "cpp", "__locale": "c", - "optional": "c" + "optional": "c", + "pretty-poly.h": "c" } } \ No newline at end of file diff --git a/README.md b/README.md index 5596793..65993ee 100644 --- a/README.md +++ b/README.md @@ -206,23 +206,25 @@ void tile_render_callback(const pp_tile_t *tile) { Note that on RP2040 interp1 is used by pretty poly. If your callback uses interp1 it must save and restore the state. -### `tile_t` +### `pp_tile_t` Information needed to blend a rendered tile into your framebuffer. ```c++ struct tile_t { - rect_t bounds; // bounds of tile in framebuffer coordinates - unsigned stride; // width of row in bytes - uint8_t *data; // pointer to mask data - - tile_t() {}; - int get_value(int x, int y); + int32_t x, y, w, h; // bounds of tile in framebuffer coordinates + uint32_t stride; // row stride of tile data + uint8_t *data; // pointer to start of mask data }; ``` This object is passed into your callback function for each tile providing the area of the framebuffer to write to with the mask data needed for blending. +`uint8_t pp_tile_get_value(pp_tile_t *tile, int32_t x, int32_t y)` +` + +Returns the value in the tile at `x`, `y ` + ### `rect_t` Defines a rectangle with a top left corner, width, and height. diff --git a/examples/character.c b/examples/character.c index d2c218d..01e07f8 100644 --- a/examples/character.c +++ b/examples/character.c @@ -15,12 +15,12 @@ void set_pen(colour c) { pen = c; } -void tile_render_callback(const pp_tile_t *t) { - for(int32_t y = 0; y < t->h; y++) { - for(int32_t x = 0; x < t->w; x++) { +void tile_render_callback(const pp_tile_t *tile) { + for(int32_t y = 0; y < tile->h; y++) { + for(int32_t x = 0; x < tile->w; x++) { colour alpha_pen = pen; - alpha_pen.rgba.a = alpha(pen.rgba.a, pp_tile_get_value(t, x, y)); - buffer[y + t->y][x + t->x] = blend(buffer[y + t->y][x + t->x], alpha_pen); + alpha_pen.rgba.a = alpha(pen.rgba.a, pp_tile_get_value(tile, x, y)); + buffer[y + tile->y][x + tile->x] = blend(buffer[y + tile->y][x + tile->x], alpha_pen); } } } diff --git a/examples/helpers.h b/examples/helpers.h index 2c1e059..27f66e7 100644 --- a/examples/helpers.h +++ b/examples/helpers.h @@ -2,6 +2,7 @@ #pragma once #include +#include typedef union { struct __attribute__((__packed__)) { @@ -65,4 +66,10 @@ colour create_colour_hsv(float h, float s, float v, float a) { case 4: return create_colour(t, p, bv, a * 255); case 5: return create_colour(bv, p, q, a * 255); } +} + +uint64_t time_ms() { + struct timeval tv; + gettimeofday(&tv, NULL); + return (((uint64_t)tv.tv_sec) * 1000) + (tv.tv_usec / 1000); } \ No newline at end of file diff --git a/examples/logo.c b/examples/logo.c index 41fde4a..ce81f79 100644 --- a/examples/logo.c +++ b/examples/logo.c @@ -1,7 +1,10 @@ #include "ctype.h" + #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" + #include "pretty-poly.h" + #include "helpers.h" const int WIDTH = 1024; @@ -14,11 +17,11 @@ void set_pen(colour c) { } void tile_render_callback(const pp_tile_t *t) { - for(int32_t y = 0; y < t->h; y++) { - for(int32_t x = 0; x < t->w; x++) { + for(int32_t y = t->y; y < t->y + t->h; y++) { + for(int32_t x = t->x; x < t->x + t->w; x++) { colour alpha_pen = pen; alpha_pen.rgba.a = alpha(pen.rgba.a, pp_tile_get_value(t, x, y)); - buffer[y + t->y][x + t->x] = blend(buffer[y + t->y][x + t->x], alpha_pen); + buffer[y][x] = blend(buffer[y][x], alpha_pen); } } } @@ -289,6 +292,8 @@ int main() { char logo_svg_path[] = "M3260 3933 c-168 -179 -274 -287 -503 -520 -248 -253 -248 -253 -1442 -253 -657 0 -1195 -3 -1195 -6 0 -9 124 -189 132 -192 5 -2 8 -9 8 -15 0 -6 9 -20 19 -31 11 -12 27 -35 38 -53 10 -18 31 -50 47 -73 29 -41 29 -41 -59 -173 -49 -73 -93 -133 -97 -135 -4 -2 -8 -9 -8 -14 0 -6 -17 -34 -38 -62 -21 -28 -42 -57 -46 -64 -6 -10 154 -12 805 -12 446 0 814 -1 816 -4 2 -2 -9 -23 -25 -47 -34 -51 -104 -188 -122 -239 -7 -19 -16 -44 -20 -55 -53 -128 -67 -261 -69 -641 -1 -117 -4 -164 -13 -171 -7 -6 -31 -13 -53 -16 -22 -3 -47 -8 -56 -12 -19 -7 -32 20 -50 110 -7 33 -13 61 -15 63 -6 8 -85 -51 -115 -86 -83 -97 -98 -161 -80 -347 20 -205 30 -241 83 -294 45 -46 99 -67 205 -80 126 -15 263 -65 396 -145 35 -20 113 -100 158 -161 24 -33 49 -66 56 -72 6 -7 13 -18 15 -24 3 -9 10 -9 27 0 12 7 20 19 17 26 -2 7 1 16 9 18 7 3 28 36 46 74 30 63 32 76 32 168 0 55 -4 111 -10 125 -6 14 -10 27 -9 30 0 3 -12 27 -28 54 -28 48 -28 48 11 90 82 86 150 228 169 351 6 43 17 61 78 130 39 44 73 82 76 85 43 43 192 269 185 280 -3 6 -2 10 4 10 27 0 190 372 210 480 5 30 13 87 17 125 4 39 8 75 9 80 1 6 3 30 3 55 2 45 2 45 734 43 403 -2 729 0 723 5 -5 4 -14 15 -20 24 -5 9 -65 98 -132 197 -68 99 -123 186 -123 192 0 7 6 20 14 28 8 9 69 97 135 196 122 180 122 180 -494 183 -564 2 -640 5 -606 26 4 3 11 23 15 44 3 21 13 61 21 88 8 27 31 108 51 179 20 72 45 162 55 200 11 39 28 102 39 140 10 39 23 87 30 108 6 21 10 43 8 48 -2 6 -32 -20 -68 -58z m-2188 -993 c-3 -149 1 -152 43 -24 14 43 35 98 46 122 20 43 35 50 87 36 19 -6 22 -11 17 -35 -4 -15 -15 -46 -26 -68 -10 -22 -19 -46 -19 -54 0 -7 -4 -17 -9 -23 -5 -5 -16 -29 -24 -54 -15 -45 -15 -45 18 -82 40 -43 51 -98 41 -195 -12 -112 -50 -143 -177 -143 -43 0 -81 5 -84 10 -8 13 -14 476 -7 578 5 73 5 73 51 70 46 -3 46 -3 43 -138z m476 107 c2 -10 1 -29 -2 -43 -6 -22 -11 -24 -70 -24 -63 0 -64 0 -70 -31 -3 -17 -6 -62 -6 -100 0 -69 0 -69 56 -69 55 0 55 0 52 -42 -3 -43 -3 -43 -55 -46 -53 -3 -53 -3 -53 -93 0 -89 0 -89 70 -89 70 0 70 0 70 -39 0 -48 -4 -50 -127 -50 -69 0 -94 4 -104 15 -9 11 -10 51 -5 162 4 81 7 187 6 236 -2 135 9 234 27 238 8 2 59 1 112 -2 83 -4 96 -8 99 -23z m820 21 c8 -8 12 -53 12 -132 1 -104 12 -189 33 -246 4 -8 8 -28 11 -45 15 -90 19 -111 26 -120 5 -5 10 -30 12 -55 3 -44 3 -45 -30 -48 -44 -4 -59 10 -67 61 -3 23 -10 60 -15 82 -5 22 -12 62 -15 89 -8 59 -21 50 -34 -24 -14 -80 -39 -189 -46 -200 -8 -14 -58 -13 -72 1 -12 12 -9 56 7 94 4 11 15 52 25 90 9 39 26 106 37 150 13 48 23 122 25 185 2 58 6 111 8 118 6 15 67 16 83 0z m869 -32 c43 -46 47 -85 36 -334 -8 -192 -11 -211 -31 -240 -43 -59 -157 -65 -212 -11 -37 37 -43 100 -36 357 6 182 7 195 28 222 30 36 71 50 133 45 41 -4 56 -10 82 -39z m485 -86 c3 -75 15 -159 28 -210 12 -47 26 -105 31 -130 5 -25 14 -65 20 -90 20 -85 18 -95 -19 -98 -41 -4 -62 12 -62 48 0 15 -6 45 -13 66 -7 22 -13 44 -13 49 0 6 -4 39 -9 75 -8 65 -8 65 -25 -5 -10 -38 -23 -101 -31 -140 -7 -38 -19 -76 -25 -82 -16 -17 -59 -17 -72 0 -11 13 -8 31 48 242 23 85 34 156 40 245 10 156 12 162 59 158 36 -3 36 -3 43 -128z m-2979 78 c3 -24 4 -82 3 -129 -3 -87 -3 -87 43 -92 106 -13 134 -53 135 -195 0 -93 -16 -142 -54 -162 -28 -15 -193 -30 -205 -19 -6 6 -11 132 -13 324 -4 315 -4 315 41 315 45 0 45 0 50 -42z m1007 -238 c0 -280 0 -280 46 -280 45 0 45 0 42 -42 -3 -43 -3 -43 -121 -46 -140 -3 -157 3 -157 58 0 40 0 40 45 40 45 0 45 0 46 68 1 37 3 123 5 192 2 69 3 162 4 208 0 82 0 82 45 82 45 0 45 0 45 -280z m312 3 c3 -278 3 -278 46 -281 43 -3 43 -3 40 -45 -3 -42 -3 -42 -118 -45 -134 -3 -170 7 -170 47 0 37 17 51 62 51 43 0 40 -26 39 305 -1 77 2 164 5 193 6 52 6 52 50 52 44 0 44 0 46 -277z m698 148 c0 -128 0 -128 58 -134 50 -5 61 -10 89 -42 24 -28 33 -50 39 -92 16 -130 -14 -214 -84 -232 -40 -11 -168 -18 -175 -11 -8 8 -18 626 -11 633 4 4 25 7 46 7 38 0 38 0 38 -129z m815 84 c0 -40 0 -40 -66 -43 -66 -3 -66 -3 -72 -160 -3 -86 -6 -208 -7 -271 0 -63 -3 -118 -6 -123 -3 -4 -23 -8 -45 -8 -39 0 -39 0 -39 325 0 326 0 326 118 323 117 -3 117 -3 117 -43z"; pp_polygon_t polygon = parse_svg_path(logo_svg_path); + uint64_t start = time_ms(); + for(int i = 240; i <= 360; i += 10) { // determine extreme bounds and scaling factor to fit on canvas pp_rect_t bounds = pp_polygon_bounds(&polygon); @@ -317,6 +322,10 @@ int main() { draw_polygon(&polygon); } + uint64_t end = time_ms(); + printf("render time: %llums", end - start); + + stbi_write_png("/tmp/out.png", WIDTH, HEIGHT, 4, (void *)buffer, WIDTH * sizeof(uint32_t)); diff --git a/pretty-poly.h b/pretty-poly.h index ffc656e..c3e4515 100644 --- a/pretty-poly.h +++ b/pretty-poly.h @@ -6,15 +6,20 @@ // An easy way to render high quality text in embedded applications running // on resource constrained microcontrollers such as the Cortex M0 and up. // -// - renders polygons: concave, self-intersecting, multi contour, holes, etc. +// - Renders polygons: concave, self-intersecting, multi contour, holes, etc. // - C17 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!) +// - 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. #pragma once @@ -27,12 +32,16 @@ #define PP_COORD_TYPE float #endif -#ifndef PP_MAX_INTERSECTIONS -#define PP_MAX_INTERSECTIONS 16 +#ifndef PP_NODE_BUFFER_HEIGHT +#define PP_NODE_BUFFER_HEIGHT 64 +#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 1024 +#define PP_TILE_BUFFER_SIZE 4096 #endif #if defined(PICO_ON_DEVICE) && PICO_ON_DEVICE @@ -46,6 +55,10 @@ #define debug(...) #endif +int _pp_max(int32_t a, int32_t b) { return a > b ? a : b; } +int _pp_min(int32_t a, int32_t b) { return a < b ? a : b; } +int32_t _pp_sign(int32_t v) {return ((uint32_t)-v >> 31) - ((uint32_t)v >> 31);} +void _pp_swap(int32_t *a, int32_t *b) {int32_t t = *a; *a = *b; *b = t;} #ifdef __cplusplus extern "C" { @@ -118,12 +131,6 @@ static void pp_transform(pp_mat3_t *transform); } #endif -// helpers -int _pp_max(int32_t a, int32_t b) { return a > b ? a : b; } -int _pp_min(int32_t a, int32_t b) { return a < b ? a : b; } -int32_t _pp_sign(int32_t v) {return ((uint32_t)-v >> 31) - ((uint32_t)v >> 31);} -void _pp_swap(int32_t *a, int32_t *b) {int32_t t = *a; *a = *b; *b = t;} - // pp_mat3_t implementation static 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;} @@ -214,8 +221,8 @@ pp_rect_t pp_rect_transform(pp_rect_t *r, pp_mat3_t *m) { } // pp_tile_t implementation -int pp_tile_get_value(const pp_tile_t *tile, const int x, const int y) { - return tile->data[x + y * tile->stride] * (255 >> _pp_antialias >> _pp_antialias); +uint8_t pp_tile_get_value(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 @@ -248,9 +255,8 @@ uint8_t tile_buffer[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) -const uint32_t node_buffer_size = PP_MAX_INTERSECTIONS * 2; -int32_t nodes[node_buffer_size][PP_MAX_INTERSECTIONS * 2]; -uint32_t node_counts[node_buffer_size]; +int32_t nodes[PP_NODE_BUFFER_HEIGHT][PP_MAX_NODES_PER_SCANLINE * 2]; +uint32_t node_counts[PP_NODE_BUFFER_HEIGHT]; void pp_clip(uint32_t x, uint32_t y, uint32_t w, uint32_t h) { @@ -266,7 +272,7 @@ int32_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 = node_buffer_size >> _pp_antialias; + _pp_tile_height = PP_NODE_BUFFER_HEIGHT >> _pp_antialias; _pp_tile_width = (int)(tile_buffer_size / _pp_tile_height); } @@ -302,13 +308,13 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { } // Early out if line is completely outside the tile, or has no lines - if (ey < 0 || sy >= (int)node_buffer_size || sy == ey) return; + 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)node_buffer_size, ey) - y; + 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) { @@ -441,7 +447,7 @@ pp_rect_t render_nodes(uint8_t *buffer, pp_rect_t *tb) { PP_COORD_TYPE aa_scale = (PP_COORD_TYPE)(1 << _pp_antialias); int anitialias_mask = (1 << _pp_antialias) - 1; - for(uint32_t y = 0; y < node_buffer_size; y++) { + for(uint32_t y = 0; y < PP_NODE_BUFFER_HEIGHT; y++) { if(node_counts[y] == 0) { if (y == rb.y) ++rb.y; continue;